From 6a3a153a50e5f59bd9073db2762e0795c9580ae5 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Thu, 30 Jan 2025 13:50:57 +0100 Subject: [PATCH 01/85] presumably updated common Signed-off-by: dzdidi --- teos-common/Cargo.toml | 6 +++--- teos-common/src/cryptography.rs | 32 ++++++++++++++++---------------- teos-common/src/test_utils.rs | 11 ++++++----- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/teos-common/Cargo.toml b/teos-common/Cargo.toml index 17766893..1ce96477 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.5", 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 cf5fef32..6b04438c 100644 --- a/teos-common/src/cryptography.rs +++ b/teos-common/src/cryptography.rs @@ -21,7 +21,7 @@ pub enum DecryptingError { /// Shadows [message_signing::sign]. pub fn sign(msg: &[u8], sk: &SecretKey) -> Result { - message_signing::sign(msg, sk) + Ok(message_signing::sign(msg, sk)) } /// Shadows [message_signing::verify]. @@ -47,8 +47,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 +64,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); @@ -100,7 +100,7 @@ mod tests { use bitcoin::hashes::hex::FromHex; const HEX_TX: &str = "010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff54038e830a1b4d696e656420627920416e74506f6f6c373432c2005b005e7a0ae3fabe6d6d7841cd582ead8ea5dd8e3de1173cae6fcd2a53c7362ebb7fb6f815604fe07cbe0200000000000000ac0e060005f90000ffffffff04d9476026000000001976a91411dbe48cc6b617f9c6adaf4d9ed5f625b1c7cb5988ac0000000000000000266a24aa21a9ed7248c6efddd8d99bfddd7f499f0b915bffa8253003cc934df1ff14a81301e2340000000000000000266a24b9e11b6d7054937e13f39529d6ad7e685e9dd4efa426f247d5f5a5bed58cdddb2d0fa60100000000000000002b6a2952534b424c4f434b3a054a68aa5368740e8b3e3c67bce45619c2cfd07d4d4f0936a5612d2d0034fa0a0120000000000000000000000000000000000000000000000000000000000000000000000000"; - const HEX_TXID: &str = "d6ac4a5e61657c4c604dcde855a1db74ec6b3e54f32695d72c5e11c7761ea1b4"; + // const HEX_TXID: &str = "d6ac4a5e61657c4c604dcde855a1db74ec6b3e54f32695d72c5e11c7761ea1b4"; const ENC_BLOB: &str = "f64d730654738fdbcd9e65068be17bc1abb44e74f8977985cce48e77209cf97292c862e4eb7190aedc6c53ceddda6871a3988d1d9608e2d0dd7a1f59769e410618a7029001479ac3b9d699b11a08b0ccb04e56bfee88461d9cd3207623a4a543996dd3805323c93cd62069636305aaf159e9cca1063ad1f097c16fb3c2ebbcf09be96512c5d7c195c684569cbe8b7979870b04cada9806b7610569c66021afcc63f46dd4af75716950c4de094334cdf7d9e532820afe29d2621dd79920c7e0ecc10853517dd84ca9d699f712c229e86954c227cba1d0fc87c8d48ac05e2de8a6bc980afdfafcd7064e411c8d76065c06cc7f233e869eaff5bd8ccb5d8f0090d91a8f017355cc115863356ecf06cdda9b309096ea766d033dbd4f70a789a5b03138cfc7e2900a79bb465abf07a7ac45c41b4b30c008d4b299aad9d001cf45acd07e47cdd63c3b13d4b0788b041735225b5db1a43a2142311f695478168e31deb260702976fd70d0724ded84a7c3f89b"; #[test] @@ -108,17 +108,17 @@ 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 = tx.compute_txid();// Txid::from(HEX_TXID).unwrap(); assert_eq!(encrypt(&tx, &txid).unwrap(), expected_enc_blob); } - #[test] - fn test_decrypt() { - 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(); - assert_eq!(decrypt(&encrypted_blob, &txid).unwrap(), expected_tx); - } + // #[test] + // fn test_decrypt() { + // 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(); + // assert_eq!(decrypt(&encrypted_blob, &txid).unwrap(), expected_tx); + // } } diff --git a/teos-common/src/test_utils.rs b/teos-common/src/test_utils.rs index 0b5a95b2..9123f3e8 100644 --- a/teos-common/src/test_utils.rs +++ b/teos-common/src/test_utils.rs @@ -7,7 +7,7 @@ use rand::Rng; use bitcoin::hashes::Hash; use bitcoin::secp256k1::SecretKey; -use bitcoin::{consensus, Script, Transaction, TxOut, Txid}; +use bitcoin::{consensus, ScriptBuf, Transaction, TxOut, Txid, Amount}; use crate::appointment::{Appointment, Locator}; use crate::cryptography; @@ -48,13 +48,14 @@ 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 random_bytes: [u8; 81] = cryptography::get_random_bytes(get_random_int::() % 81).try_into().unwrap(); + let script_string = format!("6a{}", hex::encode(random_bytes)); + let script_pubkey = ScriptBuf::from(hex::decode(script_string).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, - )), + value: Amount::from_sat(0), + script_pubkey, }); let mut raw_locator: [u8; 16] = cryptography::get_random_bytes(16).try_into().unwrap(); From 91469b5705608d4d73c45d9801c322af6c5b424c Mon Sep 17 00:00:00 2001 From: dzdidi Date: Thu, 30 Jan 2025 15:40:26 +0100 Subject: [PATCH 02/85] presumably updated teos/src/test_utils Signed-off-by: dzdidi --- teos/src/test_utils.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/teos/src/test_utils.rs b/teos/src/test_utils.rs index 4dba0a93..483941ba 100644 --- a/teos/src/test_utils.rs +++ b/teos/src/test_utils.rs @@ -18,8 +18,9 @@ use jsonrpc_http_server::{CloseHandle, Server, ServerBuilder}; use bitcoincore_rpc::{Auth, Client as BitcoindClient}; use bitcoin::blockdata::block::{Block, BlockHeader}; +use bitcoin::Amount; 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; @@ -199,7 +200,7 @@ impl Blockchain { } None => vec![get_random_tx()], }; - let hashes = txdata.iter().map(|obj| obj.txid().as_hash()); + let hashes = txdata.iter().map(|obj| obj.txid().to_raw_hash()); let mut header = BlockHeader { version: 0, prev_blockhash, @@ -289,20 +290,20 @@ pub(crate) fn get_random_tx() -> Transaction { let prev_txid_bytes = get_random_bytes(32); Transaction { - version: 2, - lock_time: 0, + version: ::Version::Two, + 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)), }], } } From c3024d2b9141ca0353eda9a750f2a854516ea8aa Mon Sep 17 00:00:00 2001 From: dzdidi Date: Thu, 30 Jan 2025 15:40:41 +0100 Subject: [PATCH 03/85] presumably updated teos/src/tx_index Signed-off-by: dzdidi --- teos/src/tx_index.rs | 141 +++++++++++++++++++++++++++++-------------- 1 file changed, 96 insertions(+), 45 deletions(-) diff --git a/teos/src/tx_index.rs b/teos/src/tx_index.rs index 404c0474..33842439 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::hash_types::BlockHash; -use bitcoin::{BlockHeader, Transaction, Txid}; +use bitcoin::{Transaction, Txid}; +use bitcoin::block::Header; use lightning_block_sync::poll::ValidatedBlock; use teos_common::appointment::Locator; @@ -111,28 +113,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::FullBlock(_) => {}, + lightning_block_sync::BlockData::HeaderOnly(header) => { + if 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(); + match block.deref() { + lightning_block_sync::BlockData::HeaderOnly(_) => {} + lightning_block_sync::BlockData::FullBlock(block) => { + 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.update(block.header, &map); } tx_index @@ -155,7 +167,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 @@ -220,6 +232,7 @@ mod tests { use crate::test_utils::{get_last_n_blocks, Blockchain}; + use bitcoin::hashes::serde_macros::serde_details::SerdeHash; use bitcoin::Block; impl TxIndex @@ -248,7 +261,10 @@ mod tests { let last_six_blocks = get_last_n_blocks(&mut chain, 6).await; let blocks: Vec = last_six_blocks .iter() - .map(|block| block.deref().clone()) + .map(|block| match block.deref() { + lightning_block_sync::BlockData::FullBlock(b) => b.clone(), + _ => panic!("Expected FullBlock"), + }) .collect(); let cache: TxIndex = TxIndex::new(&last_six_blocks, height); @@ -281,15 +297,26 @@ mod tests { 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 - ); + match first_block.deref() { + lightning_block_sync::BlockData::FullBlock(b) => { + assert_eq!(cache.get_height(&b.header.block_hash()).unwrap(), height - cache_size + 1); + } + _ => panic!("Expected FullBlock"), + } + + match last_block.deref() { + lightning_block_sync::BlockData::FullBlock(b) => { + assert_eq!(cache.get_height(&b.header.block_hash()).unwrap(), height); + } + _ => panic!("Expected FullBlock"), + } + + match mid.deref() { + lightning_block_sync::BlockData::FullBlock(b) => { + assert_eq!(cache.get_height(&b.header.block_hash()).unwrap(), height - cache_size / 2); + } + _ => panic!("Expected FullBlock"), + } } #[tokio::test] @@ -302,8 +329,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] @@ -321,30 +348,54 @@ mod tests { 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(); + let locator_tx_map; + match last_block.deref() { + lightning_block_sync::BlockData::FullBlock(b) => { + locator_tx_map = b + .txdata + .iter() + .map(|tx| (Locator::new(tx.txid()), tx.clone())) + .collect(); + } + _ => panic!("Expected FullBlock"), + } - cache.update(last_block.deref().header, &locator_tx_map); + let header = match last_block.deref() { + lightning_block_sync::BlockData::FullBlock(b) => b.header, + lightning_block_sync::BlockData::HeaderOnly(h) => h.clone() + }; + 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 = match last_block.deref() { + lightning_block_sync::BlockData::FullBlock(b) => b.header.block_hash(), + lightning_block_sync::BlockData::HeaderOnly(h) => h.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 tx = match first_block.deref() { + lightning_block_sync::BlockData::FullBlock(b) => b.txdata[0].clone(), + lightning_block_sync::BlockData::HeaderOnly(_) => { + panic!("Expected FullBlock") + } + }; + assert!(!cache.contains_key(&Locator::new(tx.txid()))); + + let block_hash = match first_block.deref() { + lightning_block_sync::BlockData::FullBlock(b) => b.header.block_hash(), + lightning_block_sync::BlockData::HeaderOnly(h) => h.block_hash(), + }; + assert!(!cache.tx_in_block.contains_key(&block_hash)); } #[tokio::test] From ffaabe4c810ebd2014cf4f88b78f6dfc82106b75 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Thu, 30 Jan 2025 17:38:20 +0100 Subject: [PATCH 04/85] drop bitcoin to 0.32.0 as is used by bitcoind-rpc Signed-off-by: dzdidi --- teos-common/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/teos-common/Cargo.toml b/teos-common/Cargo.toml index 1ce96477..14a45d40 100644 --- a/teos-common/Cargo.toml +++ b/teos-common/Cargo.toml @@ -20,7 +20,7 @@ rand = "0.8.4" chacha20poly1305 = "0.8.0" # Bitcoin and Lightning -bitcoin = { version = "0.32.5", features = [ "serde" ] } +bitcoin = { version = "0.32.0", features = [ "serde" ] } lightning = "0.1.0" [build-dependencies] From 198e1912b2773c851aebab79851efadf5129122f Mon Sep 17 00:00:00 2001 From: dzdidi Date: Thu, 30 Jan 2025 17:38:47 +0100 Subject: [PATCH 05/85] presumably updated teos/src/tx_carrier Signed-off-by: dzdidi --- teos/Cargo.toml | 10 +++++----- teos/src/carrier.rs | 23 ++++++++++++++++++----- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/teos/Cargo.toml b/teos/Cargo.toml index 0fe9339c..cb5cc36e 100644 --- a/teos/Cargo.toml +++ b/teos/Cargo.toml @@ -34,11 +34,11 @@ warp = "0.3.5" torut = "0.2.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/carrier.rs b/teos/src/carrier.rs index 15c9d835..2cd14d90 100644 --- a/teos/src/carrier.rs +++ b/teos/src/carrier.rs @@ -6,7 +6,9 @@ use std::sync::{Arc, Condvar, Mutex}; use crate::responder::ConfirmationStatus; use crate::{errors, rpc_errors}; +use bitcoin::consensus::Encodable; use bitcoin::{Transaction, Txid}; +use bitcoincore_rpc::bitcoin::hashes::hex::ToHex; use bitcoincore_rpc::{ jsonrpc::error::Error::Rpc as RpcError, jsonrpc::error::Error::Transport as TransportError, Client as BitcoindClient, Error::JsonRpc as JsonRpcError, RpcApi, @@ -190,6 +192,7 @@ mod tests { use teos_common::test_utils::{TXID_HEX, TX_HEX}; use bitcoin::consensus; + use bitcoin::hashes::Hash; use bitcoin::hashes::hex::FromHex; use bitcoincore_rpc::Auth; @@ -389,7 +392,9 @@ 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 vec = Vec::from_hex(TXID_HEX).unwrap(); + let hash: bitcoin::hashes::sha256d::Hash = bitcoin::hashes::Hash::from_slice(&vec).unwrap(); + let txid = Txid::from(hash); assert!(carrier.in_mempool(&txid)); } @@ -402,7 +407,9 @@ 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 vec = Vec::from_hex(TXID_HEX).unwrap(); + let hash: bitcoin::hashes::sha256d::Hash = bitcoin::hashes::Hash::from_slice(&vec).unwrap(); + let txid = Txid::from(hash); assert!(!carrier.in_mempool(&txid)); } @@ -417,7 +424,9 @@ 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 vec = Vec::from_hex(TXID_HEX).unwrap(); + let hash: bitcoin::hashes::sha256d::Hash = bitcoin::hashes::Hash::from_slice(&vec).unwrap(); + let txid = Txid::from(hash); assert!(!carrier.in_mempool(&txid)); } @@ -431,7 +440,9 @@ 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 vec = Vec::from_hex(TXID_HEX).unwrap(); + let hash: bitcoin::hashes::sha256d::Hash = bitcoin::hashes::Hash::from_slice(&vec).unwrap(); + let txid = Txid::from(hash); assert!(!carrier.in_mempool(&txid)); } @@ -444,7 +455,9 @@ 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 vec = Vec::from_hex(TXID_HEX).unwrap(); + let hash: bitcoin::hashes::sha256d::Hash = bitcoin::hashes::Hash::from_slice(&vec).unwrap(); + let txid = Txid::from(hash); let delay = std::time::Duration::new(3, 0); thread::spawn(move || { From afd24699e07cc54bbab95acbc0aa2fa723b8b1a2 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Fri, 31 Jan 2025 14:50:40 +0100 Subject: [PATCH 06/85] presumably getting there! Signed-off-by: dzdidi --- Cargo.lock | 1114 +++++++++++++++--------------- Cargo.toml | 2 +- teos/Cargo.toml | 2 +- teos/src/bitcoin_cli.rs | 16 +- teos/src/carrier.rs | 4 +- teos/src/chain_monitor.rs | 8 +- teos/src/dbm.rs | 2 +- teos/src/extended_appointment.rs | 2 +- teos/src/gatekeeper.rs | 4 +- teos/src/responder.rs | 12 +- teos/src/test_utils.rs | 31 +- teos/src/watcher.rs | 7 +- watchtower-plugin/Cargo.toml | 4 +- 13 files changed, 603 insertions(+), 605 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a8ace649..a8112a39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,15 +37,6 @@ dependencies = [ "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" @@ -70,11 +61,17 @@ version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" +[[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", + "synstructure", ] [[package]] @@ -176,7 +173,7 @@ checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" dependencies = [ "async-trait", "axum-core", - "bitflags", + "bitflags 1.3.2", "bytes 1.1.0", "futures-util", "http", @@ -248,60 +245,100 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" +[[package]] +name = "base58ck" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" +dependencies = [ + "bitcoin-internals", + "bitcoin_hashes", +] + [[package]] name = "base64" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.2" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] -name = "base64-compat" -version = "1.0.0" +name = "base64" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a8d4d2746f89841e49230dd26917df1876050f95abafafbe34f47cb534b88d7" -dependencies = [ - "byteorder", -] +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bech32" -version = "0.8.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" +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", + "base64 0.21.7", "bech32", + "bitcoin-internals", + "bitcoin-io", + "bitcoin-units", "bitcoin_hashes", + "hex-conservative", + "hex_lit", "secp256k1", "serde", ] +[[package]] +name = "bitcoin-internals" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2" +dependencies = [ + "serde", +] + +[[package]] +name = "bitcoin-io" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +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.10.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "006cc91e1a1d99819bc5b8214be3555c1f0611b169f527a1fdc54ed1f2b745b0" +checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" dependencies = [ + "bitcoin-io", + "hex-conservative", "serde", ] [[package]] name = "bitcoincore-rpc" -version = "0.15.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0e67dbf7a9971e7f4276f6089e9e814ce0f624a03216b7d92d00351ae7fb3e" +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" @@ -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,13 +507,12 @@ dependencies = [ [[package]] name = "cln-plugin" -version = "0.1.2" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49be99e6e5ad55d420884b5b2a68aca890bcd1a1540ed6d2892363623a60f538" +checksum = "d6b096d2f2a7c58c6faaff1b64281a4c30ff078ec3ca746975fb76ac1a6d6e1f" dependencies = [ "anyhow", "bytes 1.1.0", - "env_logger", "futures", "log", "serde", @@ -477,17 +520,18 @@ dependencies = [ "tokio 1.36.0", "tokio-stream", "tokio-util 0.7.10", + "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" @@ -532,9 +576,9 @@ dependencies = [ [[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.43", ] [[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", ] +[[package]] +name = "dnssec-prover" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96487aad690d45a83f2b9876828ba856c5430bbb143cb5730d8a5d04a4805179" + [[package]] name = "ed25519" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" dependencies = [ "signature", ] @@ -685,53 +716,19 @@ checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" -[[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 = "fallible-iterator" version = "0.2.0" @@ -802,7 +799,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", ] @@ -856,9 +853,9 @@ 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" @@ -941,15 +938,15 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[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.6", + "regex-syntax 0.8.2", ] [[package]] @@ -1000,6 +997,12 @@ dependencies = [ "ahash", ] +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" + [[package]] name = "hashbrown" version = "0.14.3" @@ -1017,18 +1020,18 @@ dependencies = [ [[package]] name = "headers" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cff78e5788be1e0ab65b04d306b2ed5092c815ec97ec70f4ebd5aee158aa55d" +checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" dependencies = [ - "base64 0.13.0", - "bitflags", + "base64 0.13.1", + "bitflags 1.3.2", "bytes 1.1.0", "headers-core", "http", "httpdate 1.0.2", "mime", - "sha-1", + "sha1", ] [[package]] @@ -1066,12 +1069,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 +1082,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 +1109,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]] @@ -1151,12 +1166,6 @@ version = "1.0.2" 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" - [[package]] name = "hyper" version = "0.13.10" @@ -1270,16 +1279,6 @@ 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,21 +1290,9 @@ 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" @@ -1330,22 +1317,23 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" -version = "0.3.58" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" 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 +1387,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.1", +] [[package]] name = "kernel32-sys" @@ -1425,6 +1416,12 @@ version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + [[package]] name = "libsqlite3-sys" version = "0.23.2" @@ -1438,32 +1435,48 @@ 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-invoice" +version = "0.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4254e7d05961a3728bc90737c522e7091735ba6f2f71014096d4b3eb4ee5d89" +dependencies = [ + "bech32", + "bitcoin", + "lightning-types", +] + [[package]] name = "lightning-net-tokio" -version = "0.0.108" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0170619152c4d6b947d5ed0de427b85691482a293e0cae52d4336a2220a776" +checksum = "cb6a6c93b1e592f1d46bb24233cac4a33b4015c99488ee229927a81d16226e45" dependencies = [ "bitcoin", "lightning", @@ -1471,17 +1484,21 @@ dependencies = [ ] [[package]] -name = "linux-raw-sys" -version = "0.1.4" +name = "lightning-types" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +checksum = "f2cd84d4e71472035903e43caded8ecc123066ce466329ccd5ae537a8d5488c7" +dependencies = [ + "bitcoin", +] [[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", ] @@ -1491,11 +1508,20 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "matches" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] name = "matchit" @@ -1517,9 +1543,9 @@ checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" [[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", @@ -1540,6 +1566,17 @@ dependencies = [ "adler", ] +[[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]] name = "mio" version = "0.6.23" @@ -1584,14 +1621,12 @@ 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", "lazy_static", @@ -1630,11 +1665,10 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[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 +1682,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,9 +1693,9 @@ 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", @@ -1669,11 +1703,10 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -1686,38 +1719,37 @@ 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", ] @@ -1733,9 +1765,9 @@ dependencies = [ [[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", ] @@ -1754,11 +1786,11 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[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,28 +1801,27 @@ 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.43", ] [[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", @@ -1805,53 +1836,53 @@ 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.10", "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", ] @@ -1926,6 +1957,15 @@ dependencies = [ "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.11", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -2145,9 +2185,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", @@ -2172,7 +2212,16 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" 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]] @@ -2181,10 +2230,19 @@ version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ - "aho-corasick 1.1.3", + "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.6", + "regex-syntax 0.8.2", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", ] [[package]] @@ -2193,11 +2251,17 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ - "aho-corasick 1.1.3", + "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.2", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.8.2" @@ -2215,11 +2279,11 @@ 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", + "base64 0.21.7", "bytes 1.1.0", "encoding_rs", "futures-core", @@ -2231,15 +2295,18 @@ dependencies = [ "hyper-tls", "ipnet", "js-sys", - "lazy_static", "log", "mime", "native-tls", + "once_cell", "percent-encoding", "pin-project-lite 0.2.13", + "rustls-pemfile 1.0.4", "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", + "system-configuration", "tokio 1.36.0", "tokio-native-tls", "tokio-socks", @@ -2251,24 +2318,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", + "cfg-if 1.0.0", "getrandom 0.2.11", "libc", "spin", "untrusted", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -2277,7 +2339,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", @@ -2294,9 +2356,9 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[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", ] @@ -2310,25 +2372,11 @@ 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.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 +2388,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", @@ -2388,52 +2435,53 @@ checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" [[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 +2490,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,9 +2500,9 @@ 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" @@ -2500,22 +2548,11 @@ dependencies = [ "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", @@ -2547,32 +2584,41 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "signal-hook-registry" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +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", @@ -2686,25 +2732,34 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "synstructure" -version = "0.12.6" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 1.0.98", - "unicode-xid", + "syn 2.0.43", ] [[package]] -name = "synstructure" -version = "0.13.1" +name = "system-configuration" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.43", + "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]] @@ -2726,7 +2781,7 @@ dependencies = [ "cfg-if 1.0.0", "fastrand", "libc", - "redox_syscall", + "redox_syscall 0.2.10", "remove_dir_all", "winapi 0.3.9", ] @@ -2781,15 +2836,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,29 +2847,39 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "6e3de26b0965292219b4287ff031fcba86837900fe9cd2b34ea8ad893c0953d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "268026685b2be38d7103e9e507c938a1fcb3d7e6eb15e87870b617bf37b6d581" dependencies = [ "proc-macro2", "quote", - "syn 1.0.98", + "syn 2.0.43", +] + +[[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", @@ -2844,9 +2900,9 @@ 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", @@ -2854,18 +2910,18 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.5.1" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" +checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" @@ -2896,7 +2952,7 @@ dependencies = [ "libc", "mio 0.8.11", "num_cpus", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "pin-project-lite 0.2.13", "signal-hook-registry", "socket2 0.5.6", @@ -2927,9 +2983,9 @@ dependencies = [ [[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", @@ -2948,9 +3004,9 @@ dependencies = [ [[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", @@ -3011,9 +3067,9 @@ dependencies = [ [[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,7 +3083,7 @@ dependencies = [ "async-stream", "async-trait", "axum", - "base64 0.21.2", + "base64 0.21.7", "bytes 1.1.0", "h2 0.3.25", "http", @@ -3037,7 +3093,7 @@ dependencies = [ "percent-encoding", "pin-project", "prost", - "rustls-pemfile 2.1.1", + "rustls-pemfile 2.2.0", "rustls-pki-types", "tokio 1.36.0", "tokio-rustls", @@ -3068,7 +3124,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", @@ -3143,6 +3199,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", + "valuable", ] [[package]] @@ -3155,6 +3212,35 @@ dependencies = [ "tracing", ] +[[package]] +name = "tracing-log" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60db860322da191b40952ad9affe65ea23e7dd6a5c442c2c42865810c6ab8e6b" +dependencies = [ + "ansi_term", + "matchers", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + [[package]] name = "triggered" version = "0.1.2" @@ -3173,7 +3259,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" dependencies = [ - "base64 0.13.0", + "base64 0.13.1", "byteorder", "bytes 1.1.0", "http", @@ -3194,18 +3280,15 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[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", -] +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-bidi" -version = "0.3.7" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" @@ -3215,30 +3298,24 @@ checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" [[package]] name = "unicode-normalization" -version = "0.1.19" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[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 +3335,12 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.2.2" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +checksum = "22fe195a4f217c25b25cb5058ced57059824a678474874038dc88d211bf508d3" dependencies = [ "form_urlencoded", "idna", - "matches", "percent-encoding", ] @@ -3274,6 +3350,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[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" @@ -3320,7 +3402,7 @@ dependencies = [ "multer", "percent-encoding", "pin-project", - "rustls-pemfile 1.0.3", + "rustls-pemfile 1.0.4", "scoped-tls", "serde", "serde_json", @@ -3347,46 +3429,47 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.81" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if 1.0.0", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.81" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", - "lazy_static", "log", "proc-macro2", "quote", - "syn 1.0.98", + "syn 2.0.43", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.31" +version = "0.4.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de9a9cec1733468a8c657e57fa2413d2ae2c0129b95e87c5b72b8ace4d13f31f" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" 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.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3394,22 +3477,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.81" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", - "syn 1.0.98", + "syn 2.0.43", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.81" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "watchtower-plugin" @@ -3434,9 +3517,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.58" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" dependencies = [ "js-sys", "wasm-bindgen", @@ -3481,15 +3564,6 @@ 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" @@ -3498,48 +3572,29 @@ 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" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 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", + "windows-targets 0.48.5", ] [[package]] name = "windows-sys" -version = "0.48.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] name = "windows-sys" -version = "0.52.0" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.6", ] [[package]] @@ -3559,25 +3614,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 +3636,9 @@ 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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -3610,21 +3648,9 @@ 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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -3634,21 +3660,15 @@ 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" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] -name = "windows_i686_msvc" -version = "0.42.0" +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -3658,21 +3678,9 @@ 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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -3682,15 +3690,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 +3702,9 @@ 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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -3724,17 +3714,18 @@ 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]] @@ -3785,12 +3776,11 @@ dependencies = [ [[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.43", ] diff --git a/Cargo.toml b/Cargo.toml index 76af6ed9..a070916c 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/teos/Cargo.toml b/teos/Cargo.toml index cb5cc36e..c71dd9fe 100644 --- a/teos/Cargo.toml +++ b/teos/Cargo.toml @@ -34,7 +34,7 @@ warp = "0.3.5" torut = "0.2.1" # Bitcoin and Lightning -bitcoin = { version = "0.32.0" } +bitcoin = { version = "0.32.0", features = [ "base64" ] } bitcoincore-rpc = "0.19.0" lightning = "0.1.0" lightning-net-tokio = "0.1.0" diff --git a/teos/src/bitcoin_cli.rs b/teos/src/bitcoin_cli.rs index 22b5338d..779db300 100644 --- a/teos/src/bitcoin_cli.rs +++ b/teos/src/bitcoin_cli.rs @@ -13,16 +13,18 @@ use std::convert::TryInto; use std::io::{Error, ErrorKind}; use std::sync::Arc; use tokio::sync::Mutex; +use std::pin::Pin; use bitcoin::base64; use bitcoin::hash_types::{BlockHash, Txid}; -use bitcoin::hashes::hex::ToHex; +use hex::ToHex; use bitcoin::{Block, Transaction}; +use bitcoin::blockdata::block::Block as BlockData; use bitcoincore_rpc::Auth; 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, BlockHeaderData, BlockSource, BlockSourceError}; /// A simple implementation of a bitcoind client (`bitcoin-cli`) with the minimal functionality required by the tower. pub struct BitcoindClient<'a> { @@ -52,7 +54,7 @@ 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<(BlockHash, Option)> { Box::pin(async move { let rpc = self.bitcoind_rpc_client.lock().await; rpc.get_block(header_hash).await @@ -102,7 +104,7 @@ 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 bitcoind_rpc_client = RpcClient::new(&rpc_credentials, http_endpoint); let client = Self { bitcoind_rpc_client: Arc::new(Mutex::new(bitcoind_rpc_client)), @@ -130,7 +132,7 @@ impl<'a> BitcoindClient<'a> { pub fn get_new_rpc_client(&self) -> std::io::Result { 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)); - RpcClient::new(&rpc_credentials, http_endpoint) + Ok(RpcClient::new(&rpc_credentials, http_endpoint)) } /// Gets the hash of the chain tip and its height. @@ -146,7 +148,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()); rpc.call_method::("sendrawtransaction", &[raw_tx_json]) .await } @@ -155,7 +157,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()); rpc.call_method::("getrawtransaction", &[txid_hex]) .await } diff --git a/teos/src/carrier.rs b/teos/src/carrier.rs index 2cd14d90..5e78c076 100644 --- a/teos/src/carrier.rs +++ b/teos/src/carrier.rs @@ -6,9 +6,9 @@ use std::sync::{Arc, Condvar, Mutex}; use crate::responder::ConfirmationStatus; use crate::{errors, rpc_errors}; -use bitcoin::consensus::Encodable; use bitcoin::{Transaction, Txid}; -use bitcoincore_rpc::bitcoin::hashes::hex::ToHex; +// use bitcoincore_rpc::bitcoin::hashes::hex::ToHex; +// use hex::ToHex; use bitcoincore_rpc::{ jsonrpc::error::Error::Rpc as RpcError, jsonrpc::error::Error::Transport as TransportError, Client as BitcoindClient, Error::JsonRpc as JsonRpcError, RpcApi, diff --git a/teos/src/chain_monitor.rs b/teos/src/chain_monitor.rs index 35534013..be4fa273 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,7 +135,7 @@ mod tests { use std::iter::FromIterator; use std::thread; - use bitcoin::network::constants::Network; + use bitcoin::Network; use bitcoin::BlockHash; use lightning_block_sync::{poll::ChainPoller, SpvClient, UnboundedCache}; @@ -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 a36c9dfb..8834c71d 100644 --- a/teos/src/dbm.rs +++ b/teos/src/dbm.rs @@ -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_string()]) } /// Loads the last known block from the database. diff --git a/teos/src/extended_appointment.rs b/teos/src/extended_appointment.rs index e7ba8f45..1517291c 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().try_into().unwrap()) } /// Serializes the [UUID] returning its byte representation. diff --git a/teos/src/gatekeeper.rs b/teos/src/gatekeeper.rs index e5d490e3..fa94fd83 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 diff --git a/teos/src/responder.rs b/teos/src/responder.rs index b7412cca..e07c0cbf 100644 --- a/teos/src/responder.rs +++ b/teos/src/responder.rs @@ -4,8 +4,10 @@ use std::collections::HashSet; use std::sync::{Arc, Mutex}; use bitcoin::{consensus, BlockHash}; -use bitcoin::{BlockHeader, Transaction, Txid}; +use bitcoin::{Transaction, Txid}; +// use bitcoin::block::Header; use lightning::chain; +use lightning::util::ser::Writeable; use lightning_block_sync::poll::ValidatedBlock; use teos_common::constants; @@ -93,8 +95,8 @@ 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.txid().to_raw_hash().encode(), + penalty_txid: t.penalty_tx.txid().to_raw_hash().encode(), penalty_rawtx: consensus::serialize(&t.penalty_tx), } } @@ -401,7 +403,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, ) { @@ -444,7 +446,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); diff --git a/teos/src/test_utils.rs b/teos/src/test_utils.rs index 483941ba..5648ba91 100644 --- a/teos/src/test_utils.rs +++ b/teos/src/test_utils.rs @@ -17,7 +17,7 @@ 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::Amount; use bitcoin::blockdata::constants::genesis_block; use bitcoin::blockdata::script::{Builder, ScriptBuf}; @@ -25,9 +25,9 @@ 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::Network; +use bitcoin::pow::Work; +use bitcoin::merkle_tree::calculate_root; use bitcoin::Witness; use lightning_block_sync::poll::{ ChainPoller, Poll, Validate, ValidatedBlock, ValidatedBlockHeader, @@ -154,8 +154,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, } @@ -185,7 +188,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::CompactTarget::from_consensus(0xff); let prev_block = self.blocks.last().unwrap(); let prev_blockhash = prev_block.block_hash(); @@ -201,16 +204,16 @@ impl Blockchain { None => vec![get_random_tx()], }; let hashes = txdata.iter().map(|obj| obj.txid().to_raw_hash()); - let mut header = BlockHeader { - version: 0, + 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; } @@ -246,7 +249,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<(Block, Option)> { Box::pin(async move { for (height, block) in self.blocks.iter().enumerate() { if block.header.block_hash() == *header_hash { @@ -290,7 +293,7 @@ pub(crate) fn get_random_tx() -> Transaction { let prev_txid_bytes = get_random_bytes(32); Transaction { - version: ::Version::Two, + version: bitcoin::transaction::Version(2), lock_time: bitcoin::locktime::absolute::LockTime::from_height(0).unwrap(), input: vec![TxIn { previous_output: OutPoint::new( @@ -406,7 +409,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( @@ -425,7 +428,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/watcher.rs b/teos/src/watcher.rs index 037319d7..1d5388f9 100644 --- a/teos/src/watcher.rs +++ b/teos/src/watcher.rs @@ -5,7 +5,8 @@ use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::{Arc, Mutex}; use bitcoin::secp256k1::SecretKey; -use bitcoin::{BlockHeader, Transaction}; +use bitcoin::Transaction; +use bitcoin::block::Header; use lightning::chain; use lightning_block_sync::poll::ValidatedBlock; @@ -493,7 +494,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, ) { @@ -522,7 +523,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() diff --git a/watchtower-plugin/Cargo.toml b/watchtower-plugin/Cargo.toml index ed9aa645..6ddf3911 100755 --- a/watchtower-plugin/Cargo.toml +++ b/watchtower-plugin/Cargo.toml @@ -25,7 +25,7 @@ tonic = { version = "0.11", features = [ "tls", "transport" ] } tokio = { version = "1.5", features = [ "rt-multi-thread", "fs" ] } # Bitcoin and Lightning -bitcoin = "0.28.0" +bitcoin = "0.32.5" cln-plugin = "0.1.2" # Local @@ -33,4 +33,4 @@ 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" From 7e591956c0c8817063c0c676e19636e7584c5b45 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Fri, 31 Jan 2025 18:22:58 +0100 Subject: [PATCH 07/85] presumably getting even closer Signed-off-by: dzdidi --- Cargo.lock | 1194 ++++++++++++++++++++++++--------------- teos/src/bitcoin_cli.rs | 9 +- teos/src/main.rs | 3 +- teos/src/test_utils.rs | 7 +- 4 files changed, 761 insertions(+), 452 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a8112a39..aaabd4f9 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,11 +28,11 @@ 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", ] @@ -57,9 +57,9 @@ 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" @@ -91,7 +91,7 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.96", "synstructure", ] @@ -103,7 +103,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.96", ] [[package]] @@ -118,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]] @@ -161,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" @@ -174,17 +175,17 @@ dependencies = [ "async-trait", "axum-core", "bitflags 1.3.2", - "bytes 1.1.0", + "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", @@ -200,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", @@ -217,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]] @@ -425,17 +426,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]] @@ -507,19 +508,19 @@ dependencies = [ [[package]] name = "cln-plugin" -version = "0.1.9" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b096d2f2a7c58c6faaff1b64281a4c30ff078ec3ca746975fb76ac1a6d6e1f" +checksum = "55eefc811f7d5280586dec7342824a84ab81f1d7e0cdb4cd579c1470e3e236cc" dependencies = [ "anyhow", - "bytes 1.1.0", + "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", ] @@ -567,9 +568,9 @@ 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", ] @@ -646,7 +647,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.43", + "syn 2.0.96", ] [[package]] @@ -676,7 +677,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.96", ] [[package]] @@ -710,9 +711,9 @@ 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" @@ -729,6 +730,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "fallible-iterator" version = "0.2.0" @@ -743,18 +754,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" @@ -779,11 +787,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", ] @@ -811,9 +818,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", @@ -826,9 +833,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", @@ -836,15 +843,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", @@ -859,32 +866,32 @@ 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", @@ -893,16 +900,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", @@ -921,20 +928,32 @@ 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", "wasi 0.11.0+wasi-snapshot-preview1", ] +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +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.28.1" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "globset" @@ -945,8 +964,8 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.6", - "regex-syntax 0.8.2", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", ] [[package]] @@ -960,8 +979,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", @@ -971,20 +990,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", ] @@ -997,6 +1016,12 @@ dependencies = [ "ahash", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.13.2" @@ -1005,9 +1030,9 @@ checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "hashlink" @@ -1020,16 +1045,15 @@ dependencies = [ [[package]] name = "headers" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" +checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" dependencies = [ - "base64 0.13.1", - "bitflags 1.3.2", - "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", "sha1", ] @@ -1040,7 +1064,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" dependencies = [ - "http", + "http 0.2.12", ] [[package]] @@ -1054,9 +1078,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" @@ -1122,9 +1146,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]] @@ -1134,7 +1169,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" dependencies = [ "bytes 0.5.6", - "http", + "http 0.2.12", ] [[package]] @@ -1143,16 +1178,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" @@ -1162,9 +1197,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" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" @@ -1177,7 +1212,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", @@ -1192,23 +1227,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", @@ -1220,9 +1255,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", ] @@ -1232,49 +1267,177 @@ 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 = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "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 = "0.2.3" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", + "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", ] @@ -1296,9 +1459,9 @@ 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", ] @@ -1311,15 +1474,15 @@ 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.76" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", @@ -1391,7 +1554,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ - "cpufeatures 0.2.1", + "cpufeatures 0.2.17", ] [[package]] @@ -1406,15 +1569,15 @@ 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" @@ -1480,7 +1643,7 @@ checksum = "cb6a6c93b1e592f1d46bb24233cac4a33b4015c99488ee229927a81d16226e45" dependencies = [ "bitcoin", "lightning", - "tokio 1.36.0", + "tokio 1.43.0", ] [[package]] @@ -1492,6 +1655,18 @@ dependencies = [ "bitcoin", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +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.12" @@ -1504,9 +1679,9 @@ dependencies = [ [[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 = "matchers" @@ -1517,12 +1692,6 @@ dependencies = [ "regex-automata 0.1.10", ] -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - [[package]] name = "matchit" version = "0.7.3" @@ -1531,15 +1700,15 @@ 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" @@ -1559,11 +1728,11 @@ 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]] @@ -1598,13 +1767,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]] @@ -1628,7 +1797,7 @@ dependencies = [ "assert-json-diff", "colored", "futures", - "hyper 0.14.28", + "hyper 0.14.32", "lazy_static", "log", "rand 0.8.5", @@ -1636,7 +1805,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "similar", - "tokio 1.36.0", + "tokio 1.43.0", ] [[package]] @@ -1645,10 +1814,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", @@ -1659,9 +1828,9 @@ 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" @@ -1701,6 +1870,16 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi 0.3.9", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -1756,9 +1935,9 @@ dependencies = [ [[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", ] @@ -1774,15 +1953,15 @@ dependencies = [ [[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" @@ -1807,7 +1986,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.96", ] [[package]] @@ -1828,6 +2007,12 @@ dependencies = [ "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" @@ -1858,7 +2043,7 @@ dependencies = [ "cfg-if 1.0.0", "instant", "libc", - "redox_syscall 0.2.10", + "redox_syscall 0.2.16", "smallvec", "winapi 0.3.9", ] @@ -1888,38 +2073,38 @@ dependencies = [ [[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]] @@ -1930,9 +2115,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" @@ -1942,9 +2127,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" @@ -1952,7 +2137,7 @@ 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", ] @@ -1963,7 +2148,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b122a615d72104fb3d8b26523fdf9232cd8ee06949fb37e4ce3ff964d15dffd" dependencies = [ - "getrandom 0.2.11", + "getrandom 0.2.15", ] [[package]] @@ -1974,18 +2159,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]] @@ -1997,7 +2185,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn 1.0.98", + "syn 1.0.109", "version_check", ] @@ -2014,31 +2202,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", @@ -2048,38 +2236,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", ] @@ -2118,7 +2305,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha 0.3.1", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -2138,7 +2325,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]] @@ -2167,11 +2354,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]] @@ -2208,9 +2395,9 @@ 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 1.3.2", ] @@ -2226,14 +2413,14 @@ dependencies = [ [[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", "memchr", - "regex-automata 0.4.6", - "regex-syntax 0.8.2", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", ] [[package]] @@ -2247,13 +2434,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.2", + "regex-syntax 0.8.5", ] [[package]] @@ -2264,9 +2451,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "remove_dir_all" @@ -2284,14 +2471,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ "base64 0.21.7", - "bytes 1.1.0", + "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", @@ -2300,14 +2487,14 @@ dependencies = [ "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", "sync_wrapper", "system-configuration", - "tokio 1.36.0", + "tokio 1.43.0", "tokio-native-tls", "tokio-socks", "tower-service", @@ -2326,7 +2513,7 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if 1.0.0", - "getrandom 0.2.11", + "getrandom 0.2.15", "libc", "spin", "untrusted", @@ -2350,9 +2537,9 @@ 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" @@ -2372,6 +2559,19 @@ dependencies = [ "nom", ] +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.8.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + [[package]] name = "rustls" version = "0.22.4" @@ -2423,15 +2623,15 @@ 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" @@ -2506,32 +2706,33 @@ 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", ] @@ -2543,7 +2744,7 @@ 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", ] @@ -2555,7 +2756,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if 1.0.0", - "cpufeatures 0.2.1", + "cpufeatures 0.2.17", "digest 0.10.7", ] @@ -2567,7 +2768,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", ] @@ -2593,6 +2794,12 @@ 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" @@ -2629,15 +2836,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" @@ -2652,9 +2862,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", @@ -2666,6 +2876,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" @@ -2693,20 +2909,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", @@ -2715,9 +2931,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", @@ -2738,7 +2954,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.96", ] [[package]] @@ -2774,16 +2990,16 @@ 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 0.2.10", - "remove_dir_all", - "winapi 0.3.9", + "getrandom 0.3.1", + "once_cell", + "rustix", + "windows-sys 0.59.0", ] [[package]] @@ -2809,7 +3025,7 @@ dependencies = [ "structopt", "tempdir", "teos-common", - "tokio 1.36.0", + "tokio 1.43.0", "tokio-stream", "toml", "tonic", @@ -2847,22 +3063,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.55" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e3de26b0965292219b4287ff031fcba86837900fe9cd2b34ea8ad893c0953d2" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.55" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "268026685b2be38d7103e9e507c938a1fcb3d7e6eb15e87870b617bf37b6d581" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.96", ] [[package]] @@ -2882,7 +3098,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", - "itoa 1.0.10", + "itoa 1.0.14", "libc", "num-conv", "num_threads", @@ -2909,20 +3125,15 @@ dependencies = [ ] [[package]] -name = "tinyvec" -version = "1.8.1" +name = "tinystr" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ - "tinyvec_macros", + "displaydoc", + "zerovec", ] -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "tokio" version = "0.2.25" @@ -2943,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", + "mio 1.0.3", "parking_lot 0.12.3", - "pin-project-lite 0.2.13", + "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]] @@ -2966,19 +3176,19 @@ 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]] @@ -2988,7 +3198,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ "native-tls", - "tokio 1.36.0", + "tokio 1.43.0", ] [[package]] @@ -2999,7 +3209,7 @@ checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" dependencies = [ "rustls", "rustls-pki-types", - "tokio 1.36.0", + "tokio 1.43.0", ] [[package]] @@ -3011,29 +3221,29 @@ 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", ] @@ -3053,16 +3263,15 @@ 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]] @@ -3084,18 +3293,18 @@ dependencies = [ "async-trait", "axum", "base64 0.21.7", - "bytes 1.1.0", - "h2 0.3.25", - "http", + "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.2.0", "rustls-pki-types", - "tokio 1.36.0", + "tokio 1.43.0", "tokio-rustls", "tokio-stream", "tower", @@ -3114,7 +3323,7 @@ dependencies = [ "proc-macro2", "prost-build", "quote", - "syn 2.0.43", + "syn 2.0.96", ] [[package]] @@ -3134,7 +3343,7 @@ dependencies = [ "serde_derive", "sha2", "sha3", - "tokio 1.36.0", + "tokio 1.43.0", ] [[package]] @@ -3145,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", @@ -3159,44 +3368,44 @@ 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", @@ -3214,9 +3423,9 @@ dependencies = [ [[package]] name = "tracing-log" -version = "0.1.4" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ "log", "once_cell", @@ -3225,12 +3434,12 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.15" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60db860322da191b40952ad9affe65ea23e7dd6a5c442c2c42865810c6ab8e6b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ - "ansi_term", "matchers", + "nu-ansi-term", "once_cell", "regex", "sharded-slab", @@ -3249,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.1", "byteorder", - "bytes 1.1.0", - "http", + "bytes 1.9.0", + "data-encoding", + "http 1.2.0", "httparse", "log", "rand 0.8.5", @@ -3274,9 +3483,9 @@ 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" @@ -3284,26 +3493,11 @@ version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" -[[package]] -name = "unicode-bidi" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" - [[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.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" -dependencies = [ - "tinyvec", -] +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" [[package]] name = "unicode-segmentation" @@ -3335,9 +3529,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.3.0" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22fe195a4f217c25b25cb5058ced57059824a678474874038dc88d211bf508d3" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", @@ -3350,6 +3544,18 @@ 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" @@ -3370,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.4", "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", ] @@ -3427,36 +3630,46 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if 1.0.0", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.96", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.49" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -3467,9 +3680,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3477,22 +3690,25 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.96", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "watchtower-plugin" @@ -3511,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.76" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" +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" @@ -3728,6 +3933,27 @@ dependencies = [ "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" @@ -3765,11 +3991,77 @@ dependencies = [ "time", ] +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +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.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" dependencies = [ "zeroize_derive", ] @@ -3782,5 +4074,27 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "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/teos/src/bitcoin_cli.rs b/teos/src/bitcoin_cli.rs index 779db300..e16fb611 100644 --- a/teos/src/bitcoin_cli.rs +++ b/teos/src/bitcoin_cli.rs @@ -13,18 +13,15 @@ use std::convert::TryInto; use std::io::{Error, ErrorKind}; use std::sync::Arc; use tokio::sync::Mutex; -use std::pin::Pin; use bitcoin::base64; use bitcoin::hash_types::{BlockHash, Txid}; -use hex::ToHex; -use bitcoin::{Block, Transaction}; -use bitcoin::blockdata::block::Block as BlockData; +use bitcoin::Transaction; use bitcoincore_rpc::Auth; 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, BlockSourceError}; +use lightning_block_sync::{AsyncBlockSourceResult, BlockHeaderData, BlockSource, BlockData}; /// A simple implementation of a bitcoind client (`bitcoin-cli`) with the minimal functionality required by the tower. pub struct BitcoindClient<'a> { @@ -54,7 +51,7 @@ impl BlockSource for &BitcoindClient<'_> { } /// Gets a block given its hash. - fn get_block<'a>(&'a self, header_hash: &'a BlockHash) -> AsyncBlockSourceResult<(BlockHash, Option)> { + 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 diff --git a/teos/src/main.rs b/teos/src/main.rs index 8cc53c08..99b95e5a 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; diff --git a/teos/src/test_utils.rs b/teos/src/test_utils.rs index 5648ba91..485e3c6f 100644 --- a/teos/src/test_utils.rs +++ b/teos/src/test_utils.rs @@ -33,7 +33,7 @@ use lightning_block_sync::poll::{ ChainPoller, Poll, Validate, ValidatedBlock, ValidatedBlockHeader, }; use lightning_block_sync::{ - AsyncBlockSourceResult, BlockHeaderData, BlockSource, BlockSourceError, UnboundedCache, + AsyncBlockSourceResult, BlockHeaderData, BlockSource, BlockSourceError, UnboundedCache, BlockData }; use teos_common::constants::IRREVOCABLY_RESOLVED; @@ -249,7 +249,7 @@ impl BlockSource for Blockchain { }) } - fn get_block<'a>(&'a self, header_hash: &'a BlockHash) -> AsyncBlockSourceResult<(Block, Option)> { + 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 { @@ -258,8 +258,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")) From 25e8727a1643f305f828cb5dd50de0bdd7240705 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Fri, 31 Jan 2025 20:02:50 +0100 Subject: [PATCH 08/85] build seem to be working Signed-off-by: dzdidi --- teos-common/src/cryptography.rs | 20 +++++----- teos/src/carrier.rs | 2 - teos/src/main.rs | 2 +- teos/src/responder.rs | 1 - watchtower-plugin/Cargo.toml | 4 +- watchtower-plugin/src/main.rs | 65 +++++++++++++++++---------------- 6 files changed, 47 insertions(+), 47 deletions(-) diff --git a/teos-common/src/cryptography.rs b/teos-common/src/cryptography.rs index 6b04438c..79d04d90 100644 --- a/teos-common/src/cryptography.rs +++ b/teos-common/src/cryptography.rs @@ -100,7 +100,7 @@ mod tests { use bitcoin::hashes::hex::FromHex; const HEX_TX: &str = "010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff54038e830a1b4d696e656420627920416e74506f6f6c373432c2005b005e7a0ae3fabe6d6d7841cd582ead8ea5dd8e3de1173cae6fcd2a53c7362ebb7fb6f815604fe07cbe0200000000000000ac0e060005f90000ffffffff04d9476026000000001976a91411dbe48cc6b617f9c6adaf4d9ed5f625b1c7cb5988ac0000000000000000266a24aa21a9ed7248c6efddd8d99bfddd7f499f0b915bffa8253003cc934df1ff14a81301e2340000000000000000266a24b9e11b6d7054937e13f39529d6ad7e685e9dd4efa426f247d5f5a5bed58cdddb2d0fa60100000000000000002b6a2952534b424c4f434b3a054a68aa5368740e8b3e3c67bce45619c2cfd07d4d4f0936a5612d2d0034fa0a0120000000000000000000000000000000000000000000000000000000000000000000000000"; - // const HEX_TXID: &str = "d6ac4a5e61657c4c604dcde855a1db74ec6b3e54f32695d72c5e11c7761ea1b4"; + const HEX_TXID: &str = "d6ac4a5e61657c4c604dcde855a1db74ec6b3e54f32695d72c5e11c7761ea1b4"; const ENC_BLOB: &str = "f64d730654738fdbcd9e65068be17bc1abb44e74f8977985cce48e77209cf97292c862e4eb7190aedc6c53ceddda6871a3988d1d9608e2d0dd7a1f59769e410618a7029001479ac3b9d699b11a08b0ccb04e56bfee88461d9cd3207623a4a543996dd3805323c93cd62069636305aaf159e9cca1063ad1f097c16fb3c2ebbcf09be96512c5d7c195c684569cbe8b7979870b04cada9806b7610569c66021afcc63f46dd4af75716950c4de094334cdf7d9e532820afe29d2621dd79920c7e0ecc10853517dd84ca9d699f712c229e86954c227cba1d0fc87c8d48ac05e2de8a6bc980afdfafcd7064e411c8d76065c06cc7f233e869eaff5bd8ccb5d8f0090d91a8f017355cc115863356ecf06cdda9b309096ea766d033dbd4f70a789a5b03138cfc7e2900a79bb465abf07a7ac45c41b4b30c008d4b299aad9d001cf45acd07e47cdd63c3b13d4b0788b041735225b5db1a43a2142311f695478168e31deb260702976fd70d0724ded84a7c3f89b"; #[test] @@ -109,16 +109,16 @@ mod tests { let tx_bytes = Vec::from_hex(HEX_TX).unwrap(); let tx: Transaction = consensus::deserialize(&tx_bytes).unwrap(); - let txid = tx.compute_txid();// Txid::from(HEX_TXID).unwrap(); + let txid = bitcoin::Txid::from_slice(&Vec::from_hex(HEX_TXID).unwrap()).unwrap(); assert_eq!(encrypt(&tx, &txid).unwrap(), expected_enc_blob); } - // #[test] - // fn test_decrypt() { - // 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(); - // assert_eq!(decrypt(&encrypted_blob, &txid).unwrap(), expected_tx); - // } + #[test] + fn test_decrypt() { + let expected_tx = consensus::deserialize(&Vec::from_hex(HEX_TX).unwrap()).unwrap(); + + let encrypted_blob = Vec::from_hex(ENC_BLOB).unwrap(); + let txid = bitcoin::Txid::from_slice(&Vec::from_hex(HEX_TXID).unwrap()).unwrap(); + assert_eq!(decrypt(&encrypted_blob, &txid).unwrap(), expected_tx); + } } diff --git a/teos/src/carrier.rs b/teos/src/carrier.rs index 5e78c076..c874bfcd 100644 --- a/teos/src/carrier.rs +++ b/teos/src/carrier.rs @@ -7,8 +7,6 @@ use crate::responder::ConfirmationStatus; use crate::{errors, rpc_errors}; use bitcoin::{Transaction, Txid}; -// use bitcoincore_rpc::bitcoin::hashes::hex::ToHex; -// use hex::ToHex; use bitcoincore_rpc::{ jsonrpc::error::Error::Rpc as RpcError, jsonrpc::error::Error::Transport as TransportError, Client as BitcoindClient, Error::JsonRpc as JsonRpcError, RpcApi, diff --git a/teos/src/main.rs b/teos/src/main.rs index 99b95e5a..97c73195 100644 --- a/teos/src/main.rs +++ b/teos/src/main.rs @@ -274,7 +274,7 @@ 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(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 e07c0cbf..d2b42932 100644 --- a/teos/src/responder.rs +++ b/teos/src/responder.rs @@ -5,7 +5,6 @@ use std::sync::{Arc, Mutex}; use bitcoin::{consensus, BlockHash}; use bitcoin::{Transaction, Txid}; -// use bitcoin::block::Header; use lightning::chain; use lightning::util::ser::Writeable; use lightning_block_sync::poll::ValidatedBlock; diff --git a/watchtower-plugin/Cargo.toml b/watchtower-plugin/Cargo.toml index 6ddf3911..d2168351 100755 --- a/watchtower-plugin/Cargo.toml +++ b/watchtower-plugin/Cargo.toml @@ -25,8 +25,8 @@ tonic = { version = "0.11", features = [ "tls", "transport" ] } tokio = { version = "1.5", features = [ "rt-multi-thread", "fs" ] } # Bitcoin and Lightning -bitcoin = "0.32.5" -cln-plugin = "0.1.2" +bitcoin = "0.32.0" +cln-plugin = "0.3.0" # Local teos-common = { path = "../teos-common" } diff --git a/watchtower-plugin/src/main.rs b/watchtower-plugin/src/main.rs index f7942ccd..f4b75f09 100755 --- a/watchtower-plugin/src/main.rs +++ b/watchtower-plugin/src/main.rs @@ -8,8 +8,9 @@ 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::ConfigOption; use cln_plugin::{anyhow, Builder, Error, Plugin}; +use cln_plugin::options::config_type::DefaultInteger; use teos_common::appointment::{Appointment, Locator}; use teos_common::net::http::Endpoint; @@ -28,6 +29,30 @@ 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 +101,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))?, ); @@ -534,26 +559,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, @@ -631,9 +640,7 @@ async fn main() -> Result<(), Error> { let max_elapsed_time = u16::try_from( midstate - .option(constants::WT_MAX_RETRY_TIME) - .unwrap() - .as_i64() + .option(&WT_MAX_RETRY_TIME_CONFIG) .unwrap(), ) .inspect_err(|_| { @@ -642,9 +649,7 @@ async fn main() -> Result<(), Error> { let auto_retry_delay = u32::try_from( midstate - .option(constants::WT_AUTO_RETRY_DELAY) - .unwrap() - .as_i64() + .option(&WT_AUTO_RETRY_DELAY_CONFIG) .unwrap(), ) .inspect_err(|_| { @@ -653,9 +658,7 @@ async fn main() -> Result<(), Error> { let max_interval_time = u16::try_from( midstate - .option(constants::DEV_WT_MAX_RETRY_INTERVAL) - .unwrap() - .as_i64() + .option(&DEV_WT_MAX_RETRY_INTERVAL_CONFIG) .unwrap(), ) .inspect_err(|_| { From 6396d61ba58ec86fbf6b3d85f0cbda2726fa9e72 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Fri, 31 Jan 2025 20:50:28 +0100 Subject: [PATCH 09/85] cargo fix Signed-off-by: dzdidi --- teos/src/carrier.rs | 2 +- teos/src/tx_index.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/teos/src/carrier.rs b/teos/src/carrier.rs index c874bfcd..6690c725 100644 --- a/teos/src/carrier.rs +++ b/teos/src/carrier.rs @@ -190,7 +190,7 @@ mod tests { use teos_common::test_utils::{TXID_HEX, TX_HEX}; use bitcoin::consensus; - use bitcoin::hashes::Hash; + use bitcoin::hashes::hex::FromHex; use bitcoincore_rpc::Auth; diff --git a/teos/src/tx_index.rs b/teos/src/tx_index.rs index 33842439..6672fce0 100644 --- a/teos/src/tx_index.rs +++ b/teos/src/tx_index.rs @@ -342,7 +342,7 @@ 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().deref(); // Init the cache with the 6 block before the last let mut cache = TxIndex::new(&last_n_blocks, height); @@ -383,7 +383,7 @@ mod tests { ); // Check that the data from the first block has been evicted - let tx = match first_block.deref() { + let tx = match first_block { lightning_block_sync::BlockData::FullBlock(b) => b.txdata[0].clone(), lightning_block_sync::BlockData::HeaderOnly(_) => { panic!("Expected FullBlock") @@ -391,7 +391,7 @@ mod tests { }; assert!(!cache.contains_key(&Locator::new(tx.txid()))); - let block_hash = match first_block.deref() { + let block_hash = match first_block { lightning_block_sync::BlockData::FullBlock(b) => b.header.block_hash(), lightning_block_sync::BlockData::HeaderOnly(h) => h.block_hash(), }; From fdd24c39916bfb05f0a2a05c01b8304acf8b5dbd Mon Sep 17 00:00:00 2001 From: dzdidi Date: Sun, 2 Feb 2025 17:16:53 +0100 Subject: [PATCH 10/85] fix cryptography tests Signed-off-by: dzdidi --- teos-common/src/cryptography.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/teos-common/src/cryptography.rs b/teos-common/src/cryptography.rs index 79d04d90..fb4a4ea5 100644 --- a/teos-common/src/cryptography.rs +++ b/teos-common/src/cryptography.rs @@ -1,9 +1,7 @@ //! Cryptography module, used in the interaction between users and towers. use rand::distributions::Uniform; -use rand::Rng; - -use chacha20poly1305::aead::{Aead, NewAead}; +use rand::Rng; use chacha20poly1305::aead::{Aead, NewAead}; use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce}; use bitcoin::consensus; @@ -95,6 +93,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; @@ -109,7 +109,7 @@ mod tests { let tx_bytes = Vec::from_hex(HEX_TX).unwrap(); let tx: Transaction = consensus::deserialize(&tx_bytes).unwrap(); - let txid = bitcoin::Txid::from_slice(&Vec::from_hex(HEX_TXID).unwrap()).unwrap(); + let txid = bitcoin::Txid::from_str(HEX_TXID).unwrap(); assert_eq!(encrypt(&tx, &txid).unwrap(), expected_enc_blob); } @@ -118,7 +118,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 = bitcoin::Txid::from_slice(&Vec::from_hex(HEX_TXID).unwrap()).unwrap(); + let txid = bitcoin::Txid::from_str(HEX_TXID).unwrap(); assert_eq!(decrypt(&encrypted_blob, &txid).unwrap(), expected_tx); } } From 2bc001260087b0a6beb3ffc75366f86d4aafc6d4 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Mon, 3 Feb 2025 13:06:48 +0100 Subject: [PATCH 11/85] fix tests for dbm Signed-off-by: dzdidi --- teos-common/src/test_utils.rs | 7 ++++++- teos/src/carrier.rs | 1 - teos/src/dbm.rs | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/teos-common/src/test_utils.rs b/teos-common/src/test_utils.rs index 9123f3e8..69b9c510 100644 --- a/teos-common/src/test_utils.rs +++ b/teos-common/src/test_utils.rs @@ -48,7 +48,12 @@ 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 random_bytes: [u8; 81] = cryptography::get_random_bytes(get_random_int::() % 81).try_into().unwrap(); + // FIXME: might be cleaner? + // Add 1 to make sure it is not 0 + let size = (get_random_int::() % 81) + 1; + let random_bytes = cryptography::get_random_bytes(size); + let mut fixed_bytes = [0u8; 81]; + fixed_bytes[..random_bytes.len()].copy_from_slice(&random_bytes); let script_string = format!("6a{}", hex::encode(random_bytes)); let script_pubkey = ScriptBuf::from(hex::decode(script_string).unwrap()); diff --git a/teos/src/carrier.rs b/teos/src/carrier.rs index 6690c725..5019950b 100644 --- a/teos/src/carrier.rs +++ b/teos/src/carrier.rs @@ -190,7 +190,6 @@ mod tests { use teos_common::test_utils::{TXID_HEX, TX_HEX}; use bitcoin::consensus; - use bitcoin::hashes::hex::FromHex; use bitcoincore_rpc::Auth; diff --git a/teos/src/dbm.rs b/teos/src/dbm.rs index 8834c71d..30523c55 100644 --- a/teos/src/dbm.rs +++ b/teos/src/dbm.rs @@ -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_string()]) + self.store_data(query, params![block_hash.to_byte_array().to_vec()]) } /// Loads the last known block from the database. From 6c127be579eddae348c8ce0e329512b386c430a6 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Mon, 3 Feb 2025 14:42:51 +0100 Subject: [PATCH 12/85] fix tests pow header req Signed-off-by: dzdidi --- teos/src/test_utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/teos/src/test_utils.rs b/teos/src/test_utils.rs index 485e3c6f..1c4c03b2 100644 --- a/teos/src/test_utils.rs +++ b/teos/src/test_utils.rs @@ -188,7 +188,7 @@ impl Blockchain { } pub fn generate(&mut self, txs: Option>) -> Block { - let bits = bitcoin::CompactTarget::from_consensus(0xff); + let bits = bitcoin::CompactTarget::from_consensus(553713663); let prev_block = self.blocks.last().unwrap(); let prev_blockhash = prev_block.block_hash(); From af86b0e0cd83ed9541e1eabc2268ea16a73fb867 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Mon, 3 Feb 2025 14:54:10 +0100 Subject: [PATCH 13/85] increase timeout for fixing test Signed-off-by: dzdidi --- watchtower-plugin/src/retrier.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/watchtower-plugin/src/retrier.rs b/watchtower-plugin/src/retrier.rs index dcbfaa66..cf0ea46d 100644 --- a/watchtower-plugin/src/retrier.rs +++ b/watchtower-plugin/src/retrier.rs @@ -598,7 +598,7 @@ mod tests { 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; + const MAX_RUN_TIME: f64 = 0.5; macro_rules! wait_until { () => {}; From c006f7de03cfbc60c71e75dfd98134755cb16096 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Mon, 3 Feb 2025 14:59:36 +0100 Subject: [PATCH 14/85] todo comment Signed-off-by: dzdidi --- teos/src/test_utils.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/teos/src/test_utils.rs b/teos/src/test_utils.rs index 1c4c03b2..b1327a2b 100644 --- a/teos/src/test_utils.rs +++ b/teos/src/test_utils.rs @@ -188,6 +188,7 @@ impl Blockchain { } pub fn generate(&mut self, txs: Option>) -> Block { + // FIXME: replace this magic number with something meaningul let bits = bitcoin::CompactTarget::from_consensus(553713663); let prev_block = self.blocks.last().unwrap(); From 7f578808bbb1b8bae4b3c3fa184d1218ed0a2699 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Mon, 3 Feb 2025 15:27:05 +0100 Subject: [PATCH 15/85] fix deprecation warnings Signed-off-by: dzdidi --- Cargo.lock | 2 +- teos/Cargo.toml | 3 ++- teos/src/api/http.rs | 2 +- teos/src/api/internal.rs | 8 +++---- teos/src/bitcoin_cli.rs | 8 ++++--- teos/src/carrier.rs | 28 +++++++++++------------ teos/src/dbm.rs | 12 +++++----- teos/src/responder.rs | 34 ++++++++++++++-------------- teos/src/test_utils.rs | 2 +- teos/src/tx_index.rs | 8 +++---- teos/src/watcher.rs | 48 ++++++++++++++++++++-------------------- 11 files changed, 79 insertions(+), 76 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aaabd4f9..bbf6e718 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -287,7 +287,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce6bc65742dea50536e35ad42492b234c27904a27f0abdcbce605015cb4ea026" dependencies = [ "base58ck", - "base64 0.21.7", "bech32", "bitcoin-internals", "bitcoin-io", @@ -3006,6 +3005,7 @@ dependencies = [ name = "teos" version = "0.2.0" dependencies = [ + "base64 0.22.1", "bitcoin", "bitcoincore-rpc", "hex", diff --git a/teos/Cargo.toml b/teos/Cargo.toml index c71dd9fe..287b0aa9 100644 --- a/teos/Cargo.toml +++ b/teos/Cargo.toml @@ -32,9 +32,10 @@ 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.32.0", features = [ "base64" ] } +bitcoin = { version = "0.32.0" } bitcoincore-rpc = "0.19.0" lightning = "0.1.0" lightning-net-tokio = "0.1.0" diff --git a/teos/src/api/http.rs b/teos/src/api/http.rs index a041ee34..b0faac2d 100644 --- a/teos/src/api/http.rs +++ b/teos/src/api/http.rs @@ -845,7 +845,7 @@ 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 appointment = generate_dummy_appointment(Some(&dispute_tx.compute_txid())).inner; let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); assert_eq!( check_api_error( diff --git a/teos/src/api/internal.rs b/teos/src/api/internal.rs index fc2085d8..884d91d0 100644 --- a/teos/src/api/internal.rs +++ b/teos/src/api/internal.rs @@ -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; @@ -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 @@ -1021,7 +1021,7 @@ 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 appointment = generate_dummy_appointment(Some(&dispute_tx.compute_txid())).inner; let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); match internal_api .add_appointment(Request::new(common_msgs::AddAppointmentRequest { diff --git a/teos/src/bitcoin_cli.rs b/teos/src/bitcoin_cli.rs index e16fb611..3ab4c73e 100644 --- a/teos/src/bitcoin_cli.rs +++ b/teos/src/bitcoin_cli.rs @@ -12,9 +12,9 @@ use std::convert::TryInto; use std::io::{Error, ErrorKind}; use std::sync::Arc; +use base64::{engine::general_purpose, Engine as _}; use tokio::sync::Mutex; -use bitcoin::base64; use bitcoin::hash_types::{BlockHash, Txid}; use bitcoin::Transaction; use bitcoincore_rpc::Auth; @@ -100,7 +100,8 @@ impl<'a> BitcoindClient<'a> { } }?; - let rpc_credentials = base64::encode(&format!("{}:{}", rpc_user, rpc_password)); + let binding = general_purpose::URL_SAFE.encode(format!("{}:{}", rpc_user, rpc_password)); + let rpc_credentials = binding.as_str(); let bitcoind_rpc_client = RpcClient::new(&rpc_credentials, http_endpoint); let client = Self { @@ -128,7 +129,8 @@ impl<'a> BitcoindClient<'a> { /// Gets a fresh RPC client. pub fn get_new_rpc_client(&self) -> std::io::Result { 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 binding = general_purpose::URL_SAFE.encode(format!("{}:{}", self.rpc_user, self.rpc_password)); + let rpc_credentials = binding.as_str(); Ok(RpcClient::new(&rpc_credentials, http_endpoint)) } diff --git a/teos/src/carrier.rs b/teos/src/carrier.rs index 5019950b..8df1ea0e 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 } @@ -217,7 +217,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 +243,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 +261,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 +284,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 +306,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 +326,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 +348,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] diff --git a/teos/src/dbm.rs b/teos/src/dbm.rs index 30523c55..b650225b 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( @@ -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); @@ -1701,7 +1701,7 @@ mod tests { dbm.store_tracker(uuid, &tracker).unwrap(); penalties_summaries - .insert(uuid, PenaltySummary::new(tracker.penalty_tx.txid(), status)); + .insert(uuid, PenaltySummary::new(tracker.penalty_tx.compute_txid(), status)); } assert_eq!(dbm.load_penalties_summaries(), penalties_summaries); diff --git a/teos/src/responder.rs b/teos/src/responder.rs index d2b42932..aaefe000 100644 --- a/teos/src/responder.rs +++ b/teos/src/responder.rs @@ -94,8 +94,8 @@ impl TransactionTracker { impl From for common_msgs::Tracker { fn from(t: TransactionTracker) -> Self { common_msgs::Tracker { - dispute_txid: t.dispute_tx.txid().to_raw_hash().encode(), - penalty_txid: t.penalty_tx.txid().to_raw_hash().encode(), + dispute_txid: t.dispute_tx.compute_txid().to_raw_hash().encode(), + penalty_txid: t.penalty_tx.compute_txid().to_raw_hash().encode(), penalty_rawtx: consensus::serialize(&t.penalty_tx), } } @@ -183,9 +183,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 { @@ -293,7 +293,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(_) => { @@ -369,7 +369,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); @@ -411,7 +411,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); @@ -491,7 +491,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 { @@ -530,7 +530,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 @@ -713,7 +713,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(); @@ -914,7 +914,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( @@ -1187,7 +1187,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 @@ -1215,7 +1215,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) @@ -1251,7 +1251,7 @@ mod tests { 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())); + generate_dummy_appointment_with_user(standalone_user_id, Some(&dispute_tx.compute_txid())); responder .gatekeeper .add_update_appointment(standalone_user_id, uuid, &appointment) @@ -1287,7 +1287,7 @@ mod tests { 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())); + generate_dummy_appointment_with_user(standalone_user_id, Some(&dispute_tx.compute_txid())); responder .gatekeeper .add_update_appointment(standalone_user_id, uuid, &appointment) @@ -1318,7 +1318,7 @@ 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( @@ -1434,7 +1434,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 b1327a2b..8377d03d 100644 --- a/teos/src/test_utils.rs +++ b/teos/src/test_utils.rs @@ -204,7 +204,7 @@ impl Blockchain { } None => vec![get_random_tx()], }; - let hashes = txdata.iter().map(|obj| obj.txid().to_raw_hash()); + let hashes = txdata.iter().map(|obj| obj.compute_txid().to_raw_hash()); let mut header = bitcoin::block::Header { version: bitcoin::block::Version::from_consensus(0), prev_blockhash, diff --git a/teos/src/tx_index.rs b/teos/src/tx_index.rs index 6672fce0..2cbb3fb8 100644 --- a/teos/src/tx_index.rs +++ b/teos/src/tx_index.rs @@ -130,7 +130,7 @@ where .iter() .map(|tx| { ( - K::from_txid(tx.txid()), + K::from_txid(tx.compute_txid()), match V::get_type() { Type::Transaction => V::from_data(Data::Transaction(tx.clone())), Type::BlockHash => { @@ -274,7 +274,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); } @@ -354,7 +354,7 @@ mod tests { locator_tx_map = b .txdata .iter() - .map(|tx| (Locator::new(tx.txid()), tx.clone())) + .map(|tx| (Locator::new(tx.compute_txid()), tx.clone())) .collect(); } _ => panic!("Expected FullBlock"), @@ -389,7 +389,7 @@ mod tests { panic!("Expected FullBlock") } }; - assert!(!cache.contains_key(&Locator::new(tx.txid()))); + assert!(!cache.contains_key(&Locator::new(tx.compute_txid()))); let block_hash = match first_block { lightning_block_sync::BlockData::FullBlock(b) => b.header.block_hash(), diff --git a/teos/src/watcher.rs b/teos/src/watcher.rs index 1d5388f9..e22e848b 100644 --- a/teos/src/watcher.rs +++ b/teos/src/watcher.rs @@ -262,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. @@ -384,7 +384,7 @@ 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, @@ -502,7 +502,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 @@ -733,7 +733,7 @@ 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())); + generate_dummy_appointment_with_user(user_id, Some(&dispute_tx.compute_txid())); let signature = cryptography::sign(&triggered_appointment.inner.to_vec(), &user_sk).unwrap(); let (receipt, slots, expiry) = watcher @@ -763,7 +763,7 @@ 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())); + 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).unwrap(); let (receipt, slots, expiry) = watcher .add_appointment(appointment_in_cache.inner, user_sig.clone()) @@ -780,7 +780,7 @@ 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 (receipt, slots, expiry) = watcher @@ -804,7 +804,7 @@ 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())); + generate_dummy_appointment_with_user(user_id, Some(&dispute_tx.compute_txid())); let user_sig = cryptography::sign(&invalid_appointment.inner.to_vec(), &user_sk).unwrap(); let (receipt, slots, expiry) = watcher .add_appointment(invalid_appointment.inner, user_sig.clone()) @@ -876,7 +876,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)); @@ -918,7 +918,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!( @@ -938,7 +938,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, @@ -966,7 +966,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(); @@ -1056,7 +1056,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(); @@ -1068,7 +1068,7 @@ 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 appointment = generate_dummy_appointment(Some(&tx.compute_txid())).inner; let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); watcher.add_appointment(appointment, signature).unwrap(); breaches.insert(*l, tx.clone()); @@ -1087,7 +1087,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(); @@ -1096,7 +1096,7 @@ mod tests { // Let the watcher track these breaches. for (_, tx) in breaches.iter() { - let appointment = generate_dummy_appointment(Some(&tx.txid())).inner; + let appointment = generate_dummy_appointment(Some(&tx.compute_txid())).inner; let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); watcher.add_appointment(appointment, signature).unwrap(); } @@ -1112,7 +1112,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(); @@ -1123,7 +1123,7 @@ 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 @@ -1155,7 +1155,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(); @@ -1166,7 +1166,7 @@ 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(); watcher.add_appointment(appointment, signature).unwrap(); @@ -1187,7 +1187,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(); @@ -1198,7 +1198,7 @@ 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. @@ -1299,7 +1299,7 @@ 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())); + generate_dummy_appointment_with_user(user2_id, Some(&dispute_tx.compute_txid())); let sig = cryptography::sign(&appointment.inner.to_vec(), &user2_sk).unwrap(); watcher.add_appointment(appointment.inner, sig).unwrap(); @@ -1317,7 +1317,7 @@ 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(); @@ -1336,7 +1336,7 @@ 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())); + generate_dummy_appointment_with_user(user2_id, Some(&dispute_tx.compute_txid())); let sig = cryptography::sign(&appointment.inner.to_vec(), &user2_sk).unwrap(); watcher.add_appointment(appointment.inner, sig).unwrap(); From 618390464a76a458f3051bfce6fba532bc266079 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Mon, 3 Feb 2025 15:34:19 +0100 Subject: [PATCH 16/85] increase timeout for fixing test Signed-off-by: dzdidi --- watchtower-plugin/src/retrier.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/watchtower-plugin/src/retrier.rs b/watchtower-plugin/src/retrier.rs index cf0ea46d..049981a8 100644 --- a/watchtower-plugin/src/retrier.rs +++ b/watchtower-plugin/src/retrier.rs @@ -596,9 +596,9 @@ mod tests { 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_ELAPSED_TIME: u16 = 3; const MAX_INTERVAL_TIME: u16 = 1; - const MAX_RUN_TIME: f64 = 0.5; + const MAX_RUN_TIME: f64 = 1.0; macro_rules! wait_until { () => {}; From 82e0b09bb74d7cc00d6c7b11e8011fb9a378cf9d Mon Sep 17 00:00:00 2001 From: dzdidi Date: Mon, 3 Feb 2025 21:17:34 +0100 Subject: [PATCH 17/85] fmt Signed-off-by: dzdidi --- teos-common/src/cryptography.rs | 5 +-- teos-common/src/test_utils.rs | 2 +- teos/src/bitcoin_cli.rs | 12 ++++--- teos/src/chain_monitor.rs | 2 +- teos/src/dbm.rs | 6 ++-- teos/src/extended_appointment.rs | 7 +++- teos/src/responder.rs | 17 +++++++--- teos/src/test_utils.rs | 9 ++--- teos/src/tx_index.rs | 58 ++++++++++++++++++-------------- teos/src/watcher.rs | 7 ++-- watchtower-plugin/src/main.rs | 41 +++++++++------------- 11 files changed, 94 insertions(+), 72 deletions(-) diff --git a/teos-common/src/cryptography.rs b/teos-common/src/cryptography.rs index fb4a4ea5..548f9f45 100644 --- a/teos-common/src/cryptography.rs +++ b/teos-common/src/cryptography.rs @@ -1,8 +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::aead::{Aead, NewAead}; use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce}; +use rand::distributions::Uniform; +use rand::Rng; use bitcoin::consensus; use bitcoin::hashes::{sha256, Hash}; diff --git a/teos-common/src/test_utils.rs b/teos-common/src/test_utils.rs index 69b9c510..52834d0c 100644 --- a/teos-common/src/test_utils.rs +++ b/teos-common/src/test_utils.rs @@ -7,7 +7,7 @@ use rand::Rng; use bitcoin::hashes::Hash; use bitcoin::secp256k1::SecretKey; -use bitcoin::{consensus, ScriptBuf, Transaction, TxOut, Txid, Amount}; +use bitcoin::{consensus, Amount, ScriptBuf, Transaction, TxOut, Txid}; use crate::appointment::{Appointment, Locator}; use crate::cryptography; diff --git a/teos/src/bitcoin_cli.rs b/teos/src/bitcoin_cli.rs index 3ab4c73e..3232da4c 100644 --- a/teos/src/bitcoin_cli.rs +++ b/teos/src/bitcoin_cli.rs @@ -9,10 +9,10 @@ * at your option. */ +use base64::{engine::general_purpose, Engine as _}; use std::convert::TryInto; use std::io::{Error, ErrorKind}; use std::sync::Arc; -use base64::{engine::general_purpose, Engine as _}; use tokio::sync::Mutex; use bitcoin::hash_types::{BlockHash, Txid}; @@ -21,7 +21,7 @@ use bitcoincore_rpc::Auth; 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, BlockData}; +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> { @@ -51,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, BlockData> { + 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 @@ -129,7 +132,8 @@ impl<'a> BitcoindClient<'a> { /// Gets a fresh RPC client. pub fn get_new_rpc_client(&self) -> std::io::Result { let http_endpoint = HttpEndpoint::for_host(self.host.to_owned()).with_port(self.port); - let binding = general_purpose::URL_SAFE.encode(format!("{}:{}", self.rpc_user, self.rpc_password)); + let binding = + general_purpose::URL_SAFE.encode(format!("{}:{}", self.rpc_user, self.rpc_password)); let rpc_credentials = binding.as_str(); Ok(RpcClient::new(&rpc_credentials, http_endpoint)) } diff --git a/teos/src/chain_monitor.rs b/teos/src/chain_monitor.rs index be4fa273..50a690b3 100644 --- a/teos/src/chain_monitor.rs +++ b/teos/src/chain_monitor.rs @@ -135,8 +135,8 @@ mod tests { use std::iter::FromIterator; use std::thread; - use bitcoin::Network; use bitcoin::BlockHash; + use bitcoin::Network; use lightning_block_sync::{poll::ChainPoller, SpvClient, UnboundedCache}; use crate::test_utils::{Blockchain, START_HEIGHT}; diff --git a/teos/src/dbm.rs b/teos/src/dbm.rs index b650225b..73226287 100644 --- a/teos/src/dbm.rs +++ b/teos/src/dbm.rs @@ -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.compute_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 1517291c..a46e92ec 100644 --- a/teos/src/extended_appointment.rs +++ b/teos/src/extended_appointment.rs @@ -23,7 +23,12 @@ 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).to_byte_array().try_into().unwrap()) + UUID( + ripemd160::Hash::hash(&uuid_data) + .to_byte_array() + .try_into() + .unwrap(), + ) } /// Serializes the [UUID] returning its byte representation. diff --git a/teos/src/responder.rs b/teos/src/responder.rs index aaefe000..49b53671 100644 --- a/teos/src/responder.rs +++ b/teos/src/responder.rs @@ -1250,8 +1250,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.compute_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) @@ -1286,8 +1288,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.compute_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) @@ -1318,7 +1322,10 @@ mod tests { .lock() .unwrap() .get_issued_receipts() - .insert(get_random_tx().compute_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( diff --git a/teos/src/test_utils.rs b/teos/src/test_utils.rs index 8377d03d..9927b4c4 100644 --- a/teos/src/test_utils.rs +++ b/teos/src/test_utils.rs @@ -18,22 +18,23 @@ use jsonrpc_http_server::{CloseHandle, Server, ServerBuilder}; use bitcoincore_rpc::{Auth, Client as BitcoindClient}; use bitcoin::block::Block; -use bitcoin::Amount; use bitcoin::blockdata::constants::genesis_block; 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; -use bitcoin::pow::Work; 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, BlockData + AsyncBlockSourceResult, BlockData, BlockHeaderData, BlockSource, BlockSourceError, + UnboundedCache, }; use teos_common::constants::IRREVOCABLY_RESOLVED; diff --git a/teos/src/tx_index.rs b/teos/src/tx_index.rs index 2cbb3fb8..d5197050 100644 --- a/teos/src/tx_index.rs +++ b/teos/src/tx_index.rs @@ -3,9 +3,9 @@ use std::fmt; use std::hash::Hash; use std::ops::Deref; +use bitcoin::block::Header; use bitcoin::hash_types::BlockHash; use bitcoin::{Transaction, Txid}; -use bitcoin::block::Header; use lightning_block_sync::poll::ValidatedBlock; use teos_common::appointment::Locator; @@ -114,7 +114,7 @@ where for block in last_n_blocks.iter().rev() { if let Some(prev_block_hash) = tx_index.blocks.back() { match block.deref() { - lightning_block_sync::BlockData::FullBlock(_) => {}, + lightning_block_sync::BlockData::FullBlock(_) => {} lightning_block_sync::BlockData::HeaderOnly(header) => { if header.prev_blockhash != *prev_block_hash { panic!("last_n_blocks contains unchained blocks"); @@ -126,25 +126,27 @@ where match block.deref() { lightning_block_sync::BlockData::HeaderOnly(_) => {} lightning_block_sync::BlockData::FullBlock(block) => { - 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(); + 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 @@ -299,7 +301,10 @@ mod tests { match first_block.deref() { lightning_block_sync::BlockData::FullBlock(b) => { - assert_eq!(cache.get_height(&b.header.block_hash()).unwrap(), height - cache_size + 1); + assert_eq!( + cache.get_height(&b.header.block_hash()).unwrap(), + height - cache_size + 1 + ); } _ => panic!("Expected FullBlock"), } @@ -313,7 +318,10 @@ mod tests { match mid.deref() { lightning_block_sync::BlockData::FullBlock(b) => { - assert_eq!(cache.get_height(&b.header.block_hash()).unwrap(), height - cache_size / 2); + assert_eq!( + cache.get_height(&b.header.block_hash()).unwrap(), + height - cache_size / 2 + ); } _ => panic!("Expected FullBlock"), } @@ -352,17 +360,17 @@ mod tests { match last_block.deref() { lightning_block_sync::BlockData::FullBlock(b) => { locator_tx_map = b - .txdata - .iter() - .map(|tx| (Locator::new(tx.compute_txid()), tx.clone())) - .collect(); + .txdata + .iter() + .map(|tx| (Locator::new(tx.compute_txid()), tx.clone())) + .collect(); } _ => panic!("Expected FullBlock"), } let header = match last_block.deref() { lightning_block_sync::BlockData::FullBlock(b) => b.header, - lightning_block_sync::BlockData::HeaderOnly(h) => h.clone() + lightning_block_sync::BlockData::HeaderOnly(h) => h.clone(), }; cache.update(header, &locator_tx_map); diff --git a/teos/src/watcher.rs b/teos/src/watcher.rs index e22e848b..be71c091 100644 --- a/teos/src/watcher.rs +++ b/teos/src/watcher.rs @@ -4,9 +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::Transaction; -use bitcoin::block::Header; use lightning::chain; use lightning_block_sync::poll::ValidatedBlock; @@ -384,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.compute_txid()) { + match cryptography::decrypt( + appointment.encrypted_blob(), + &dispute_tx.compute_txid(), + ) { Ok(penalty_tx) => { if let ConfirmationStatus::Rejected(_) = self.responder.handle_breach( uuid, diff --git a/watchtower-plugin/src/main.rs b/watchtower-plugin/src/main.rs index f4b75f09..17f78d42 100755 --- a/watchtower-plugin/src/main.rs +++ b/watchtower-plugin/src/main.rs @@ -8,9 +8,9 @@ use serde_json::json; use tokio::io::{stdin, stdout}; use tokio::sync::mpsc::unbounded_channel; +use cln_plugin::options::config_type::DefaultInteger; use cln_plugin::options::ConfigOption; use cln_plugin::{anyhow, Builder, Error, Plugin}; -use cln_plugin::options::config_type::DefaultInteger; use teos_common::appointment::{Appointment, Locator}; use teos_common::net::http::Endpoint; @@ -29,11 +29,12 @@ 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 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, @@ -638,28 +639,18 @@ async fn main() -> Result<(), Error> { .await, )); - 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 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(&WT_AUTO_RETRY_DELAY_CONFIG) - .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(&DEV_WT_MAX_RETRY_INTERVAL_CONFIG) - .unwrap(), + midstate.option(&DEV_WT_MAX_RETRY_INTERVAL_CONFIG).unwrap(), ) .inspect_err(|_| { log::error!("{} out of range", constants::DEV_WT_MAX_RETRY_INTERVAL); From d13e1ad8751b517a5a5a3170aaebd951e1a61e96 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Mon, 10 Feb 2025 12:38:40 +0100 Subject: [PATCH 18/85] fmt, clippy, review Signed-off-by: dzdidi --- teos-common/src/test_utils.rs | 13 +++++-------- teos/src/bitcoin_cli.rs | 9 ++++----- teos/src/extended_appointment.rs | 7 +------ teos/src/responder.rs | 4 ++-- teos/src/test_utils.rs | 4 ++-- teos/src/tx_index.rs | 23 +++++++++++------------ 6 files changed, 25 insertions(+), 35 deletions(-) diff --git a/teos-common/src/test_utils.rs b/teos-common/src/test_utils.rs index 52834d0c..c59ec7d3 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; @@ -48,14 +49,10 @@ 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(); - // FIXME: might be cleaner? - // Add 1 to make sure it is not 0 - let size = (get_random_int::() % 81) + 1; - let random_bytes = cryptography::get_random_bytes(size); - let mut fixed_bytes = [0u8; 81]; - fixed_bytes[..random_bytes.len()].copy_from_slice(&random_bytes); - let script_string = format!("6a{}", hex::encode(random_bytes)); - let script_pubkey = ScriptBuf::from(hex::decode(script_string).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 { diff --git a/teos/src/bitcoin_cli.rs b/teos/src/bitcoin_cli.rs index 3232da4c..0863f219 100644 --- a/teos/src/bitcoin_cli.rs +++ b/teos/src/bitcoin_cli.rs @@ -17,7 +17,7 @@ use tokio::sync::Mutex; use bitcoin::hash_types::{BlockHash, Txid}; use bitcoin::Transaction; -use bitcoincore_rpc::Auth; +use bitcoincore_rpc::{Auth, RawTx}; use lightning::util::ser::Writeable; use lightning_block_sync::http::{HttpEndpoint, JsonResponse}; use lightning_block_sync::rpc::RpcClient; @@ -105,7 +105,7 @@ impl<'a> BitcoindClient<'a> { let binding = general_purpose::URL_SAFE.encode(format!("{}:{}", rpc_user, rpc_password)); let rpc_credentials = binding.as_str(); - let bitcoind_rpc_client = RpcClient::new(&rpc_credentials, http_endpoint); + let bitcoind_rpc_client = RpcClient::new(rpc_credentials, http_endpoint); let client = Self { bitcoind_rpc_client: Arc::new(Mutex::new(bitcoind_rpc_client)), @@ -132,9 +132,8 @@ impl<'a> BitcoindClient<'a> { /// Gets a fresh RPC client. pub fn get_new_rpc_client(&self) -> std::io::Result { let http_endpoint = HttpEndpoint::for_host(self.host.to_owned()).with_port(self.port); - let binding = + let rpc_credentials = general_purpose::URL_SAFE.encode(format!("{}:{}", self.rpc_user, self.rpc_password)); - let rpc_credentials = binding.as_str(); Ok(RpcClient::new(&rpc_credentials, http_endpoint)) } @@ -151,7 +150,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()); + let raw_tx_json = serde_json::json!(raw_tx.encode().raw_hex()); rpc.call_method::("sendrawtransaction", &[raw_tx_json]) .await } diff --git a/teos/src/extended_appointment.rs b/teos/src/extended_appointment.rs index a46e92ec..6545a7f7 100644 --- a/teos/src/extended_appointment.rs +++ b/teos/src/extended_appointment.rs @@ -23,12 +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) - .to_byte_array() - .try_into() - .unwrap(), - ) + UUID(ripemd160::Hash::hash(&uuid_data).to_byte_array()) } /// Serializes the [UUID] returning its byte representation. diff --git a/teos/src/responder.rs b/teos/src/responder.rs index 49b53671..241f09b5 100644 --- a/teos/src/responder.rs +++ b/teos/src/responder.rs @@ -94,8 +94,8 @@ impl TransactionTracker { impl From for common_msgs::Tracker { fn from(t: TransactionTracker) -> Self { common_msgs::Tracker { - dispute_txid: t.dispute_tx.compute_txid().to_raw_hash().encode(), - penalty_txid: t.penalty_tx.compute_txid().to_raw_hash().encode(), + dispute_txid: t.dispute_tx.compute_txid().encode(), + penalty_txid: t.penalty_tx.compute_txid().encode(), penalty_rawtx: consensus::serialize(&t.penalty_tx), } } diff --git a/teos/src/test_utils.rs b/teos/src/test_utils.rs index 9927b4c4..9137914a 100644 --- a/teos/src/test_utils.rs +++ b/teos/src/test_utils.rs @@ -410,7 +410,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.as_slice(), height, carrier, gatekeeper, dbm) + Responder::new(last_n_blocks.as_slice(), height, carrier, gatekeeper, dbm) } pub(crate) async fn create_watcher( @@ -429,7 +429,7 @@ pub(crate) async fn create_watcher( Watcher::new( gatekeeper, responder, - &last_n_blocks.as_slice(), + 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 d5197050..8dbb21b0 100644 --- a/teos/src/tx_index.rs +++ b/teos/src/tx_index.rs @@ -124,7 +124,9 @@ where }; match block.deref() { - lightning_block_sync::BlockData::HeaderOnly(_) => {} + lightning_block_sync::BlockData::HeaderOnly(_) => { + panic!("Expected FullBlock") + } lightning_block_sync::BlockData::FullBlock(block) => { let map = block .txdata @@ -356,21 +358,18 @@ mod tests { let mut cache = TxIndex::new(&last_n_blocks, height); // Update the cache with the last block - let locator_tx_map; - match last_block.deref() { - lightning_block_sync::BlockData::FullBlock(b) => { - locator_tx_map = b - .txdata - .iter() - .map(|tx| (Locator::new(tx.compute_txid()), tx.clone())) - .collect(); - } + let locator_tx_map = match last_block.deref() { + lightning_block_sync::BlockData::FullBlock(b) => b + .txdata + .iter() + .map(|tx| (Locator::new(tx.compute_txid()), tx.clone())) + .collect(), _ => panic!("Expected FullBlock"), - } + }; let header = match last_block.deref() { lightning_block_sync::BlockData::FullBlock(b) => b.header, - lightning_block_sync::BlockData::HeaderOnly(h) => h.clone(), + lightning_block_sync::BlockData::HeaderOnly(h) => *h, }; cache.update(header, &locator_tx_map); From 01b4e42960c820dfd186a45d811984c41bc66507 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Mon, 10 Feb 2025 13:09:42 +0100 Subject: [PATCH 19/85] add msrv and tool chain file Signed-off-by: dzdidi --- DEPENDENCIES.md | 2 +- rust-toolchain.toml | 6 ++++++ teos/src/bitcoin_cli.rs | 11 +++++------ 3 files changed, 12 insertions(+), 7 deletions(-) create mode 100644 rust-toolchain.toml diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index 4efa2041..3ea5e114 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 00000000..8a95a070 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,6 @@ +[toolchain] +channel = "1.81.0" +components = [ + "rustfmt", + "clippy", +] diff --git a/teos/src/bitcoin_cli.rs b/teos/src/bitcoin_cli.rs index 0863f219..41aaacbd 100644 --- a/teos/src/bitcoin_cli.rs +++ b/teos/src/bitcoin_cli.rs @@ -9,7 +9,7 @@ * at your option. */ -use base64::{engine::general_purpose, Engine as _}; +use base64::{engine::general_purpose::URL_SAFE, Engine as _}; use std::convert::TryInto; use std::io::{Error, ErrorKind}; use std::sync::Arc; @@ -103,9 +103,8 @@ impl<'a> BitcoindClient<'a> { } }?; - let binding = general_purpose::URL_SAFE.encode(format!("{}:{}", rpc_user, rpc_password)); - let rpc_credentials = binding.as_str(); - let bitcoind_rpc_client = RpcClient::new(rpc_credentials, http_endpoint); + let rpc_credentials = URL_SAFE.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)), @@ -133,7 +132,7 @@ impl<'a> BitcoindClient<'a> { pub fn get_new_rpc_client(&self) -> std::io::Result { let http_endpoint = HttpEndpoint::for_host(self.host.to_owned()).with_port(self.port); let rpc_credentials = - general_purpose::URL_SAFE.encode(format!("{}:{}", self.rpc_user, self.rpc_password)); + URL_SAFE.encode(format!("{}:{}", self.rpc_user, self.rpc_password)); Ok(RpcClient::new(&rpc_credentials, http_endpoint)) } @@ -159,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()); + let txid_hex = serde_json::json!(txid.encode().raw_hex()); rpc.call_method::("getrawtransaction", &[txid_hex]) .await } From de4cf7612a6b32568ce51815ffe2c65426f07561 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Mon, 10 Feb 2025 15:57:32 +0100 Subject: [PATCH 20/85] revert const to original values, await for idling Signed-off-by: dzdidi --- watchtower-plugin/src/retrier.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/watchtower-plugin/src/retrier.rs b/watchtower-plugin/src/retrier.rs index 049981a8..d806bf97 100644 --- a/watchtower-plugin/src/retrier.rs +++ b/watchtower-plugin/src/retrier.rs @@ -596,9 +596,9 @@ mod tests { 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 = 3; + const MAX_ELAPSED_TIME: u16 = 2; const MAX_INTERVAL_TIME: u16 = 1; - const MAX_RUN_TIME: f64 = 1.0; + const MAX_RUN_TIME: f64 = 0.2; macro_rules! wait_until { () => {}; @@ -1236,12 +1236,13 @@ mod tests { { // After the retriers gives up, it should go idling and flag the tower as unreachable tokio::time::sleep(Duration::from_secs_f64( - MAX_ELAPSED_TIME as f64 + MAX_RUN_TIME, + 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); From 9d49657a03608ea8dd5a3981fb6a239b9777ca6c Mon Sep 17 00:00:00 2001 From: dzdidi Date: Mon, 10 Feb 2025 16:43:01 +0100 Subject: [PATCH 21/85] fmt Signed-off-by: dzdidi --- teos-common/src/test_utils.rs | 3 ++- teos/src/bitcoin_cli.rs | 3 +-- watchtower-plugin/src/retrier.rs | 9 +++++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/teos-common/src/test_utils.rs b/teos-common/src/test_utils.rs index c59ec7d3..4bd5cb91 100644 --- a/teos-common/src/test_utils.rs +++ b/teos-common/src/test_utils.rs @@ -51,7 +51,8 @@ pub fn generate_random_appointment(dispute_txid: Option<&Txid>) -> Appointment { 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(); + 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. diff --git a/teos/src/bitcoin_cli.rs b/teos/src/bitcoin_cli.rs index 41aaacbd..02970d98 100644 --- a/teos/src/bitcoin_cli.rs +++ b/teos/src/bitcoin_cli.rs @@ -131,8 +131,7 @@ impl<'a> BitcoindClient<'a> { /// Gets a fresh RPC client. pub fn get_new_rpc_client(&self) -> std::io::Result { let http_endpoint = HttpEndpoint::for_host(self.host.to_owned()).with_port(self.port); - let rpc_credentials = - URL_SAFE.encode(format!("{}:{}", self.rpc_user, self.rpc_password)); + let rpc_credentials = URL_SAFE.encode(format!("{}:{}", self.rpc_user, self.rpc_password)); Ok(RpcClient::new(&rpc_credentials, http_endpoint)) } diff --git a/watchtower-plugin/src/retrier.rs b/watchtower-plugin/src/retrier.rs index d806bf97..7219a5dc 100644 --- a/watchtower-plugin/src/retrier.rs +++ b/watchtower-plugin/src/retrier.rs @@ -1236,11 +1236,16 @@ mod tests { { // After the retriers gives up, it should go idling and flag the tower as unreachable tokio::time::sleep(Duration::from_secs_f64( - MAX_ELAPSED_TIME as f64 + MAX_RUN_TIME + MAX_ELAPSED_TIME as f64 + MAX_RUN_TIME, )) .await; - wait_until!(wt_client.lock().unwrap().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(); From 73f49cef447412b4a8889ca6f326535b08f9a297 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Tue, 11 Feb 2025 09:47:52 +0100 Subject: [PATCH 22/85] ci: cln v24.11.1 Signed-off-by: dzdidi --- .github/workflows/cln-plugin.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cln-plugin.yaml b/.github/workflows/cln-plugin.yaml index 15b74ff3..dda4850e 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: From 7cf1f1eb08939c1b8390d995f790dbcdb92e8953 Mon Sep 17 00:00:00 2001 From: sangbida Date: Tue, 25 Feb 2025 14:22:02 +1000 Subject: [PATCH 23/85] Add rust toolchain to cln-plugin.yml Update toolchain version Change the path to link cln --- .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 dda4850e..1db0e40a 100644 --- a/.github/workflows/cln-plugin.yaml +++ b/.github/workflows/cln-plugin.yaml @@ -53,11 +53,16 @@ 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 tar -xzf bitcoin-${{ env.bitcoind_version }}-x86_64-linux-gnu.tar.gz - ln -s $(pwd)/bitcoin-${{ env.bitcoind_version }}/bin/bitcoin* /usr/local/bin + sudo ln -s $(pwd)/bitcoin-${{ env.bitcoind_version }}/bin/bitcoin* /usr/local/bin - name: Load CLN cache id: cache-cln uses: actions/cache@v4 @@ -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 From 30fc64093d1aa7c29a5cf5891a2616c24f0e8f66 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Mon, 3 Mar 2025 10:37:19 +0100 Subject: [PATCH 24/85] import base64 encoder, dont wrap client creation in cli Signed-off-by: dzdidi --- teos/src/bitcoin_cli.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/teos/src/bitcoin_cli.rs b/teos/src/bitcoin_cli.rs index 02970d98..4b73bdec 100644 --- a/teos/src/bitcoin_cli.rs +++ b/teos/src/bitcoin_cli.rs @@ -9,7 +9,7 @@ * at your option. */ -use base64::{engine::general_purpose::URL_SAFE, Engine as _}; +use base64::{engine::general_purpose::URL_SAFE as BASE64, Engine}; use std::convert::TryInto; use std::io::{Error, ErrorKind}; use std::sync::Arc; @@ -103,7 +103,7 @@ impl<'a> BitcoindClient<'a> { } }?; - let rpc_credentials = URL_SAFE.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 { @@ -129,10 +129,11 @@ 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 = URL_SAFE.encode(format!("{}:{}", self.rpc_user, self.rpc_password)); - Ok(RpcClient::new(&rpc_credentials, http_endpoint)) + let rpc_credentials = BASE64.encode(format!("{}:{}", self.rpc_user, self.rpc_password)); + + RpcClient::new(&rpc_credentials, http_endpoint) } /// Gets the hash of the chain tip and its height. From 2ae307eb726cf5b804afdf64c550855605252f27 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Mon, 3 Mar 2025 10:45:26 +0100 Subject: [PATCH 25/85] unfailible sign Signed-off-by: dzdidi --- teos-common/src/cryptography.rs | 4 +-- teos-common/src/receipts.rs | 5 ++-- teos/src/api/http.rs | 21 +++++--------- teos/src/api/internal.rs | 40 +++++++++++++-------------- teos/src/gatekeeper.rs | 2 +- teos/src/watcher.rs | 47 ++++++++++++++++---------------- watchtower-plugin/src/main.rs | 8 ++---- watchtower-plugin/src/retrier.rs | 18 ++++++------ 8 files changed, 67 insertions(+), 78 deletions(-) diff --git a/teos-common/src/cryptography.rs b/teos-common/src/cryptography.rs index 548f9f45..5dee4779 100644 --- a/teos-common/src/cryptography.rs +++ b/teos-common/src/cryptography.rs @@ -19,8 +19,8 @@ pub enum DecryptingError { } /// Shadows [message_signing::sign]. -pub fn sign(msg: &[u8], sk: &SecretKey) -> Result { - Ok(message_signing::sign(msg, sk)) +pub fn sign(msg: &[u8], sk: &SecretKey) -> String { + message_signing::sign(msg, sk) } /// Shadows [message_signing::verify]. diff --git a/teos-common/src/receipts.rs b/teos-common/src/receipts.rs index 699c3c54..7164fc9a 100644 --- a/teos-common/src/receipts.rs +++ b/teos-common/src/receipts.rs @@ -93,7 +93,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 +153,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/src/api/http.rs b/teos/src/api/http.rs index b0faac2d..949c08b5 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( @@ -846,7 +846,7 @@ mod tests_methods { // Try to add it via the http API let appointment = generate_dummy_appointment(Some(&dispute_tx.compute_txid())).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( 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 884d91d0..8a1e1ea9 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) @@ -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) @@ -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 { @@ -1022,7 +1022,7 @@ mod tests_public_api { // Try to add it again using the API. let appointment = generate_dummy_appointment(Some(&dispute_tx.compute_txid())).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 { 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/gatekeeper.rs b/teos/src/gatekeeper.rs index fa94fd83..99e7452d 100644 --- a/teos/src/gatekeeper.rs +++ b/teos/src/gatekeeper.rs @@ -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/watcher.rs b/teos/src/watcher.rs index be71c091..caf77668 100644 --- a/teos/src/watcher.rs +++ b/teos/src/watcher.rs @@ -644,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(); @@ -706,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 { @@ -722,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(); @@ -737,8 +737,7 @@ mod tests { let dispute_tx = get_random_tx(); let (uuid, triggered_appointment) = generate_dummy_appointment_with_user(user_id, Some(&dispute_tx.compute_txid())); - let signature = - cryptography::sign(&triggered_appointment.inner.to_vec(), &user_sk).unwrap(); + 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(); @@ -767,7 +766,7 @@ mod tests { let dispute_tx = tip_txs.last().unwrap(); let (uuid, appointment_in_cache) = 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).unwrap(); + 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(); @@ -785,7 +784,7 @@ mod tests { let (uuid, mut invalid_appointment) = 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(); @@ -808,7 +807,7 @@ 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.compute_txid())); - 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(); @@ -845,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), @@ -860,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), @@ -985,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(); @@ -1016,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(); @@ -1034,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 { .. }) @@ -1072,7 +1071,7 @@ mod tests { // Track some of the these transactions. if i % 2 == 0 { let appointment = generate_dummy_appointment(Some(&tx.compute_txid())).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(); breaches.insert(*l, tx.clone()); } @@ -1100,7 +1099,7 @@ mod tests { // Let the watcher track these breaches. for (_, tx) in breaches.iter() { let appointment = generate_dummy_appointment(Some(&tx.compute_txid())).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(); } @@ -1133,7 +1132,7 @@ mod tests { 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(); } @@ -1171,7 +1170,7 @@ mod tests { let (uuid, appointment) = 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); } @@ -1208,7 +1207,7 @@ mod tests { 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(); } @@ -1259,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. @@ -1303,7 +1302,7 @@ mod tests { let dispute_tx = get_random_tx(); let (uuid, appointment) = generate_dummy_appointment_with_user(user2_id, Some(&dispute_tx.compute_txid())); - 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(); assert!(watcher.dbm.lock().unwrap().appointment_exists(uuid)); @@ -1323,7 +1322,7 @@ mod tests { 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])); @@ -1340,7 +1339,7 @@ mod tests { let dispute_tx = get_random_tx(); let (uuid, appointment) = generate_dummy_appointment_with_user(user2_id, Some(&dispute_tx.compute_txid())); - 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(); // Set the carrier response diff --git a/watchtower-plugin/src/main.rs b/watchtower-plugin/src/main.rs index 17f78d42..74ebaa1d 100755 --- a/watchtower-plugin/src/main.rs +++ b/watchtower-plugin/src/main.rs @@ -188,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( @@ -233,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( @@ -448,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. diff --git a/watchtower-plugin/src/retrier.rs b/watchtower-plugin/src/retrier.rs index 7219a5dc..e89d4e50 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); @@ -1273,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); @@ -1374,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); @@ -1445,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 49a8c4a79b07d464abf3eae602fbe824ef615294 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Mon, 3 Mar 2025 10:52:52 +0100 Subject: [PATCH 26/85] test: txid from_str Signed-off-by: dzdidi --- teos/src/carrier.rs | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/teos/src/carrier.rs b/teos/src/carrier.rs index 8df1ea0e..75d0fe68 100644 --- a/teos/src/carrier.rs +++ b/teos/src/carrier.rs @@ -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}; @@ -389,9 +390,7 @@ mod tests { start_server(bitcoind_mock.server); let carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, start_height); - let vec = Vec::from_hex(TXID_HEX).unwrap(); - let hash: bitcoin::hashes::sha256d::Hash = bitcoin::hashes::Hash::from_slice(&vec).unwrap(); - let txid = Txid::from(hash); + let txid = Txid::from_str(TXID_HEX).unwrap(); assert!(carrier.in_mempool(&txid)); } @@ -404,9 +403,7 @@ mod tests { start_server(bitcoind_mock.server); let carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, start_height); - let vec = Vec::from_hex(TXID_HEX).unwrap(); - let hash: bitcoin::hashes::sha256d::Hash = bitcoin::hashes::Hash::from_slice(&vec).unwrap(); - let txid = Txid::from(hash); + let txid = Txid::from_str(TXID_HEX).unwrap(); assert!(!carrier.in_mempool(&txid)); } @@ -421,9 +418,7 @@ mod tests { start_server(bitcoind_mock.server); let carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, start_height); - let vec = Vec::from_hex(TXID_HEX).unwrap(); - let hash: bitcoin::hashes::sha256d::Hash = bitcoin::hashes::Hash::from_slice(&vec).unwrap(); - let txid = Txid::from(hash); + let txid = Txid::from_str(TXID_HEX).unwrap(); assert!(!carrier.in_mempool(&txid)); } @@ -437,9 +432,7 @@ mod tests { start_server(bitcoind_mock.server); let carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, start_height); - let vec = Vec::from_hex(TXID_HEX).unwrap(); - let hash: bitcoin::hashes::sha256d::Hash = bitcoin::hashes::Hash::from_slice(&vec).unwrap(); - let txid = Txid::from(hash); + let txid = Txid::from_str(TXID_HEX).unwrap(); assert!(!carrier.in_mempool(&txid)); } @@ -452,9 +445,7 @@ mod tests { let start_height = START_HEIGHT as u32; let carrier = Carrier::new(bitcoin_cli, bitcoind_reachable.clone(), start_height); - let vec = Vec::from_hex(TXID_HEX).unwrap(); - let hash: bitcoin::hashes::sha256d::Hash = bitcoin::hashes::Hash::from_slice(&vec).unwrap(); - let txid = Txid::from(hash); + let txid = Txid::from_str(TXID_HEX).unwrap(); let delay = std::time::Duration::new(3, 0); thread::spawn(move || { From dc0191bc79e9675125729ac3bf352bb7b1772d12 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Mon, 3 Mar 2025 10:55:08 +0100 Subject: [PATCH 27/85] nit: rename local variable Signed-off-by: dzdidi --- teos/src/test_utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/teos/src/test_utils.rs b/teos/src/test_utils.rs index 9137914a..a415d5b3 100644 --- a/teos/src/test_utils.rs +++ b/teos/src/test_utils.rs @@ -205,7 +205,7 @@ impl Blockchain { } None => vec![get_random_tx()], }; - let hashes = txdata.iter().map(|obj| obj.compute_txid().to_raw_hash()); + 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, From 1cde45f6e35e6c3fd1e2e6502c5970f763e65553 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Mon, 3 Mar 2025 11:40:27 +0100 Subject: [PATCH 28/85] remove network mapping in favor of instantiator Signed-off-by: dzdidi --- teos/src/main.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/teos/src/main.rs b/teos/src/main.rs index 97c73195..61001764 100644 --- a/teos/src/main.rs +++ b/teos/src/main.rs @@ -258,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, @@ -274,7 +267,7 @@ async fn main() { dbm.clone(), )); - let mut poller = ChainPoller::new(&mut derefed, Network::from_core_arg(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| { From 48e8b093ed41ef75557d024d69e62cf0e67f4b2b Mon Sep 17 00:00:00 2001 From: dzdidi Date: Mon, 3 Mar 2025 11:49:12 +0100 Subject: [PATCH 29/85] fix tx enconding in tracker Signed-off-by: dzdidi --- teos/src/responder.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/teos/src/responder.rs b/teos/src/responder.rs index 241f09b5..5c8af6bd 100644 --- a/teos/src/responder.rs +++ b/teos/src/responder.rs @@ -5,8 +5,9 @@ use std::sync::{Arc, Mutex}; use bitcoin::{consensus, BlockHash}; use bitcoin::{Transaction, Txid}; +use bitcoin::hashes::Hash; + use lightning::chain; -use lightning::util::ser::Writeable; use lightning_block_sync::poll::ValidatedBlock; use teos_common::constants; @@ -94,8 +95,8 @@ impl TransactionTracker { impl From for common_msgs::Tracker { fn from(t: TransactionTracker) -> Self { common_msgs::Tracker { - dispute_txid: t.dispute_tx.compute_txid().encode(), - penalty_txid: t.penalty_tx.compute_txid().encode(), + 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), } } From fd860a2aaa5600e521d703caa4d61c36fbc9d9c4 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Mon, 3 Mar 2025 12:01:01 +0100 Subject: [PATCH 30/85] test: cleaner target bits Signed-off-by: dzdidi --- teos/src/main.rs | 5 ++++- teos/src/responder.rs | 16 +++++++++++++--- teos/src/test_utils.rs | 3 +-- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/teos/src/main.rs b/teos/src/main.rs index 61001764..15c15918 100644 --- a/teos/src/main.rs +++ b/teos/src/main.rs @@ -267,7 +267,10 @@ async fn main() { dbm.clone(), )); - let mut poller = ChainPoller::new(&mut derefed, Network::from_core_arg(&conf.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 5c8af6bd..f816aac6 100644 --- a/teos/src/responder.rs +++ b/teos/src/responder.rs @@ -3,9 +3,9 @@ use std::collections::HashSet; use std::sync::{Arc, Mutex}; +use bitcoin::hashes::Hash; use bitcoin::{consensus, BlockHash}; use bitcoin::{Transaction, Txid}; -use bitcoin::hashes::Hash; use lightning::chain; use lightning_block_sync::poll::ValidatedBlock; @@ -95,8 +95,18 @@ impl TransactionTracker { impl From for common_msgs::Tracker { fn from(t: TransactionTracker) -> Self { common_msgs::Tracker { - 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(), + 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), } } diff --git a/teos/src/test_utils.rs b/teos/src/test_utils.rs index a415d5b3..48fc168f 100644 --- a/teos/src/test_utils.rs +++ b/teos/src/test_utils.rs @@ -189,8 +189,7 @@ impl Blockchain { } pub fn generate(&mut self, txs: Option>) -> Block { - // FIXME: replace this magic number with something meaningul - let bits = bitcoin::CompactTarget::from_consensus(553713663); + 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(); From 8839a2c7f7beb4f8c4cd47a425d7345882ea3538 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Mon, 3 Mar 2025 12:05:13 +0100 Subject: [PATCH 31/85] ci: remove sudo in adding bin to path Signed-off-by: dzdidi --- .github/workflows/cln-plugin.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cln-plugin.yaml b/.github/workflows/cln-plugin.yaml index 1db0e40a..6b710ab6 100644 --- a/.github/workflows/cln-plugin.yaml +++ b/.github/workflows/cln-plugin.yaml @@ -62,7 +62,7 @@ jobs: run: | wget https://bitcoincore.org/bin/bitcoin-core-${{ env.bitcoind_version }}/bitcoin-${{ env.bitcoind_version }}-x86_64-linux-gnu.tar.gz tar -xzf bitcoin-${{ env.bitcoind_version }}-x86_64-linux-gnu.tar.gz - sudo ln -s $(pwd)/bitcoin-${{ env.bitcoind_version }}/bin/bitcoin* /usr/local/bin + ln -s $(pwd)/bitcoin-${{ env.bitcoind_version }}/bin/bitcoin* /usr/local/bin - name: Load CLN cache id: cache-cln uses: actions/cache@v4 From c9ec4b5aa4be2a115539e701bed6d6645a6aa830 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Tue, 25 Mar 2025 09:45:08 +0100 Subject: [PATCH 32/85] remove leftover todo comment Signed-off-by: dzdidi --- teos-common/src/receipts.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/teos-common/src/receipts.rs b/teos-common/src/receipts.rs index 7164fc9a..5dc0e00c 100644 --- a/teos-common/src/receipts.rs +++ b/teos-common/src/receipts.rs @@ -92,7 +92,6 @@ 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)); } From 522414ba73309a6357ab32552d89a93f2449f56c Mon Sep 17 00:00:00 2001 From: dzdidi Date: Tue, 25 Mar 2025 10:16:07 +0100 Subject: [PATCH 33/85] block getters test helpers Signed-off-by: dzdidi --- teos/src/test_utils.rs | 15 +++++++ teos/src/tx_index.rs | 93 +++++++++++++----------------------------- 2 files changed, 44 insertions(+), 64 deletions(-) diff --git a/teos/src/test_utils.rs b/teos/src/test_utils.rs index 48fc168f..3f7db465 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; @@ -371,6 +372,20 @@ 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, diff --git a/teos/src/tx_index.rs b/teos/src/tx_index.rs index 8dbb21b0..617d98e2 100644 --- a/teos/src/tx_index.rs +++ b/teos/src/tx_index.rs @@ -234,7 +234,7 @@ 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; @@ -263,13 +263,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| match block.deref() { - lightning_block_sync::BlockData::FullBlock(b) => b.clone(), - _ => panic!("Expected FullBlock"), - }) - .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); @@ -295,38 +289,24 @@ 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); - match first_block.deref() { - lightning_block_sync::BlockData::FullBlock(b) => { - assert_eq!( - cache.get_height(&b.header.block_hash()).unwrap(), - height - cache_size + 1 - ); - } - _ => panic!("Expected FullBlock"), - } - - match last_block.deref() { - lightning_block_sync::BlockData::FullBlock(b) => { - assert_eq!(cache.get_height(&b.header.block_hash()).unwrap(), height); - } - _ => panic!("Expected FullBlock"), - } - - match mid.deref() { - lightning_block_sync::BlockData::FullBlock(b) => { - assert_eq!( - cache.get_height(&b.header.block_hash()).unwrap(), - height - cache_size / 2 - ); - } - _ => panic!("Expected FullBlock"), - } + assert_eq!( + cache.get_height(&first_block.header.block_hash()).unwrap(), + height - cache_size + 1 + ); + assert_eq!( + 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 + ); } #[tokio::test] @@ -352,25 +332,20 @@ 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(); + 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 = match last_block.deref() { - lightning_block_sync::BlockData::FullBlock(b) => b - .txdata - .iter() - .map(|tx| (Locator::new(tx.compute_txid()), tx.clone())) - .collect(), - _ => panic!("Expected FullBlock"), - }; + let full_block = get_full_block(&last_block); + let locator_tx_map = full_block + .txdata + .iter() + .map(|tx| (Locator::new(tx.compute_txid()), tx.clone())) + .collect(); - let header = match last_block.deref() { - lightning_block_sync::BlockData::FullBlock(b) => b.header, - lightning_block_sync::BlockData::HeaderOnly(h) => *h, - }; + let header = full_block.header; cache.update(header, &locator_tx_map); // Check that the new data is in the cache @@ -380,28 +355,18 @@ mod tests { assert!(cache.contains_key(locator)); } - let block_hash = match last_block.deref() { - lightning_block_sync::BlockData::FullBlock(b) => b.header.block_hash(), - lightning_block_sync::BlockData::HeaderOnly(h) => h.block_hash(), - }; + let block_hash = full_block.header.block_hash(); assert_eq!( cache.tx_in_block[&block_hash], locator_tx_map.keys().cloned().collect::>() ); // Check that the data from the first block has been evicted - let tx = match first_block { - lightning_block_sync::BlockData::FullBlock(b) => b.txdata[0].clone(), - lightning_block_sync::BlockData::HeaderOnly(_) => { - panic!("Expected FullBlock") - } - }; + 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 = match first_block { - lightning_block_sync::BlockData::FullBlock(b) => b.header.block_hash(), - lightning_block_sync::BlockData::HeaderOnly(h) => h.block_hash(), - }; + let block_hash = first_full_block.header.block_hash(); assert!(!cache.tx_in_block.contains_key(&block_hash)); } From c84984b3884f5fb6c142ff1eea653c84c3504a59 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Tue, 25 Mar 2025 10:45:49 +0100 Subject: [PATCH 34/85] fmt Signed-off-by: dzdidi --- teos/src/test_utils.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/teos/src/test_utils.rs b/teos/src/test_utils.rs index 3f7db465..16f9675f 100644 --- a/teos/src/test_utils.rs +++ b/teos/src/test_utils.rs @@ -373,10 +373,7 @@ pub(crate) async fn get_last_n_blocks(chain: &mut Blockchain, n: usize) -> Vec Vec { - last_n_blocks - .iter() - .map(get_full_block) - .collect() + last_n_blocks.iter().map(get_full_block).collect() } pub(crate) fn get_full_block(block: &ValidatedBlock) -> Block { From 1deb0628a038e258b86568c859a1c6b9e3a4ff4d Mon Sep 17 00:00:00 2001 From: dzdidi Date: Wed, 5 Feb 2025 14:52:38 +0100 Subject: [PATCH 35/85] cargo init Signed-off-by: dzdidi --- Cargo.lock | 4 ++++ Cargo.toml | 1 + teos-ldk-client/Cargo.toml | 6 ++++++ teos-ldk-client/src/main.rs | 3 +++ 4 files changed, 14 insertions(+) create mode 100644 teos-ldk-client/Cargo.toml create mode 100644 teos-ldk-client/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index bbf6e718..a62fd331 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3052,6 +3052,10 @@ dependencies = [ "tonic-build", ] +[[package]] +name = "teos-ldk-client" +version = "0.1.0" + [[package]] name = "textwrap" version = "0.11.0" diff --git a/Cargo.toml b/Cargo.toml index a070916c..a8888e38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,5 +4,6 @@ resolver = "2" members = [ "teos", "teos-common", + "teos-ldk-client", "watchtower-plugin" ] diff --git a/teos-ldk-client/Cargo.toml b/teos-ldk-client/Cargo.toml new file mode 100644 index 00000000..9f2c6c57 --- /dev/null +++ b/teos-ldk-client/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "teos-ldk-client" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/teos-ldk-client/src/main.rs b/teos-ldk-client/src/main.rs new file mode 100644 index 00000000..e7a11a96 --- /dev/null +++ b/teos-ldk-client/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} From 3154121ddc707b29930af4877803682d67bf0170 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Wed, 5 Feb 2025 17:31:19 +0100 Subject: [PATCH 36/85] Readme draft. Mix of pseudo code and rust. Signed-off-by: dzdidi --- teos-ldk-client/README.md | 236 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 teos-ldk-client/README.md diff --git a/teos-ldk-client/README.md b/teos-ldk-client/README.md new file mode 100644 index 00000000..dd0ee27c --- /dev/null +++ b/teos-ldk-client/README.md @@ -0,0 +1,236 @@ +# Watchtower LDK client + +This is a watchtower client crate to interact with an [Eye of Satoshi tower](https://github.com/talaia-labs/rust-teos), and eventually with any [BOLT13](https://github.com/sr-gi/bolt13/blob/master/13-watchtowers.md) compliant watchtower. It is designed to be integrated into [LDK-Node](https://github.com/lightningdevkit/ldk-node). + +The crate manages all the client-side logic to send appointment to a number of registered towers every time a new commitment transaction is generated. It also keeps a summary of the messages sent to the towers and their responses. + +The client instance has the following methods: + +- `register_tower `: registers the user id (compressed public key) with a given tower. +- `get_tower_info `: gets all the locally stored data about a given tower. +- `retry_tower `: tries to send pending appointment to a (previously) unreachable tower. +- `abandon_tower `: deletes all data associated with a given tower. +- `ping_tower `: Polls the tower to check if it is online. +- `list_towers`: lists all registered towers. +- `get_appointment `: queries a given tower about an appointment. +- `get_subscription_info `: gets the subscription information by querying the tower. +- `get_appointment_receipt `: pulls a given appointment receipt from the local database. +- `get_registration_receipt `: pulls the latest registration receipt from the local database. +- `on_commitment_revocation `: sends appointments to the registered towers for every new commitment transaction. + +# Configuration + +## User identification and encryptions + +The constructor accepts a key pair that will be used as the user identifier. All requests from the user are signed using the secret key, so the tower can authenticate the user after the registration process (`register_tower`). + +All the appointments generated by the tower, as well as all the registered towers' data, are encrytped using aforementioned key are stored in a [KVStore](https://docs.rs/lightning/latest/lightning/util/persist/trait.KVStore.html) persisted storage like [VSS](https://github.com/lightningdevkit/vss-server) or [lightning_persister](https://docs.rs/lightning-persister/latest/lightning_persister/index.html) + +## Network configuration + +Network configuration options currently available options are: + +- `max_retry_count`: how many times a retry strategy will try to reach a temporary unreachable tower before giving up. +- `retry_delay`: how long (in seconds) the client will wait before auto-retrying a failed tower. + +# Getting started as a developer + +## Creating a client instance +``` +let teos_client = TeosClient::new(key_pair, storage) + .set_max_retry_count(10) + .set_retry_delay(1) + .build(); +``` + + +## Registering with a tower + +Once the crate is instantiated, the first step is to register your node with an active tower. You can do so by calling: + +``` +async teos_client.register_tower(tower_connector: TowerConnector)) -> Result; +``` + +Where `tower_connector` represents the target tower public key. As a convenience, `tower_connector` may be of the form `tower_public_key@host` or `tower_public_key@host:port`. Port defaults to `9814`. + +### Example + +``` +teos_client = register_tower(TowerConnector::new("02bd2b759dd8a4fcef0f7d9692c105da8400d5da7942ee039e869fbfb8738ffde4@127.0.0.1:9814")).await? +``` + +If the tower is online, you should get back a response similar to this: + +``` +{ + "user_id": "032fd79e4052531955cf3782b09b495a75919317573ba2fb4dca199652595ced2a", + "available_slots": 10000, + "subscription_expiry": 4712 +} +``` + +Where `available_slots` is the amount of free slots the user has available in the tower, `user_id` is the user's public key and `subscription_expiry` is the block height when the subscription expires. Generally speaking, a slot fits an appointment, so in this example the user can send **10000** appointments in roughly **one month**. + +Notice that, ideally, the client and the tower have to agree on the **subscription details** (`available_slots` and `subscription_expiry`). Currently, those depend only on the tower, since it is offering the service for free. However, in the current state, hitting `register_tower` again will add another `10000` slots and reset the time to `current_height + roughtly_one_mont_in_blocks`. + +Note that this data will be stored internally inside of client's state and it does not require developer to do anything with it. + +## Sending data to the tower +Once node is registered with at least one tower it can start sending appointments to the tower for every commitment transaction update on any of your channels using `on_commitment_revocation(tx: CommitmentRevocation) -> Result<(), Error>`. Where `CommitmentRevocation` has the following structure: +``` +{ + channel_id: String, + commit_num: u32, + commitment_txid: Txid, + penalty_tx: Transaction, +} +``` +In the current version, everything is sent to every registered tower (**full replication**). For the end user there is nothing to be done here, under normal conditions, the crate takes care of it. + +## Checking the state of the towers + +To find out more information about registered towers, you can use `list_towers` and `get_tower_info`: + +``` +list_towers() -> Vec +``` +``` +{ + "public_key": "02bd2b759dd8a4fcef0f7d9692c105da8400d5da7942ee039e869fbfb8738ffde4", + "net_addr": "http://localhost:9814", + "available_slots": 9996, + "subscription_expiry": 4712, + "status": "reachable", + "pending_appointments": [], + "invalid_appointments": [] +} +``` + +The overview contains the `id` and network address of the tower (`netaddr`), as well as the current `status` and two list of appointments: **pending** and **invalid**. + +The tower has 5 different statuses: + +- `reachable`: the tower is reachable at the given network address. +- `temporarily unreachable`: the tower is temporarily unreachable, meaning that one of the last requests sent to it has failed. +- `unreachable`: the tower has been unreachable for a while. +- `misbehaving`: the tower has sent us incorrect data. +- `subscription error`: the subscription with the tower has expired or run out of slots. + +The main difference between `temporarily unreachable` and `unreachable` is the amount of time that has passed since we last received a response. If a tower is temporarily unreachable, a backoff strategy is triggered and all the appointments that cannot be delivered are stored under `pending_appointments`. If the tower comes back online within the retry strategy, every pending appointment is sent through and the tower is flagged back as `reachable`. However, if the backoff strategy ends up giving up, the tower is flagged as `unreachable`. + +If the client receives data from a tower that is not properly signed, the tower is flagged as `misbehaving` and it is abandoned, meaning that no more appointments are sent to it. This state should never be reached by honest towers. + +A `subscription error` means that the subscription needs to be renewed (hit `registertower` again). + +Regarding `pending_appointments` and `invalid_appointments` they store the data that is pending to be sent to the tower (for unreachable towers) and the appointments that have been rejected by the tower for being invalid, respectively. The latter should never get populated for honest clients. + +`gettowerinfo` provides more detailed information about the tower: + +**Usage** + +``` +get_tower_info tower_id +``` + +**Call** + +``` +get_tower_info 02bd2b759dd8a4fcef0f7d9692c105da8400d5da7942ee039e869fbfb8738ffde4 +``` + +**Return** + +``` +{ + "net_addr": "http://localhost:9814", + "available_slots": 9996, + "subscription_expiry": 4712, + "status": "reachable", + "appointments": { + "b851b8ec05f5809b9a710f7d9d24db6c": "rbxrs8ncqgzyrxkw5h95a64tbeyhmx6wopdtqndktkko3mq8q3tkczjyk19epd713it8warbpnxgk8py6utq87dt16f3qk6ehkjw5c7q", + "10c6f7787fc33d6298fa89fc41f6a0eb": "dhrtt91bbswmmu41nu4quszt7bsxzpfyx84ycfc1yjt73rs8eqpqg3fwqq8q9tff8aqorohueo3bcgqrww1ocef38hdfuhna44ikjife", + "52dc9bd565bdfe227111927e3964d70b": "d9495n3giiof4rq5aqzh4a6fezftnhofwdi1gb7q5mciyq9besdh4xixczitpgo5dxzdnyzzdy4b9i7hd1zcojdgw833975dn8azfc7x", + "e2824d355f711806d38671c19b91110d": "rbxhpeztw74dspxsr3tk7jdekw7cbkt88kfmda4guf5xkmh1tcmeauqf3s15168y8eo438nbpath58qrxsh9usskzmxk8suf1h19meae" + }, + "pending_appointments": { + "062dc0f28ce5b31e6902c87ff1de15ee": { + "encrypted_blob": "e91bf1a1ab097f71976f240fb2d0c036f5b2188f14089dd1960e041b0a4d31a2bcbf9d6bec064a1d81471bdacf1f4d3b7c8d5df280d86a44504a5ee2ebf309adadc4976cc48cef7b94c9a8f17a16f0dcddfd6d0d105621bc519c0f20b46a8335a3a091bf6bfcc813bd4e34e644822bddda81b2a829d8a3b522b4c9b3f4465a6e416ae9ca8c808637cbc51e8d73dfe80cad3a6cc8c5ca018dd8a4cf2edbc02fd5f6cee0aef5ed5411731ef89061272712180c04150652f5bbb1b540ccc72547fe4ca5e92819c3bbb2feeccd7ce8f7b6568dd7f725fefdd64684f63d59e5d719b24a11272c64818b6319c19a261ee9c8a1674eb2e7c7367797893ac8", + "to_self_delay": 42 + }, + "ad229060698d4bc2b910a30933b1b50a": { + "encrypted_blob": "5881dce52efc18b698adc4f93b4ba275eb73271645b471227680ddb889ab60870972c4d44278dc55da4502021d9af67fd4e40803a2c9a6b4d2fe1d1f89b93373407302b67d12bb6c90b6e72b073f1c6bb3c69d57e635bfd5ff2b9648812364821b30bd95b3e8b3b2a888da8225e3d4d5cd2cd1cf2705d022b908b6b2d71c155ea50c38e2b3fb45a615c7bc1d61607a9240999c1bf174d6153b4ca7d086586614c99a45d7195c589fda8101ee8801e28b7ccad7c2b5fbda38cfe7b5e8ec13c23b8fa3cc3e6791ea9675f312cd59278ea0434538d15600b7fc905cf5a8371fc93d1e834e16d5c6399127b71c8f5c9ebfc11c5c8d3f72ce92e278f163", + "to_self_delay": 42 + } + }, + "invalid_appointments": {} +} +``` + +Notice that there are is a new field in this report: `appointments`. + +`appointments` contains a collection of `locator:tower_signature` pairs of all the appointments sent and accepted by the tower. + +The report may also contain a `misbehaving_proof` field if the tower has misbehaved (this is not the case for this example). The proof would look as follows: + +``` + "misbehaving_proof": { + "locator": "3ebd6c5a4d5ec18c815ad9fcda9aac75", + "appointment_receipt": { + "user_signature": "d7efykp63dy69jrtc3r65pssbdhp4335etq3jap1zqk135qmrtyhr8ghbdhw8y8f7nsjgmm9eoyhsfj6yugzq1bu657frmwwrudr9gpt", + "start_block": 391, + "signature": "rd41nsmhtjsawhc9pta1p5na7kmsyk48xttjy4bt3tbkbajboyzfq6mpamkjixs1w7qotocwjg3sxnbzg6uduec4cnahhkmctgddjn8w" + }, + "recovered_id": "02bd2b759dd8a4fcef0f7d9692c105da8400d5da7942ee039e869fbfb8738ffde4" + } +``` + +Finally, notice how `pending_appointments` now contains all the data about the pending appointments (**the full appointment**). The same applies to `invalid_appointments`. + +## Manually retrying a tower (YAGNI ?) +If a tower has been flagged as **unreachable** (after the default backoff has failed) or there has been a **subscription error**, the tower won't be tried again until the user manually requests so. This can be managed with the `retry_tower` command: + +**Usage** + +``` +retry_tower tower_id +``` +**Call** + +``` +retry_tower 02bd2b759dd8a4fcef0f7d9692c105da8400d5da7942ee039e869fbfb8738ffde4 +``` +**Return** + +``` +"Retrying 02bd2b759dd8a4fcef0f7d9692c105da8400d5da7942ee039e869fbfb8738ffde4" +``` + +Notice that this only works if the tower is **unreachable**. A tower cannot be retried if it is already being retried (**temporarily unreachable**). + +## Query data from a tower +Data can be queried from a tower to check, for instance, that the tower is keeping it or that it is correct. This can be done using the `get_appointment` command: + +**Usage** + +``` +get_appointment +``` +**Call** + +``` +get_appointment 02bd2b759dd8a4fcef0f7d9692c105da8400d5da7942ee039e869fbfb8738ffde4 b851b8ec05f5809b9a710f7d9d24db6c +``` + +**Return** + +``` +{ + "appointment": { + "locator": "b851b8ec05f5809b9a710f7d9d24db6c", + "encrypted_blob": "017044dd0686e89bd3cf69777f1fdcb63d13eafa35e1946a0ac1324247ed793f11e27b3ee599bb1676cc98862c1f07d8e5bd29ed51c94c4ea2721a2b6f205f11cbdb1478da413ced585fe5069c6f438e977d325499bdedb985c055eaff00466209007587f20d09d153b537b0b1b6f5b8151384a1ad9f94dfffd5d5f6c2d484bad7d007976fdcaff173b18dbc4e1e24ca2ae29f8ab7e6933468c179f3857c813441e303b2e9e9b7625b19d8460d368f66cf5a7a2f54139ae0a0c9f0ef0c56183734e5dd51289ecb4f046d97e02895373c97e242c71f910c3ed1fc1b32eda4a3c28c73ad7e5fef624094fadb0753c03f8c9a4189a427e721f3ddfc0a", + "to_self_delay": 42 + }, + "status": "being_watched" +} +``` From 9f6972c33a763ad233599caf28d6e8ae8621e853 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Thu, 6 Feb 2025 11:53:29 +0100 Subject: [PATCH 37/85] lib draft for wt client Signed-off-by: dzdidi --- teos-ldk-client/src/lib.rs | 79 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 teos-ldk-client/src/lib.rs diff --git a/teos-ldk-client/src/lib.rs b/teos-ldk-client/src/lib.rs new file mode 100644 index 00000000..2adba4b7 --- /dev/null +++ b/teos-ldk-client/src/lib.rs @@ -0,0 +1,79 @@ +use std::collections::HashMap; + +use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; + +use teos_common::{TowerId, UserId}; + +use crate::retrier::RetrierStatus; +use crate::{MisbehaviorProof, SubscriptionError, TowerInfo, TowerStatus, TowerSummary}; + +struct TeosCleint { + key_pair: (PublicKey, SecretKey), + storage: Storage, + max_retry_count: u32, + retry_relay: u32, + // TODO: + // KV storage + // in memory towers info + towers: HashMap +} + +impl TeosCleint { + pub fn new(secret_key: SecretKey, storage: Storage) -> Self { + TeosCleint { + key_pair: (PublicKey::from_secret_key(&Secp256k1::new(), &secret_key), secret_key), + storage, + max_retry_count: 3, + retry_relay: 3, + towers: HashMap::new(), + } + } + + pub fn set_max_retry_count(&mut self, max_retry_count: u32) { + self.max_retry_count = max_retry_count; + } + + pub fn set_retry_relay(&mut self, retry_relay: u32) { + self.retry_relay = retry_relay; + } + + pub fn build(&self) -> Result<(), ()> { + // validate things here + Ok(()) + } + + /// registers the user id (compressed public key) with a given tower. + // pub fn register_tower + + /// gets all the locally stored data about a given tower. + // pub fn get_tower_info + + /// tries to send pending appointment to a (previously) unreachable tower. + // pub fn retry_tower + + /// deletes all data associated with a given tower. + // pub fn abandon_tower + + /// Polls the tower to check if it is online. + // pub fn ping_tower + + /// lists all registered towers. + // pub fn list_towers + + /// queries a given tower about an appointment. + // pub fn get_appointment + + /// gets the subscription information by querying the tower. + // pub fn get_subscription_info + + /// pulls a given appointment receipt from the local database. + // pub fn get_appointment_receipt + + /// pulls the latest registration receipt from the local database. + // pub fn get_registration_receipt + + /// sends appointments to the registered towers for every new commitment transaction. + // pub fn on_commitment_revocation + +} + From 9ec88ed7ad27deb5e6b3e9c05d001d799c0d6bbe Mon Sep 17 00:00:00 2001 From: dzdidi Date: Thu, 6 Feb 2025 15:56:23 +0100 Subject: [PATCH 38/85] draft based on wt client Signed-off-by: dzdidi --- Cargo.lock | 17 + teos-ldk-client/Cargo.toml | 22 + teos-ldk-client/src/constants.rs | 52 + teos-ldk-client/src/convert.rs | 562 ++++++++++ teos-ldk-client/src/lib.rs | 549 +++++++++- teos-ldk-client/src/main.rs | 3 - teos-ldk-client/src/net/http.rs | 687 ++++++++++++ teos-ldk-client/src/net/mod.rs | 25 + teos-ldk-client/src/retrier.rs | 1634 +++++++++++++++++++++++++++++ teos-ldk-client/src/ser.rs | 70 ++ teos-ldk-client/src/storage.rs | 216 ++++ teos-ldk-client/src/test_utils.rs | 16 + teos-ldk-client/src/wt_client.rs | 900 ++++++++++++++++ 13 files changed, 4698 insertions(+), 55 deletions(-) create mode 100644 teos-ldk-client/src/constants.rs create mode 100644 teos-ldk-client/src/convert.rs delete mode 100644 teos-ldk-client/src/main.rs create mode 100644 teos-ldk-client/src/net/http.rs create mode 100644 teos-ldk-client/src/net/mod.rs create mode 100644 teos-ldk-client/src/retrier.rs create mode 100644 teos-ldk-client/src/ser.rs create mode 100644 teos-ldk-client/src/storage.rs create mode 100644 teos-ldk-client/src/test_utils.rs create mode 100644 teos-ldk-client/src/wt_client.rs diff --git a/Cargo.lock b/Cargo.lock index a62fd331..41998338 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3055,6 +3055,23 @@ dependencies = [ [[package]] name = "teos-ldk-client" version = "0.1.0" +dependencies = [ + "backoff", + "bitcoin", + "hex", + "home", + "lightning", + "log", + "mockito", + "reqwest", + "rusqlite", + "serde", + "serde_json", + "tempdir", + "teos-common", + "tokio 1.43.0", + "tonic", +] [[package]] name = "textwrap" diff --git a/teos-ldk-client/Cargo.toml b/teos-ldk-client/Cargo.toml index 9f2c6c57..5eebc9d9 100644 --- a/teos-ldk-client/Cargo.toml +++ b/teos-ldk-client/Cargo.toml @@ -4,3 +4,25 @@ version = "0.1.0" edition = "2021" [dependencies] +# General +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", "socks" ] } +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.11", features = [ "tls", "transport" ] } +tokio = { version = "1.5", features = [ "rt-multi-thread", "fs" ] } + +# Bitcoin and Lightning +bitcoin = "0.32.0" +lightning = "0.1.0" + +# Local +teos-common = { path = "../teos-common" } + +[dev-dependencies] +mockito = "0.32.4" +tempdir = "0.3.7" diff --git a/teos-ldk-client/src/constants.rs b/teos-ldk-client/src/constants.rs new file mode 100644 index 00000000..90c50d8b --- /dev/null +++ b/teos-ldk-client/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 = 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 = 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 = 900; +pub const DEV_WT_MAX_RETRY_INTERVAL_DESC: &str = + "maximum length (in seconds) for a retry interval. Defaults to 15 min"; + +/// 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"; +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 + +pub const HOOK_COMMITMENT_REVOCATION: &str = "commitment_revocation"; diff --git a/teos-ldk-client/src/convert.rs b/teos-ldk-client/src/convert.rs new file mode 100644 index 00000000..77ca95ab --- /dev/null +++ b/teos-ldk-client/src/convert.rs @@ -0,0 +1,562 @@ +use std::fmt; +use std::{convert::TryFrom, str::FromStr}; + +use hex::FromHex; +use serde::{Deserialize, Serialize}; +use serde_json::json; + +use bitcoin::{Transaction, Txid}; + +use teos_common::appointment::Locator; +use teos_common::TowerId; + +/// Errors related to the `registertower` command. +#[derive(Debug)] +pub enum RegisterError { + InvalidId(String), + InvalidHost(String), + InvalidPort(String), + InvalidFormat(String), +} + +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}"), + } + } +} + +/// Parameters related to the `registertower` command. +#[derive(Debug, Serialize)] +pub struct RegisterParams { + pub tower_id: TowerId, + pub host: Option, + pub port: Option, +} + +impl RegisterParams { + fn new(tower_id: &str, host: Option<&str>, port: Option) -> Result { + let mut params = RegisterParams::from_id(tower_id)?; + + if host.is_some() { + params = params.with_host(host.unwrap())? + } + + if port.is_some() { + params = params.with_port(port.unwrap())? + } + + Ok(params) + } + + fn from_id(tower_id: &str) -> Result { + Ok(Self { + tower_id: TowerId::from_str(tower_id) + .map_err(|_| RegisterError::InvalidId("Invalid tower id".to_owned()))?, + host: None, + port: None, + }) + } + + fn with_host(self, host: &str) -> Result { + if host.is_empty() { + Err(RegisterError::InvalidHost("hostname is empty".to_owned())) + } else if host.contains(' ') { + Err(RegisterError::InvalidHost( + "hostname contains white spaces".to_owned(), + )) + } else { + Ok(Self { + host: Some(String::from(host)), + ..self + }) + } + } + + 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}" + ))) + } else { + Ok(Self { + port: Some(port as u16), + ..self + }) + } + } +} + +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) => { + let s = s.trim(); + let mut v = s.split('@'); + let tower_id = v.next().unwrap(); + + match v.next() { + Some(x) => { + let mut v = x.split(':'); + let host = v.next(); + let port = if let Some(p) = v.next() { + p.parse() + .map(Some) + .map_err(|_| RegisterError::InvalidPort(format!("Port is not a number: {p}")))? + } else { + None + }; + + RegisterParams::new(tower_id, host, port) + } + None => RegisterParams::from_id(tower_id), + } + }, + 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().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, 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}'"), + )), + } + } +} + +/// Errors related to the `getappointment` command. +#[derive(Debug)] +pub enum GetAppointmentError { + InvalidId(String), + InvalidLocator(String), + InvalidFormat(String), +} + +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}"), + } + } +} + +/// Parameters related to the `getappointment` command. +#[derive(Debug)] +pub struct GetAppointmentParams { + pub tower_id: TowerId, + pub locator: Locator, +} + +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) => { + let param_count = a.len(); + if param_count != 2 { + Err(GetAppointmentError::InvalidFormat(format!( + "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() { + 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".to_owned(), + )) + }?; + + let locator = if let Some(s) = a.get(1).unwrap().as_str() { + Locator::from_hex(s).map_err(|_| { + GetAppointmentError::InvalidLocator("Invalid locator".to_owned()) + }) + } else { + Err(GetAppointmentError::InvalidLocator( + "locator must be a hex encoded string".to_owned(), + )) + }?; + + 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!( + "{k} is mandatory" + ))); + } + } + + 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}'" + ))), + } + } +} + +/// Data associated with a commitment revocation. Represents the data sent by CoreLN through the `commitment_revocation` hook. +#[derive(Debug, Serialize, Deserialize)] +pub struct CommitmentRevocation { + pub channel_id: String, + #[serde(rename(deserialize = "commitnum"))] + pub commit_num: u32, + pub commitment_txid: Txid, + #[serde(deserialize_with = "crate::ser::deserialize_tx")] + pub penalty_tx: Transaction, +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + use std::collections::HashMap; + + const VALID_ID: &str = "020dea894c967319407265764aba31bdef75d463f96800f34dd6df61380d82dfc0"; + + mod register_command { + use super::*; + + #[test] + fn test_from_id() { + // The tower id should be a valid id, otherwise the params construction will fail + let params = RegisterParams::from_id(VALID_ID).unwrap(); + assert!(params.host.is_none()); + assert!(params.port.is_none()); + + // Any incorrectly formatted id will make it fail + assert!(matches!( + RegisterParams::from_id(""), + Err(RegisterError::InvalidId(..)) + )); + } + + #[test] + fn test_with_host() { + // 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.to_owned())); + + // Host must not be empty not have spaces + assert!(matches!( + RegisterParams::from_id(VALID_ID).unwrap().with_host(""), + Err(RegisterError::InvalidHost(..)) + )); + assert!(matches!( + RegisterParams::from_id(VALID_ID) + .unwrap() + .with_host("myhost "), + Err(RegisterError::InvalidHost(..)) + )); + } + + #[test] + fn test_with_port() { + let mut params = RegisterParams::from_id(VALID_ID).unwrap(); + + // Any 16-bytes value will do for the port + let port = 6677; + params = params.with_port(port).unwrap(); + assert_eq!(params.port, Some(port as u16)); + + // Going over u16::MAX will make this fail + let port = u16::MAX as u64 + 1; + assert!(matches!( + params.with_port(port), + Err(RegisterError::InvalidPort(..)) + )); + } + + #[test] + fn test_try_from_json_string() { + let ok = [ + 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!("{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())]); + let p = RegisterParams::try_from(v); + assert!(matches!(p, Ok(..))); + } + + for s in wrong_id { + let v = serde_json::Value::Array(vec![serde_json::Value::String(s.to_string())]); + let p = RegisterParams::try_from(v); + assert!(matches!(p, Err(RegisterError::InvalidId(..)))); + } + + for s in wrong_host { + let v = serde_json::Value::Array(vec![serde_json::Value::String(s.to_string())]); + let p = RegisterParams::try_from(v); + assert!(matches!(p, Err(RegisterError::InvalidHost(..)))); + } + + for s in wrong_port { + let v = serde_json::Value::Array(vec![serde_json::Value::String(s.to_string())]); + let p = RegisterParams::try_from(v); + assert!(matches!(p, Err(RegisterError::InvalidPort(..)))); + } + } + + #[test] + fn test_try_from_json_array() { + let id = json!(VALID_ID); + let number_id = json!(0); + + let host = json!("host"); + let number_host = json!(1); + + let port = json!(80); + let string_port = json!("80"); + + for v in [vec![&id, &host, &port], vec![&id, &host], vec![&id]] { + let p = RegisterParams::try_from(json!(v)); + assert!(matches!(p, Ok(..))); + } + + // Wrong id + let p = RegisterParams::try_from(json!(vec![&number_id, &host, &port])); + assert!(matches!(p, Err(RegisterError::InvalidId(..)))); + + // Wrong host + let p = RegisterParams::try_from(json!(vec![&id, &number_host, &port])); + assert!(matches!(p, Err(RegisterError::InvalidHost(..)))); + + // Wrong port + let p = RegisterParams::try_from(json!(vec![&id, &host, &string_port])); + assert!(matches!(p, Err(RegisterError::InvalidPort(..)))); + + // Wrong param count (params should be 1-3) + let p = RegisterParams::try_from(json!(vec![&id, &host, &port, &id])); + 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) + let p = RegisterParams::try_from(json!(true)); + assert!(matches!(p, Err(RegisterError::InvalidFormat(..)))); + } + } + + mod get_appointment_command { + use super::*; + + #[test] + fn test_try_from_array() { + let id = json!(VALID_ID); + let wrong_id = + json!("050dea894c967319407265764aba31bdef75d463f96800f34dd6df61380d82dfc0"); + let number_id = json!(0); + + let locator = json!("c69517f00d9482e6b1c41639f9bdfd5c"); + let wrong_locator = + json!("c69517f00d9482e6b1c41639f9bdfd5cc69517f00d9482e6b1c41639f9bdfd5c"); + let number_locator = json!(1); + + // Valid params + let p = GetAppointmentParams::try_from(json!(vec![&id, &locator])); + assert!(matches!(p, Ok(..))); + + // Wrong params + // Id is a hex string but the format is wrong (wrong prefix) + let p = GetAppointmentParams::try_from(json!(vec![&wrong_id, &locator])); + assert!(matches!(p, Err(GetAppointmentError::InvalidId(..)))); + // Ud is not a hex string + let p = GetAppointmentParams::try_from(json!(vec![&number_id, &wrong_locator])); + assert!(matches!(p, Err(GetAppointmentError::InvalidId(..)))); + + // Locator is a hex string but not properly formatted (wrong length) + let p = GetAppointmentParams::try_from(json!(vec![&id, &wrong_locator])); + assert!(matches!(p, Err(GetAppointmentError::InvalidLocator(..)))); + // Locator is not a hex string + let p = GetAppointmentParams::try_from(json!(vec![&id, &number_locator])); + 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) + let p = RegisterParams::try_from(json!(true)); + assert!(matches!(p, Err(RegisterError::InvalidFormat(..)))); + } + + #[test] + fn test_wrong_param_count() { + // The param count for get_appointment must be 2. + let params_vec = [vec![], vec![1], vec![1, 2, 3]]; + + for params in params_vec { + let p = GetAppointmentParams::try_from(json!(params)); + assert!(matches!(p, Err(..))); + } + } + } +} diff --git a/teos-ldk-client/src/lib.rs b/teos-ldk-client/src/lib.rs index 2adba4b7..64f07525 100644 --- a/teos-ldk-client/src/lib.rs +++ b/teos-ldk-client/src/lib.rs @@ -1,79 +1,524 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; +use std::fmt; -use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; +use serde::Serialize; -use teos_common::{TowerId, UserId}; +use teos_common::appointment::{Appointment, Locator}; +use teos_common::net::NetAddr; +use teos_common::receipts::AppointmentReceipt; +use teos_common::TowerId; -use crate::retrier::RetrierStatus; -use crate::{MisbehaviorProof, SubscriptionError, TowerInfo, TowerStatus, TowerSummary}; +pub mod constants; +pub mod convert; +pub mod storage; +pub mod net; +pub mod retrier; +mod ser; +pub mod wt_client; -struct TeosCleint { - key_pair: (PublicKey, SecretKey), - storage: Storage, - max_retry_count: u32, - retry_relay: u32, - // TODO: - // KV storage - // in memory towers info - towers: HashMap +#[cfg(test)] +mod test_utils; + +/// The status the tower can be found at. +#[derive(Clone, Serialize, PartialEq, Eq, Copy, Debug)] +#[serde(rename_all = "snake_case")] +pub enum TowerStatus { + Reachable, + TemporaryUnreachable, + Unreachable, + SubscriptionError, + Misbehaving, } -impl TeosCleint { - pub fn new(secret_key: SecretKey, storage: Storage) -> Self { - TeosCleint { - key_pair: (PublicKey::from_secret_key(&Secp256k1::new(), &secret_key), secret_key), - storage, - max_retry_count: 3, - retry_relay: 3, - towers: HashMap::new(), - } +/// The status an appointment can be at. +pub enum AppointmentStatus { + Accepted, + Pending, + Invalid, +} + +/// Errors related to updating a subscription +#[derive(Debug, PartialEq, Eq)] +pub enum SubscriptionError { + Expiry, + Slots, +} + +impl SubscriptionError { + /// Whether the error is related to the expiry time or not. + pub fn is_expiry(&self) -> bool { + *self == SubscriptionError::Expiry } +} - pub fn set_max_retry_count(&mut self, max_retry_count: u32) { - self.max_retry_count = max_retry_count; +impl fmt::Display for TowerStatus { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + match self { + TowerStatus::Reachable => "reachable", + TowerStatus::TemporaryUnreachable => "temporary unreachable", + TowerStatus::Unreachable => "unreachable", + TowerStatus::SubscriptionError => "subscription error", + TowerStatus::Misbehaving => "misbehaving", + } + ) } +} - pub fn set_retry_relay(&mut self, retry_relay: u32) { - self.retry_relay = retry_relay; +impl TowerStatus { + /// Whether the tower is reachable or not. + pub fn is_reachable(&self) -> bool { + *self == TowerStatus::Reachable } - pub fn build(&self) -> Result<(), ()> { - // validate things here - Ok(()) + /// Whether the tower is unreachable or not. + pub fn is_temporary_unreachable(&self) -> bool { + *self == TowerStatus::TemporaryUnreachable } - /// registers the user id (compressed public key) with a given tower. - // pub fn register_tower + /// Whether the tower is unreachable or not. + pub fn is_unreachable(&self) -> bool { + *self == TowerStatus::Unreachable + } - /// gets all the locally stored data about a given tower. - // pub fn get_tower_info + /// Whether the tower is misbehaving or not. + pub fn is_misbehaving(&self) -> bool { + *self == TowerStatus::Misbehaving + } - /// tries to send pending appointment to a (previously) unreachable tower. - // pub fn retry_tower + /// Whether there is a subscription issue with the tower. + 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. +#[derive(Clone, Serialize, Debug, PartialEq, Eq)] +pub struct TowerSummary { + #[serde(flatten)] + pub net_addr: NetAddr, + pub available_slots: u32, + subscription_start: u32, + pub subscription_expiry: u32, + pub status: TowerStatus, + #[serde(serialize_with = "teos_common::ser::serialize_locators")] + pub pending_appointments: HashSet, + #[serde(serialize_with = "teos_common::ser::serialize_locators")] + pub invalid_appointments: HashSet, +} + +impl TowerSummary { + /// Creates a new [TowerSummary] instance. + pub fn new( + net_addr: String, + available_slots: u32, + subscription_start: u32, + subscription_expiry: u32, + ) -> Self { + Self { + net_addr: NetAddr::new(net_addr), + available_slots, + subscription_start, + subscription_expiry, + status: TowerStatus::Reachable, + pending_appointments: HashSet::new(), + invalid_appointments: HashSet::new(), + } + } + + /// Creates a new instance with some associated appointment data. + pub fn with_appointments( + net_addr: String, + available_slots: u32, + subscription_start: u32, + subscription_expiry: u32, + pending_appointments: HashSet, + invalid_appointments: HashSet, + ) -> Self { + Self { + net_addr: NetAddr::new(net_addr), + available_slots, + subscription_start, + subscription_expiry, + status: TowerStatus::Reachable, + pending_appointments, + invalid_appointments, + } + } - /// deletes all data associated with a given tower. - // pub fn abandon_tower + /// Creates a new instance using the existing info but updating the status. + pub fn with_status(mut self, status: TowerStatus) -> Self { + self.status = status; + self + } - /// Polls the tower to check if it is online. - // pub fn ping_tower + /// 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 = NetAddr::new(net_addr); + self.available_slots = available_slots; + self.subscription_start = subscription_start; + self.subscription_expiry = subscription_expiry; + } +} - /// lists all registered towers. - // pub fn list_towers +impl From for TowerSummary { + fn from(info: TowerInfo) -> Self { + TowerSummary::with_appointments( + info.net_addr, + info.available_slots, + info.subscription_start, + info.subscription_expiry, + info.pending_appointments + .iter() + .map(|a| a.locator) + .collect(), + info.invalid_appointments + .iter() + .map(|a| a.locator) + .collect(), + ) + .with_status(info.status) + } +} - /// queries a given tower about an appointment. - // pub fn get_appointment +/// Summarized data associated with a given tower. +#[derive(Clone, Serialize, Debug, PartialEq, Eq)] +pub struct TowerInfo { + pub net_addr: String, + pub available_slots: u32, + pub subscription_start: u32, + pub subscription_expiry: u32, + pub status: TowerStatus, + #[serde(serialize_with = "crate::ser::serialize_receipts")] + pub appointments: HashMap, + #[serde(serialize_with = "crate::ser::serialize_appointments")] + pub pending_appointments: Vec, + #[serde(serialize_with = "crate::ser::serialize_appointments")] + pub invalid_appointments: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub misbehaving_proof: Option, +} - /// gets the subscription information by querying the tower. - // pub fn get_subscription_info +impl TowerInfo { + /// Creates a new [TowerInfo] instance. + pub fn new( + net_addr: String, + available_slots: u32, + subscription_start: u32, + subscription_expiry: u32, + appointments: HashMap, + pending_appointments: Vec, + invalid_appointments: Vec, + ) -> Self { + Self { + net_addr, + available_slots, + subscription_start, + subscription_expiry, + status: TowerStatus::Reachable, + appointments, + pending_appointments, + invalid_appointments, + misbehaving_proof: None, + } + } - /// pulls a given appointment receipt from the local database. - // pub fn get_appointment_receipt + /// Creates a new instance using the existing info but updating the status. + pub fn with_status(mut self, status: TowerStatus) -> Self { + self.status = status; + self + } - /// pulls the latest registration receipt from the local database. - // pub fn get_registration_receipt + /// Sets the misbehaving proof of a tower. + pub fn set_misbehaving_proof(&mut self, proof: MisbehaviorProof) { + self.misbehaving_proof = Some(proof); + } +} - /// sends appointments to the registered towers for every new commitment transaction. - // pub fn on_commitment_revocation +/// A misbehaving proof. Contains proof of a tower replying with a public key different from the advertised one. +#[derive(Clone, Serialize, Debug, PartialEq, Eq)] +pub struct MisbehaviorProof { + #[serde(with = "hex::serde")] + pub locator: Locator, + pub appointment_receipt: AppointmentReceipt, + pub recovered_id: TowerId, +} +impl MisbehaviorProof { + /// Creates a new [MisbehavingProof] instance. + pub fn new( + locator: Locator, + appointment_receipt: AppointmentReceipt, + recovered_id: TowerId, + ) -> Self { + Self { + locator, + appointment_receipt, + recovered_id, + } + } } +#[cfg(test)] +mod tests { + use super::*; + + const STATUSES: [TowerStatus; 5] = [ + TowerStatus::Reachable, + TowerStatus::TemporaryUnreachable, + TowerStatus::Unreachable, + TowerStatus::SubscriptionError, + TowerStatus::Misbehaving, + ]; + + const AVAILABLE_SLOTS: u32 = 21; + const SUBSCRIPTION_START: u32 = 100; + const SUBSCRIPTION_EXPIRY: u32 = SUBSCRIPTION_START + 42; + + mod tower_status { + use super::*; + use TowerStatus::*; + + #[test] + fn test_is_reachable() { + for status in STATUSES { + if status == Reachable { + assert!(status.is_reachable()) + } else { + assert!(!status.is_reachable()); + } + } + } + + #[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 == Misbehaving { + assert!(status.is_misbehaving()) + } else { + assert!(!status.is_misbehaving()); + } + } + } + + #[test] + fn test_is_subscription_error() { + for status in STATUSES { + 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 { + use super::*; + + use std::iter::FromIterator; + + 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(); + + let tower_summary = TowerSummary::new( + net_addr.clone(), + AVAILABLE_SLOTS, + SUBSCRIPTION_START, + SUBSCRIPTION_EXPIRY, + ); + assert_eq!( + tower_summary, + TowerSummary { + net_addr: NetAddr::new(net_addr), + available_slots: AVAILABLE_SLOTS, + subscription_start: SUBSCRIPTION_START, + subscription_expiry: SUBSCRIPTION_EXPIRY, + status: TowerStatus::Reachable, + pending_appointments: HashSet::new(), + invalid_appointments: HashSet::new(), + }, + ); + } + + #[test] + fn test_with_appointments() { + let net_addr: String = "addr".to_owned(); + + let pending_appointments = + HashSet::from_iter([generate_random_appointment(None).locator]); + let invalid_appointments = + HashSet::from_iter([generate_random_appointment(None).locator]); + + let tower_summary = TowerSummary::with_appointments( + net_addr.clone(), + AVAILABLE_SLOTS, + SUBSCRIPTION_START, + SUBSCRIPTION_EXPIRY, + pending_appointments.clone(), + invalid_appointments.clone(), + ); + assert_eq!( + tower_summary, + TowerSummary { + net_addr: NetAddr::new(net_addr), + available_slots: AVAILABLE_SLOTS, + subscription_start: SUBSCRIPTION_START, + subscription_expiry: SUBSCRIPTION_EXPIRY, + status: TowerStatus::Reachable, + pending_appointments, + invalid_appointments, + }, + ); + } + + #[test] + fn test_with_status() { + let mut tower_summary = TowerSummary::new( + "addr".to_owned(), + AVAILABLE_SLOTS, + SUBSCRIPTION_START, + SUBSCRIPTION_EXPIRY, + ); + + let unreachable_tower = tower_summary.clone().with_status(TowerStatus::Unreachable); + tower_summary.status = TowerStatus::Unreachable; + assert_eq!(unreachable_tower, tower_summary); + } + } + + mod tower_info { + use super::*; + + use teos_common::test_utils::{generate_random_appointment, get_random_user_id}; + + impl TowerInfo { + pub fn empty( + net_addr: String, + available_slots: u32, + subscription_start: u32, + subscription_expiry: u32, + ) -> Self { + TowerInfo::new( + net_addr, + available_slots, + subscription_start, + subscription_expiry, + HashMap::new(), + Vec::new(), + Vec::new(), + ) + } + } + + #[test] + fn test_new() { + let tower_info = TowerInfo::new( + "addr".to_owned(), + AVAILABLE_SLOTS, + SUBSCRIPTION_START, + SUBSCRIPTION_EXPIRY, + HashMap::new(), + Vec::new(), + Vec::new(), + ); + + assert!(tower_info.status.is_reachable()); + assert!(tower_info.misbehaving_proof.is_none()); + } + + #[test] + fn test_with_status() { + let mut tower_info = TowerInfo::empty( + "addr".to_owned(), + AVAILABLE_SLOTS, + SUBSCRIPTION_START, + SUBSCRIPTION_EXPIRY, + ); + + let unreachable_tower = tower_info.clone().with_status(TowerStatus::Unreachable); + tower_info.status = TowerStatus::Unreachable; + assert_eq!(unreachable_tower, tower_info); + } + + #[test] + fn test_set_misbehaving_proof() { + let mut tower_info = TowerInfo::empty( + "addr".to_owned(), + AVAILABLE_SLOTS, + SUBSCRIPTION_START, + SUBSCRIPTION_EXPIRY, + ); + assert_eq!(tower_info.misbehaving_proof, None); + + let appointment_receipt = AppointmentReceipt::with_signature( + "user_signature".to_owned(), + SUBSCRIPTION_START + 1, + "tower_signature".to_owned(), + ); + let proof = MisbehaviorProof::new( + generate_random_appointment(None).locator, + appointment_receipt, + get_random_user_id(), + ); + + tower_info.set_misbehaving_proof(proof.clone()); + assert_eq!(tower_info.misbehaving_proof, Some(proof)); + } + } +} diff --git a/teos-ldk-client/src/main.rs b/teos-ldk-client/src/main.rs deleted file mode 100644 index e7a11a96..00000000 --- a/teos-ldk-client/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Hello, world!"); -} diff --git a/teos-ldk-client/src/net/http.rs b/teos-ldk-client/src/net/http.rs new file mode 100644 index 00000000..1a93633d --- /dev/null +++ b/teos-ldk-client/src/net/http.rs @@ -0,0 +1,687 @@ +use reqwest::{Method, Response}; +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}; +use teos_common::{TowerId, UserId}; + +use crate::net::ProxyInfo; +use crate::MisbehaviorProof; + +/// Represents a generic api response. +#[derive(Serialize, Deserialize, Debug)] +#[serde(untagged)] +pub enum ApiResponse { + Response(T), + Error(ApiError), +} + +/// API errors that can be received when interacting with the tower. Error codes match `teos_common::errors`. +#[derive(Serialize, Deserialize, Debug)] +pub struct ApiError { + pub error: String, + pub error_code: u8, +} + +/// Errors related to requests sent to the tower. +#[derive(Debug, PartialEq, Eq)] +pub enum RequestError { + ConnectionError(String), + DeserializeError(String), + Unexpected(String), +} + +impl RequestError { + pub fn is_connection(&self) -> bool { + matches!(self, RequestError::ConnectionError(_)) + } +} + +/// Errors related to the `add_appointment` requests to the tower. +#[derive(Debug)] +pub enum AddAppointmentError { + RequestError(RequestError), + ApiError(ApiError), + SignatureError(MisbehaviorProof), +} + +impl From for AddAppointmentError { + fn from(r: RequestError) -> Self { + AddAppointmentError::RequestError(r) + } +} + +/// 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: &NetAddr, + proxy: &Option, +) -> Result { + log::info!("Registering in the Eye of Satoshi (tower_id={tower_id})"); + process_post_response( + post_request( + tower_net_addr, + Endpoint::Register, + &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, + tower_net_addr: &NetAddr, + proxy: &Option, + appointment: &Appointment, + signature: &str, +) -> Result<(u32, AppointmentReceipt), AddAppointmentError> { + log::debug!( + "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!("Remaining slots: {}", response.available_slots); + log::debug!("Start block: {}", response.start_block); + + Ok((response.available_slots, receipt)) +} + +/// Handles the logic of interacting with the `add_appointment` endpoint of the tower. +pub async fn send_appointment( + tower_id: TowerId, + tower_net_addr: &NetAddr, + proxy: &Option, + appointment: &Appointment, + signature: &str, +) -> Result<(common_msgs::AddAppointmentResponse, AppointmentReceipt), AddAppointmentError> { + let request_data = common_msgs::AddAppointmentRequest { + appointment: Some(appointment.clone().into()), + signature: signature.to_owned(), + }; + + match process_post_response( + post_request( + tower_net_addr, + Endpoint::AddAppointment, + &request_data, + proxy, + ) + .await, + ) + .await? + { + ApiResponse::Response::(r) => { + let receipt = AppointmentReceipt::with_signature( + signature.to_owned(), + r.start_block, + r.signature.clone(), + ); + let recovered_id = TowerId( + cryptography::recover_pk(&receipt.to_vec(), &receipt.signature().unwrap()).unwrap(), + ); + if recovered_id == tower_id { + Ok((r, receipt)) + } else { + Err(AddAppointmentError::SignatureError(MisbehaviorProof::new( + appointment.locator, + receipt, + recovered_id, + ))) + } + } + ApiResponse::Error(e) => Err(AddAppointmentError::ApiError(e)), + } +} + +/// A generic function to send a request to a tower. +async fn request( + tower_net_addr: &NetAddr, + endpoint: Endpoint, + proxy: &Option, + method: Method, + data: Option, +) -> Result { + let client = if let Some(proxy) = proxy { + if proxy.always_use || tower_net_addr.is_onion() { + reqwest::Client::builder() + .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(), + )); + } + reqwest::Client::new() + }; + + 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. +pub async fn process_post_response( + post_request: Result, +) -> Result { + // 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}")) + }), + Err(e) => Err(e), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + use crate::test_utils::get_dummy_add_appointment_response; + use teos_common::test_utils::{ + generate_random_appointment, get_random_appointment_receipt, + get_random_registration_receipt, get_random_user_id, + }; + + mod request_error { + use super::*; + + #[test] + fn test_is_connection() { + let error_message = "error_msg"; + for error in [ + RequestError::ConnectionError(error_message.to_owned()), + RequestError::DeserializeError(error_message.to_owned()), + RequestError::Unexpected(error_message.to_owned()), + ] { + if error == RequestError::ConnectionError(error_message.to_owned()) { + assert!(error.is_connection()) + } else { + assert!(!error.is_connection()) + } + } + } + } + + #[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 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.url()), + &None, + ) + .await + .unwrap(); + + api_mock.assert_async().await; + assert_eq!(receipt, registration_receipt); + } + + #[tokio::test] + async fn test_register_connection_error() { + let error = register( + get_random_user_id(), + get_random_user_id(), + &NetAddr::new("http://server_addr".to_owned()), + &None, + ) + .await + .unwrap_err(); + + assert!(matches!(error, RequestError::ConnectionError { .. })) + } + + #[tokio::test] + async fn test_register_deserialize_error() { + 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.url()), + &None, + ) + .await + .unwrap_err(); + + api_mock.assert_async().await; + 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 + // in case there are no errors. All the error cases will be tested in `send_appointment`. + let (tower_sk, tower_pk) = cryptography::get_random_keypair(); + let appointment = generate_random_appointment(None); + + let appointment_receipt = get_random_appointment_receipt(tower_sk); + let add_appointment_response = + get_dummy_add_appointment_response(appointment.locator, &appointment_receipt); + + 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.url()), + &None, + &appointment, + appointment_receipt.user_signature(), + ) + .await + .unwrap(); + + api_mock.assert_async().await; + assert_eq!(response, add_appointment_response.available_slots); + assert_eq!(receipt, appointment_receipt); + } + + #[tokio::test] + async fn test_send_appointment() { + let (tower_sk, tower_pk) = cryptography::get_random_keypair(); + let appointment = generate_random_appointment(None); + + let appointment_receipt = get_random_appointment_receipt(tower_sk); + let add_appointment_response = + get_dummy_add_appointment_response(appointment.locator, &appointment_receipt); + + 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.url()), + &None, + &appointment, + appointment_receipt.user_signature(), + ) + .await + .unwrap(); + + api_mock.assert_async().await; + assert_eq!(response, add_appointment_response); + assert_eq!(receipt, appointment_receipt); + } + + #[tokio::test] + async fn test_send_appointment_misbehaving() { + let (sybil_tower_sk, sibyl_tower_pk) = cryptography::get_random_keypair(); + let appointment = generate_random_appointment(None); + + let appointment_receipt = get_random_appointment_receipt(sybil_tower_sk); + let add_appointment_response = + get_dummy_add_appointment_response(appointment.locator, &appointment_receipt); + + 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.url()), + &None, + &appointment, + appointment_receipt.user_signature(), + ) + .await + .unwrap_err(); + + api_mock.assert_async().await; + if let AddAppointmentError::SignatureError(proof) = error { + assert_eq!( + MisbehaviorProof::new( + appointment.locator, + appointment_receipt, + TowerId(sibyl_tower_pk) + ), + proof + ) + } else { + panic!("SignatureError was expected") + } + } + + #[tokio::test] + async fn test_send_appointment_connection_error() { + let error = send_appointment( + get_random_user_id(), + &NetAddr::new("http://server_addr".to_owned()), + &None, + &generate_random_appointment(None), + "user_sig", + ) + .await + .unwrap_err(); + + if let AddAppointmentError::RequestError(e) = error { + assert!(matches!(e, RequestError::ConnectionError { .. })) + } else { + panic!("ConnectionError was expected") + } + } + + #[tokio::test] + async fn test_send_appointment_deserialize_error() { + 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.url()), + &None, + &generate_random_appointment(None), + "user_sig", + ) + .await + .unwrap_err(); + + api_mock.assert_async().await; + if let AddAppointmentError::RequestError(e) = error { + assert!(matches!(e, RequestError::DeserializeError { .. })) + } else { + panic!("DeserializeError was expected") + } + } + + #[tokio::test] + async fn test_send_appointment_api_error() { + let api_error = ApiError { + error: "error_msg".to_owned(), + error_code: 1, + }; + + 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.url()), + &None, + &generate_random_appointment(None), + "user_sig", + ) + .await + .unwrap_err(); + + api_mock.assert_async().await; + 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; + 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.url()), + Endpoint::Register, + json!(""), + &None, + ) + .await; + + api_mock.assert_async().await; + assert!(matches!(response, Ok(Response { .. }))); + } + + #[tokio::test] + async fn test_post_request_connection_error() { + assert!(post_request( + &NetAddr::new("http://unreachable_url".to_owned()), + Endpoint::Register, + json!(""), + &None, + ) + .await + .unwrap_err() + .is_connection()); + } + + #[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`. + // So just testing that specific case should be enough. + + 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.url()), + Endpoint::GetAppointment, + json!(""), + &None, + ) + .await, + ) + .await + .unwrap_err(); + + api_mock.assert_async().await; + assert!(matches!(error, RequestError::DeserializeError { .. })); + } +} diff --git a/teos-ldk-client/src/net/mod.rs b/teos-ldk-client/src/net/mod.rs new file mode 100644 index 00000000..cdd28059 --- /dev/null +++ b/teos-ldk-client/src/net/mod.rs @@ -0,0 +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/teos-ldk-client/src/retrier.rs b/teos-ldk-client/src/retrier.rs new file mode 100644 index 00000000..049981a8 --- /dev/null +++ b/teos-ldk-client/src/retrier.rs @@ -0,0 +1,1634 @@ +use std::collections::{HashMap, HashSet}; +use std::fmt::Display; +use std::sync::{Arc, Mutex}; +use std::time::{Duration, Instant}; +use tokio::sync::mpsc::{error::TryRecvError, 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::{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 + 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, RevocationData)>, + max_elapsed_time_secs: u16, + auto_retry_delay: u32, + max_interval_time_secs: u16, + retriers: HashMap>, +} + +impl RetryManager { + pub fn new( + wt_client: Arc>, + unreachable_towers: UnboundedReceiver<(TowerId, RevocationData)>, + max_elapsed_time_secs: u16, + auto_retry_delay: u32, + 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(), + } + } + + /// Starts the retry manager's main logic loop. + /// This method will keep running until the `unreachable_towers` sender disconnects. + /// + /// 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, data)) => { + // 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}"); + } 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 retrier. 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()); + } + } + 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.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. + tokio::time::sleep(Duration::from_secs(POLLING_TIME)).await; + } + Err(TryRecvError::Disconnected) => break, + } + } + } + + /// 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_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}"); + e.insert(Arc::new(Retrier::new( + self.wt_client.clone(), + tower_id, + locators, + ))); + } else { + let mut pending_appointments = self + .retriers + .get(&tower_id) + .unwrap() + .pending_appointments + .lock() + .unwrap(); + for locator in locators { + log::debug!("Adding pending appointment {locator} to existing tower {tower_id}",); + pending_appointments.insert(locator); + } + } + } + + 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); + } +} + +#[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 + /// 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 either temporary unreachable or subscription error. + 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, + /// 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 { + wt_client: Arc>, + tower_id: TowerId, + pending_appointments: Mutex>, + status: Mutex, +} + +impl Retrier { + pub fn new( + wt_client: Arc>, + tower_id: TowerId, + locators: HashSet, + ) -> Self { + Self { + wt_client, + tower_id, + pending_appointments: Mutex::new(locators), + status: Mutex::new(RetrierStatus::Stopped), + } + } + + fn has_pending_appointments(&self) -> bool { + !self.pending_appointments.lock().unwrap().is_empty() + } + + fn set_status(&self, status: RetrierStatus) { + *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().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.is_stopped() && self.has_pending_appointments() + } + + 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!(*self.status.lock().unwrap(), RetrierStatus::Stopped); + + // 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 + .get_tower_status(&self.tower_id) + .unwrap() + .is_subscription_error() + { + state.set_tower_status(self.tower_id, TowerStatus::TemporaryUnreachable); + } + } + self.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 { self.run().await }, + |err, _| { + log::warn!("Retry error happened with {}. {err}", self.tower_id); + }, + ) + .await; + + 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. + 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); + } + 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 {}. {e}", self.tower_id); + if e.is_permanent() { + 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> { + // 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.contains_key(&self.tower_id) { + return Err(Error::permanent(RetryError::Abandoned)); + } + + 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) + .await + .map_err(|e| { + log::debug!("Cannot renew registration with tower. Error: {e:?}"); + Error::transient(RetryError::Subscription( + "Cannot renew registration with tower".to_owned(), + false, + )) + })?; + if !receipt.verify(&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.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() { + let locators = self.pending_appointments.lock().unwrap().clone(); + for locator in locators.into_iter() { + let appointment = self + .wt_client + .lock() + .unwrap() + .dbm + .load_appointment(locator) + .unwrap(); + + match http::add_appointment( + tower_id, + &net_addr, + &proxy, + &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!( + "{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}"); + 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!( + "{tower_id} rejected the appointment. Error: {}, error_code: {}", + 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) => { + return Err(Error::permanent(RetryError::Misbehaving(proof))); + } + } + } + } + } + } + + Ok(()) + } + + /// 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); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + 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, + get_registration_receipt_from_previous, + }; + + use crate::net::http::ApiError; + use crate::test_utils::get_dummy_add_appointment_response; + + 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 = 3; + const MAX_INTERVAL_TIME: u16 = 1; + const MAX_RUN_TIME: f64 = 1.0; + + 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 { + wt_client, + tower_id, + pending_appointments: Mutex::new(HashSet::new()), + status: Mutex::new(RetrierStatus::Stopped), + } + } + } + + #[tokio::test] + 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 mut server = mockito::Server::new_async().await; + + // Add a tower with pending appointments + let (tower_sk, 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.url(), &receipt) + .unwrap(); + + // Add appointment to pending + let appointment = generate_random_appointment(None); + wt_client + .lock() + .unwrap() + .add_pending_appointment(tower_id, &appointment); + + // Prepare the mock response + 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("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))) + .unwrap(); + + 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 + }); + + // 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()); + + wait_until!(wt_client + .lock() + .unwrap() + .get_retrier_status(&tower_id) + .is_none()); + + { + 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(); + } + + #[tokio::test] + async fn test_manage_retry_unreachable() { + 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, + )); + + // Add a tower with pending appointments + let (tower_sk, 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, "http://unreachable.tower", &receipt) + .unwrap(); + + // Add appointment to pending + let appointment = generate_random_appointment(None); + wt_client + .lock() + .unwrap() + .add_pending_appointment(tower_id, &appointment); + + // 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( + 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() + .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 (this gives up due to accumulation of transient errors, so the retriers will be idle). + 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 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, + ); + 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("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 + .lock() + .unwrap() + .add_update_tower( + tower_id, + &server.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_async().await; + + task.abort(); + } + + #[tokio::test] + async fn test_manage_retry_rejected() { + 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 mut server = mockito::Server::new_async().await; + + // 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.url(), &receipt) + .unwrap(); + + // Add appointment to pending + let appointment = generate_random_appointment(None); + wt_client + .lock() + .unwrap() + .add_pending_appointment(tower_id, &appointment); + + // Prepare the mock response + 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))) + .unwrap(); + + 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 + }); + + // 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()); + + // Wait for the remaining time and re-check + 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() + .towers + .get(&tower_id) + .unwrap() + .pending_appointments + .contains(&appointment.locator)); + assert!(wt_client + .lock() + .unwrap() + .towers + .get(&tower_id) + .unwrap() + .invalid_appointments + .contains(&appointment.locator)); + api_mock.assert_async().await; + + task.abort(); + } + + #[tokio::test] + async fn test_manage_retry_misbehaving() { + 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 mut server = mockito::Server::new_async().await; + + // 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.url(), &receipt) + .unwrap(); + + // Add appointment to pending + let appointment = generate_random_appointment(None); + wt_client + .lock() + .unwrap() + .add_pending_appointment(tower_id, &appointment); + + // Prepare the mock response + let mut add_appointment_receipt = AppointmentReceipt::new( + cryptography::sign(&appointment.to_vec(), &wt_client.lock().unwrap().user_sk).unwrap(), + 42, + ); + // Sign with a random key so it counts as misbehaving + 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("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))) + .unwrap(); + + 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 + }); + + // 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()); + + // 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()); + api_mock.assert_async().await; + + task.abort(); + } + + #[tokio::test] + async fn test_manage_retry_abandoned() { + 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 = mockito::Server::new_async().await; + + // 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.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 + tx.send((tower_id, RevocationData::None)).unwrap(); + + 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 + }); + assert!(!wt_client.lock().unwrap().towers.contains_key(&tower_id)); + + 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 mut server = mockito::Server::new_async().await; + + // 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.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 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, + ); + 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("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 + .lock() + .unwrap() + .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( + wt_client_clone, + rx, + MAX_ELAPSED_TIME, + LONG_AUTO_RETRY_DELAY, + MAX_INTERVAL_TIME, + ) + .manage_retry() + .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()); + + // Wait for the remaining time and re-check + wait_until!(wt_client + .lock() + .unwrap() + .get_retrier_status(&tower_id) + .is_none()); + + { + 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; + + 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_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()); + + 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 appointment2 = generate_random_appointment(None); + wt_client + .lock() + .unwrap() + .add_pending_appointment(tower_id, &appointment2); + tx.send((tower_id, RevocationData::Fresh(appointment2.locator))) + .unwrap(); + + { + 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(); + assert_eq!(tower.status, TowerStatus::Unreachable); + } + + // 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 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 + .lock() + .unwrap() + .towers + .get_mut(&tower_id) + .unwrap() + .set_net_addr(server.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(); + + // 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; + 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; + + task.abort(); + } + + #[tokio::test] + 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(); + let wt_client = Arc::new(Mutex::new( + WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await, + )); + 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.url(), &receipt) + .unwrap(); + + // Add appointment to pending + let appointment = generate_random_appointment(None); + wt_client + .lock() + .unwrap() + .add_pending_appointment(tower_id, &appointment); + + // Prepare the mock response + 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("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_async().await; + } + + #[tokio::test] + 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(); + let wt_client = Arc::new(Mutex::new( + WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await, + )); + 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.url(), &receipt) + .unwrap(); + + // If there are no pending appointments the method will simply return + let r = Retrier::empty(wt_client, tower_id).run().await; + assert_eq!(r, Ok(())); + } + + #[tokio::test] + 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(); + let wt_client = Arc::new(Mutex::new( + WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await, + )); + 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.url(), &receipt) + .unwrap(); + + // Add appointment to pending + let appointment = generate_random_appointment(None); + wt_client + .lock() + .unwrap() + .add_pending_appointment(tower_id, &appointment); + + // Prepare the mock response + let mut add_appointment_receipt = AppointmentReceipt::new( + cryptography::sign(&appointment.to_vec(), &wt_client.lock().unwrap().user_sk).unwrap(), + 42, + ); + 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("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!(matches!( + r, + Err(Error::Permanent(RetryError::Misbehaving { .. },)) + )); + api_mock.assert_async().await; + } + + #[tokio::test] + 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(); + let wt_client = Arc::new(Mutex::new( + 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 + let receipt = get_random_registration_receipt(); + wt_client + .lock() + .unwrap() + .add_update_tower(tower_id, "http://unreachable.tower", &receipt) + .unwrap(); + + // Add some pending appointments and try again (with an unreachable tower). + let appointment = generate_random_appointment(None); + wt_client + .lock() + .unwrap() + .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, HashSet::from([appointment.locator])); + let r = retrier.run().await; + + assert_eq!(r, Err(Error::transient(RetryError::Unreachable))); + } + + #[tokio::test] + 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(); + let wt_client = Arc::new(Mutex::new( + WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await, + )); + 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.url(), &receipt) + .unwrap(); + + 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); + wt_client + .lock() + .unwrap() + .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, HashSet::from([appointment.locator])); + let r = retrier.run().await; + + assert!(matches!( + r, + Err(Error::Transient { + err: RetryError::Subscription { .. }, + .. + }) + )); + api_mock.assert_async().await; + } + + #[tokio::test] + 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(); + let wt_client = Arc::new(Mutex::new( + WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await, + )); + 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.url(), &receipt) + .unwrap(); + + 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); + wt_client + .lock() + .unwrap() + .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, + HashSet::from([appointment.locator]), + ); + let r = retrier.run().await; + + assert!(wt_client + .lock() + .unwrap() + .towers + .get(&tower_id) + .unwrap() + .invalid_appointments + .contains(&appointment.locator)); + assert!(r.is_ok()); + api_mock.assert_async().await; + } + + #[tokio::test] + 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(); + let wt_client = Arc::new(Mutex::new( + 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 + let receipt = get_random_registration_receipt(); + wt_client + .lock() + .unwrap() + .add_update_tower(tower_id, "http://tower.adrress", &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::empty(wt_client, tower_id).run().await; + + assert_eq!(r, Err(Error::permanent(RetryError::Abandoned))); + } +} diff --git a/teos-ldk-client/src/ser.rs b/teos-ldk-client/src/ser.rs new file mode 100644 index 00000000..d0c2e9ba --- /dev/null +++ b/teos-ldk-client/src/ser.rs @@ -0,0 +1,70 @@ +use bitcoin::consensus::encode; +use bitcoin::Transaction; + +use teos_common::appointment::{Appointment, Locator}; + +use hex::FromHex; +use serde::{de, ser::SerializeMap, Deserializer, Serialize, Serializer}; +use std::collections::HashMap; + +pub fn deserialize_tx<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + struct TransactionVisitor; + + impl<'de> de::Visitor<'de> for TransactionVisitor { + type Value = Transaction; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a hex string containing the transaction") + } + + fn visit_str(self, v: &str) -> Result + where + E: de::Error, + { + let tx = encode::deserialize( + &Vec::from_hex(v).map_err(|_| E::custom("transaction is not hex encoded"))?, + ) + .map_err(|_| E::custom("transaction cannot be deserialized"))?; + Ok(tx) + } + } + + deserializer.deserialize_any(TransactionVisitor) +} + +pub fn serialize_receipts(hm: &HashMap, s: S) -> Result +where + S: Serializer, +{ + let mut map = s.serialize_map(Some(hm.len()))?; + for (locator, sig) in hm { + map.serialize_entry(&hex::encode(locator), sig)?; + } + map.end() +} + +#[derive(Serialize)] +struct AppointmentInners { + encrypted_blob: String, + to_self_delay: u32, +} + +pub fn serialize_appointments(v: &Vec, s: S) -> Result +where + S: Serializer, +{ + let mut map = s.serialize_map(Some(v.len()))?; + for a in v { + map.serialize_entry( + &hex::encode(a.locator), + &AppointmentInners { + encrypted_blob: hex::encode(&a.encrypted_blob), + to_self_delay: a.to_self_delay, + }, + )?; + } + map.end() +} diff --git a/teos-ldk-client/src/storage.rs b/teos-ldk-client/src/storage.rs new file mode 100644 index 00000000..889d74b2 --- /dev/null +++ b/teos-ldk-client/src/storage.rs @@ -0,0 +1,216 @@ +use std::collections::{HashMap, HashSet}; + +use lightning::util::persist::KVStore; +use lightning::io::Error; +use bitcoin::secp256k1::SecretKey; +use serde::Serialize; +use teos_common::{TowerId, UserId}; +use teos_common::receipts::{AppointmentReceipt, RegistrationReceipt}; +use teos_common::appointment::{Appointment, Locator}; + +use serde::Deserialize; +use crate::{AppointmentStatus, MisbehaviorProof, TowerInfo, TowerStatus, TowerSummary}; + +#[derive(Serialize, Deserialize)] +pub(crate) struct Storage { + store: T, + sk: String, +} + +impl Storage { + pub(crate) fn new(store: T, sk: String) -> Self { + Storage { + store, + sk + } + } + + /// Stores a tower record into the database alongside the corresponding registration receipt. + /// + /// This function MUST be guarded against inserting duplicate (tower_id, subscription_expiry) pairs. + /// This is currently done in WTClient::add_update_tower. + pub fn store_tower_record(&mut self, tower_id: TowerId, net_addr: &str, receipt: &RegistrationReceipt) -> Result<(), Error> { + // primary namespace: "watchtower" + // secondary namespance: "tower_record" + // key: + // value: ? + todo!(); + } + + /// Loads a tower record from the database. + /// + /// 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) -> Option { + // primary namespace: "watchtower" + // secondary namespance: "tower_record" + // key: + todo!(); + } + + /// 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> { + // primary namespace: "watchtower" + // secondary namespance: "tower_record" + // key: + // value: ? + todo!(); + } + + /// Loads all tower records from the database. + pub fn load_towers(&self) -> HashMap { + // primary namespace: "watchtower" + // secondary namespance: "tower_record" + todo!(); + } + + /// Loads the latest registration receipt for a given tower. + /// + /// Latests is determined by the one with the `subscription_expiry` further into the future. + pub fn load_registration_receipt(&self, tower_id: TowerId, user_id: UserId) -> Option { + // primary namespace: "watchtower" + // secondary namespance: "registration_receipt" + // key: + todo!(); + } + + + /// Stores an appointments receipt into the database representing an appointment accepted by a given tower. + pub fn store_appointment_receipt(&mut self, tower_id: TowerId, locator: Locator, available_slots: u32, receipt: &AppointmentReceipt) -> Result<(), Error> { + // primary namespace: "watchtower" + // secondary namespance: "appointment_receipt" + // key: + + // value: ? + todo!(); + } + + /// Loads a given appointment receipt of a given tower from the database. + pub fn load_appointment_receipt(&self, tower_id: TowerId, locator: Locator) -> Option { + // primary namespace: "watchtower" + // secondary namespance: "appointment_receipt" + // key: + + todo!(); + } + + /// 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. + pub fn load_appointment_receipts(&self, tower_id: TowerId) -> HashMap { + // primary namespace: "watchtower" + // secondary namespance: "appointment_receipt" + // filter by tower_id + todo!(); + } + + /// Loads a collection of locators from the database entry associated to a given tower. + /// + /// The loaded locators can be loaded either from appointment_receipts, pending_appointments or invalid_appointments + /// depending on `status`. + pub fn load_appointment_locators(&self, tower_id: TowerId, status: AppointmentStatus) -> HashSet { + // primary namespace: "watchtower" + // secondary namespance: "{status}_appointment_receipt" + todo!(); + } + + /// Loads an appointment from the database. + pub fn load_appointment(&self, locator: Locator) -> Option { + // primary namespace: "watchtower" + // secondary namespance: "appointment_receipt" + // key: + todo!(); + } + + /// Stores an appointment into the database. + /// + /// Appointments are only stored as a whole when they are pending or invalid. + /// Accepted appointments are simplified in the form of an appointment receipt. + fn store_appointment(&self, appointment: &Appointment) -> Result { + // primary namespace: "watchtower" + // secondary namespance: "appointment_receipt" + // key: ? + // value: ? + todo!(); + } + + /// Stores a pending appointment into the database. + /// + /// A pending appointment is an appointment that was sent to a tower when it was unreachable. + /// This data is stored so it can be resent once the tower comes back online. + /// Internally calls [Self::store_appointment]. + pub fn store_pending_appointment(&self, tower_id: TowerId, appointment: &Appointment) -> Result<(), Error> { + // primary namespace: "watchtower" + // secondary namespance: "pending_appointment_receipt" + // key: + // value: ? + todo!(); + } + + /// Removes a pending appointment from the database. + /// + /// If the pending appointment is the only instance of the appointment, the appointment will also be deleted form the appointments table. + pub fn delete_pending_appointment(&self, tower_id: TowerId, locator: Locator) -> Result<(), Error> { + // primary namespace: "watchtower" + // secondary namespance: "pending_appointment_receipt" + // key: ? + // value: ? + todo!(); + } + + /// Stores an invalid appointment into the database. + /// + /// An invalid appointment is an appointment that was rejected by the tower. + /// Storing this data may allow us to see what was the issue and send the data later on. + /// Internally calls [Self::store_appointment]. + pub fn store_invalid_appointment(&mut self, tower_id: TowerId, appointment: &Appointment) -> Result<(), Error> { + // primary namespace: "watchtower" + // secondary namespance: "invalid_appointment_receipt" + // key: ? + // value: ? + todo!(); + } + + /// Loads non finalized appointments from the database for a given tower based on a status flag. + /// + /// This is meant to be used only for pending and invalid appointments, if the method is called for + /// accepted appointment, an empty collection will be returned. + pub fn load_appointments(&self, tower_id: TowerId, status: AppointmentStatus) -> Vec { + // primary namespace: "watchtower" + // secondary namespance: "{status}_appointment_receipt" + // key: ? + // value: ? + todo!(); + } + + /// Stores a misbehaving proof into the database. + /// + /// A misbehaving proof is proof that the tower has signed an appointment using a key different + /// than the one advertised to the user when they registered. + pub fn store_misbehaving_proof(&self, tower_id: TowerId, proof: &MisbehaviorProof) -> Result<(), Error> { + // primary namespace: "watchtower" + // secondary namespance: "misbehaving_proof" + // key: ? + // value: ? + todo!(); + } + + /// Loads the misbehaving proof for a given tower from the database (if found). + fn load_misbehaving_proof(&self, tower_id: TowerId) -> Option { + // primary namespace: "watchtower" + // secondary namespance: "misbehaving_proof" + // key: ? + todo!(); + } + + /// Checks whether a misbehaving proof exists for a given tower. + fn exists_misbehaving_proof(&self, tower_id: TowerId) -> bool { + // primary namespace: "watchtower" + // secondary namespance: "misbehaving_proof" + // key: ? + todo!(); + } +} diff --git a/teos-ldk-client/src/test_utils.rs b/teos-ldk-client/src/test_utils.rs new file mode 100644 index 00000000..be89b61a --- /dev/null +++ b/teos-ldk-client/src/test_utils.rs @@ -0,0 +1,16 @@ +use teos_common::appointment::Locator; +use teos_common::protos as common_msgs; +use teos_common::receipts::AppointmentReceipt; + +pub fn get_dummy_add_appointment_response( + locator: Locator, + receipt: &AppointmentReceipt, +) -> common_msgs::AddAppointmentResponse { + common_msgs::AddAppointmentResponse { + locator: locator.to_vec(), + start_block: receipt.start_block(), + signature: receipt.signature().unwrap(), + available_slots: 21, + subscription_expiry: 1000, + } +} diff --git a/teos-ldk-client/src/wt_client.rs b/teos-ldk-client/src/wt_client.rs new file mode 100644 index 00000000..8bf82508 --- /dev/null +++ b/teos-ldk-client/src/wt_client.rs @@ -0,0 +1,900 @@ +use std::collections::{HashMap, HashSet}; +use std::iter::FromIterator; +use std::path::PathBuf; +use tokio::fs; +use tokio::sync::mpsc::UnboundedSender; + +use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; + +use teos_common::appointment::{Appointment, Locator}; +use teos_common::cryptography; +use teos_common::dbm::Error as DBError; +use teos_common::receipts::{AppointmentReceipt, RegistrationReceipt}; +use teos_common::{TowerId, UserId}; + +use crate::net::ProxyInfo; +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, RevocationData)>, + // Map of existing retriers and its state. + pub retriers: HashMap, + /// The user secret key. + pub user_sk: SecretKey, + /// The user identifier. + pub user_id: UserId, + /// Optional proxy + 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| { + log::error!("Cannot create data dir: {e:?}"); + std::process::exit(1); + }); + + let dbm = DBM::new(&data_dir.join("watchtowers_db.sql3")).unwrap(); + + let (user_sk, user_id) = if let Some(sk) = dbm.load_client_key() { + ( + sk, + UserId(PublicKey::from_secret_key(&Secp256k1::new(), &sk)), + ) + } 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(); + for (tower_id, tower) in towers.iter() { + if tower.status.is_temporary_unreachable() { + unreachable_towers + .send(( + *tower_id, + RevocationData::Stale(tower.pending_appointments.iter().cloned().collect()), + )) + .unwrap(); + } + } + + log::info!("Plugin watchtower client initialized. User id = {user_id}"); + + WTClient { + towers, + unreachable_towers, + retriers: HashMap::new(), + dbm, + user_sk, + user_id, + proxy, + } + } + + /// Adds or updates a tower entry. + pub fn add_update_tower( + &mut self, + tower_id: TowerId, + tower_net_addr: &str, + receipt: &RegistrationReceipt, + ) -> Result<(), SubscriptionError> { + if let Some(tower) = self.towers.get(&tower_id) { + // TODO: For now we're forcing updates to increase both slots and expiry. This is not mandatory and may + // be changed in the future, but the tower is currently set to do this anyway so let's keep it simple. + if receipt.subscription_expiry() <= tower.subscription_expiry { + return Err(SubscriptionError::Expiry); + } else { + let tower_info = self.dbm.load_tower_record(tower_id).unwrap(); + if receipt.available_slots() <= tower_info.available_slots { + return Err(SubscriptionError::Slots); + } + } + } + + self.dbm + .store_tower_record(tower_id, tower_net_addr, receipt) + .unwrap(); + + 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(()) + } + + /// Gets the latest registration receipt of a given tower. + 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) -> Option { + 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) { + if tower.status != status { + tower.status = status + } else { + log::debug!("{tower_id} status is already {status}") + } + } else { + log::error!("Cannot change tower status to {status}. Unknown tower_id: {tower_id}"); + } + } + + /// 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, + tower_id: TowerId, + locator: Locator, + available_slots: u32, + receipt: &AppointmentReceipt, + ) { + if let Some(tower) = self.towers.get_mut(&tower_id) { + // DISCUSS: It may be nice to independently compute the slots and compare + tower.available_slots = available_slots; + + self.dbm + .store_appointment_receipt(tower_id, locator, available_slots, receipt) + .unwrap(); + } else { + log::error!("Cannot add appointment receipt to tower. Unknown tower_id: {tower_id}"); + } + } + + /// Gets an appointment receipt from the database (if found). + pub fn get_appointment_receipt( + &self, + tower_id: TowerId, + locator: Locator, + ) -> Option { + self.dbm.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) { + tower.pending_appointments.insert(appointment.locator); + + self.dbm + .store_pending_appointment(tower_id, appointment) + .unwrap(); + } else { + log::error!("Cannot add pending appointment to tower. Unknown tower_id: {tower_id}"); + } + } + + /// Removes a pending appointment from the tower record. + pub fn remove_pending_appointment(&mut self, tower_id: TowerId, locator: Locator) { + if let Some(tower) = self.towers.get_mut(&tower_id) { + tower.pending_appointments.remove(&locator); + + self.dbm + .delete_pending_appointment(tower_id, locator) + .unwrap(); + } else { + log::error!("Cannot remove pending appointment to tower. Unknown tower_id: {tower_id}"); + } + } + + /// Adds an invalid appointment to the tower record. + pub fn add_invalid_appointment(&mut self, tower_id: TowerId, appointment: &Appointment) { + if let Some(tower) = self.towers.get_mut(&tower_id) { + tower.invalid_appointments.insert(appointment.locator); + + self.dbm + .store_invalid_appointment(tower_id, appointment) + .unwrap(); + } else { + log::error!("Cannot add invalid appointment to tower. Unknown tower_id: {tower_id}"); + } + } + + /// 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.store_misbehaving_proof(tower_id, &proof).unwrap(); + tower.status = TowerStatus::Misbehaving; + } else { + 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.remove_tower_record(tower_id) + } else { + Err(DBError::NotFound) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use tempdir::TempDir; + use tokio::sync::mpsc::unbounded_channel; + + use teos_common::test_utils::{ + generate_random_appointment, get_random_appointment_receipt, + get_random_registration_receipt, get_random_user_id, + get_registration_receipt_from_previous, + }; + + #[tokio::test] + async fn test_add_update_load_tower() { + 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; + + // 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_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(), + receipt.subscription_start(), + receipt.subscription_expiry(), + ); + + wt_client + .add_update_tower(tower_id, &tower_info.net_addr, &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); + + // Calling the method again with updated information should also updated the records in memory and the database + receipt = get_registration_receipt_from_previous(&receipt); + + let updated_tower_info = TowerInfo::empty( + "talaia.watch".to_owned(), + receipt.available_slots(), + receipt.subscription_start(), + receipt.subscription_expiry(), + ); + wt_client + .add_update_tower(tower_id, &updated_tower_info.net_addr, &receipt) + .unwrap(); + + assert_eq!( + wt_client.towers.get(&tower_id), + Some(&TowerSummary::from(updated_tower_info.clone())) + ); + assert_eq!( + wt_client.load_tower_info(tower_id).unwrap(), + updated_tower_info + ); + + // If we try to update without increasing both the end_time and the slots, this will fail + let mut receipt_same_slots = RegistrationReceipt::new( + receipt.user_id(), + receipt.available_slots(), + receipt.subscription_start(), + receipt.subscription_expiry() + 1, + ); + 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), + 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, + &receipt_same_expiry + ), + 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] + 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(); + let mut wt_client = + WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await; + + // If the tower is unknown nothing will happen + let unknown_tower = get_random_user_id(); + wt_client.set_tower_status(unknown_tower, TowerStatus::Reachable); + assert!(!wt_client.towers.contains_key(&unknown_tower)); + + // If the tower is known, the status will be updated. + let receipt = get_random_registration_receipt(); + let tower_id = get_random_user_id(); + wt_client + .add_update_tower(tower_id, "talaia.watch", &receipt) + .unwrap(); + + for status in [ + TowerStatus::Reachable, + TowerStatus::TemporaryUnreachable, + TowerStatus::Unreachable, + TowerStatus::SubscriptionError, + TowerStatus::Misbehaving, + ] { + wt_client.set_tower_status(tower_id, status); + assert_eq!(status, wt_client.get_tower_status(&tower_id).unwrap()); + } + } + + #[tokio::test] + async fn test_add_appointment_receipt() { + 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 (tower_sk, tower_pk) = cryptography::get_random_keypair(); + let tower_id = TowerId(tower_pk); + + 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.towers.contains_key(&tower_id)); + + // Add the tower to the state and try again + let tower_info = TowerInfo::new( + "talaia.watch".to_owned(), + registration_receipt.available_slots(), + registration_receipt.subscription_start(), + registration_receipt.subscription_expiry(), + HashMap::from([(locator, appointment_receipt.signature().unwrap())]), + Vec::new(), + Vec::new(), + ); + wt_client + .add_update_tower(tower_id, &tower_info.net_addr, ®istration_receipt) + .unwrap(); + wt_client.add_appointment_receipt( + tower_id, + locator, + registration_receipt.available_slots(), + &appointment_receipt, + ); + + assert!(wt_client.towers.contains_key(&tower_id)); + assert_eq!( + wt_client.towers.get(&tower_id).unwrap(), + &TowerSummary::from(tower_info.clone()) + ); + assert_eq!(wt_client.load_tower_info(tower_id).unwrap(), tower_info); + } + + #[tokio::test] + async fn test_add_pending_appointment() { + 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 tower_id = get_random_user_id(); + + let registration_receipt = get_random_registration_receipt(); + let appointment = generate_random_appointment(None); + + // If we call this on an unknown tower it will simply do nothing + wt_client.add_pending_appointment(tower_id, &appointment); + assert!(!wt_client.towers.contains_key(&tower_id)); + + // Add the tower to the state and try again + let tower_info = TowerInfo::new( + "talaia.watch".to_owned(), + registration_receipt.available_slots(), + registration_receipt.subscription_start(), + registration_receipt.subscription_expiry(), + HashMap::new(), + vec![appointment.clone()], + Vec::new(), + ); + + wt_client + .add_update_tower(tower_id, &tower_info.net_addr, ®istration_receipt) + .unwrap(); + wt_client.add_pending_appointment(tower_id, &appointment); + + assert!(wt_client.towers.contains_key(&tower_id)); + assert_eq!( + wt_client.towers.get(&tower_id).unwrap(), + &TowerSummary::from(tower_info.clone()) + ); + // When towers data is loaded from the database, it is assumed to be reachable. + assert_eq!( + wt_client.load_tower_info(tower_id).unwrap(), + tower_info.with_status(TowerStatus::TemporaryUnreachable) + ); + } + + #[tokio::test] + async fn test_remove_pending_appointment() { + 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 tower_id = get_random_user_id(); + + let registration_receipt = get_random_registration_receipt(); + let appointment = generate_random_appointment(None); + + // If we call this on an unknown tower it will simply do nothing + wt_client.remove_pending_appointment(tower_id, appointment.locator); + + // Add the tower to the state and try again + wt_client + .add_update_tower(tower_id, "talaia.watch", ®istration_receipt) + .unwrap(); + wt_client.add_pending_appointment(tower_id, &appointment); + + wt_client.remove_pending_appointment(tower_id, appointment.locator); + assert!(!wt_client + .towers + .get(&tower_id) + .unwrap() + .pending_appointments + .contains(&appointment.locator)); + // This bit is tested exhaustively in the DBM. + assert!(!wt_client.dbm.appointment_exists(appointment.locator)); + } + + #[tokio::test] + async fn test_add_invalid_appointment() { + 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 tower_id = get_random_user_id(); + + let registration_receipt = get_random_registration_receipt(); + let appointment = generate_random_appointment(None); + + // If we call this on an unknown tower it will simply do nothing + wt_client.add_invalid_appointment(tower_id, &appointment); + assert!(!wt_client.towers.contains_key(&tower_id)); + + // Add the tower to the state and try again + let tower_info = TowerInfo::new( + "talaia.watch".to_owned(), + registration_receipt.available_slots(), + registration_receipt.subscription_start(), + registration_receipt.subscription_expiry(), + HashMap::new(), + Vec::new(), + vec![appointment.clone()], + ); + + wt_client + .add_update_tower(tower_id, &tower_info.net_addr, ®istration_receipt) + .unwrap(); + wt_client.add_invalid_appointment(tower_id, &appointment); + + assert!(wt_client.towers.contains_key(&tower_id)); + assert_eq!( + wt_client.towers.get(&tower_id).unwrap(), + &TowerSummary::from(tower_info.clone()) + ); + assert_eq!(wt_client.load_tower_info(tower_id).unwrap(), tower_info); + } + + #[tokio::test] + async fn test_move_pending_appointment_to_invalid() { + 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 tower_id = get_random_user_id(); + + let registration_receipt = get_random_registration_receipt(); + let appointment = generate_random_appointment(None); + + wt_client + .add_update_tower(tower_id, "talaia.watch", ®istration_receipt) + .unwrap(); + wt_client.add_pending_appointment(tower_id, &appointment); + + // Check that the appointment can be moved from pending to invalid + wt_client.add_invalid_appointment(tower_id, &appointment); + wt_client.remove_pending_appointment(tower_id, appointment.locator); + + assert!(!wt_client + .towers + .get(&tower_id) + .unwrap() + .pending_appointments + .contains(&appointment.locator)); + assert!(wt_client + .towers + .get(&tower_id) + .unwrap() + .invalid_appointments + .contains(&appointment.locator)); + assert!(!wt_client + .dbm + .load_appointment_locators(tower_id, crate::AppointmentStatus::Pending) + .contains(&appointment.locator)); + assert!(wt_client + .dbm + .load_appointment_locators(tower_id, crate::AppointmentStatus::Invalid) + .contains(&appointment.locator)); + assert!(wt_client.dbm.appointment_exists(appointment.locator)); + } + + #[tokio::test] + async fn test_move_pending_appointment_to_invalid_multiple_towers() { + // Check that moving an appointment from pending to invalid can be done even if multiple towers have a reference to it + 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 tower_id = get_random_user_id(); + let another_tower_id = get_random_user_id(); + 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) + .unwrap(); + wt_client + .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); + + // Check that the appointment can be moved from pending to invalid + wt_client.add_invalid_appointment(tower_id, &appointment); + wt_client.remove_pending_appointment(tower_id, appointment.locator); + + // TOWER_ID CHECKS + assert!(!wt_client + .towers + .get(&tower_id) + .unwrap() + .pending_appointments + .contains(&appointment.locator)); + assert!(wt_client + .towers + .get(&tower_id) + .unwrap() + .invalid_appointments + .contains(&appointment.locator)); + assert!(!wt_client + .dbm + .load_appointment_locators(tower_id, crate::AppointmentStatus::Pending) + .contains(&appointment.locator)); + assert!(wt_client + .dbm + .load_appointment_locators(tower_id, crate::AppointmentStatus::Invalid) + .contains(&appointment.locator)); + + // ANOTHER_TOWER_ID CHECKS + assert!(wt_client + .towers + .get(&another_tower_id) + .unwrap() + .pending_appointments + .contains(&appointment.locator)); + assert!(!wt_client + .towers + .get(&another_tower_id) + .unwrap() + .invalid_appointments + .contains(&appointment.locator)); + assert!(wt_client + .dbm + .load_appointment_locators(another_tower_id, crate::AppointmentStatus::Pending) + .contains(&appointment.locator)); + assert!(!wt_client + .dbm + .load_appointment_locators(another_tower_id, crate::AppointmentStatus::Invalid) + .contains(&appointment.locator)); + + // GENERAL + assert!(wt_client.dbm.appointment_exists(appointment.locator)); + } + + #[tokio::test] + async fn test_flag_misbehaving_tower() { + 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 (tower_sk, tower_pk) = cryptography::get_random_keypair(); + let tower_id = TowerId(tower_pk); + + // If we call this on an unknown tower it will simply do nothing + let appointment = generate_random_appointment(None); + let receipt = get_random_appointment_receipt(tower_sk); + let proof = MisbehaviorProof::new(appointment.locator, receipt, get_random_user_id()); + wt_client.flag_misbehaving_tower(tower_id, proof.clone()); + assert!(!wt_client.towers.contains_key(&tower_id)); + + // // Add the tower to the state and try again + let registration_receipt = get_random_registration_receipt(); + wt_client + .add_update_tower(tower_id, "talaia.watch", ®istration_receipt) + .unwrap(); + wt_client.flag_misbehaving_tower(tower_id, proof.clone()); + + // Check data in memory + let tower_summary = wt_client.towers.get(&tower_id); + assert!(tower_summary.is_some()); + assert!(tower_summary.unwrap().status.is_misbehaving()); + + // Check data in DB + let loaded_info = wt_client.load_tower_info(tower_id).unwrap(); + assert!(loaded_info.status.is_misbehaving()); + 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 = 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(); + let tower_id = TowerId(tower_pk); + let tower_info = TowerInfo::empty( + "talaia.watch".to_owned(), + 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, &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!(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 + 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.appointment_receipt_exists(locator, tower_id)); + + // Remove and check both the tower and the appointment + wt_client.remove_tower(tower_id).unwrap(); + 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)); + } + + #[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 = 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(); + let tower1_id = TowerId(tower1_pk); + let (tower2_sk, tower2_pk) = cryptography::get_random_keypair(); + let tower2_id = TowerId(tower2_pk); + + wt_client + .add_update_tower(tower1_id, "talaia.watch", &receipt) + .unwrap(); + wt_client + .add_update_tower(tower2_id, "talaia.watch", &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.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(); + 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)); + } + + #[tokio::test] + async fn test_remove_inexistent_tower() { + 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) + )); + } +} From dd0a7af9569af7e8ba40dd73331d114d8430fb49 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Thu, 6 Feb 2025 17:42:54 +0100 Subject: [PATCH 39/85] draft based on wt p2 Signed-off-by: dzdidi --- Cargo.lock | 10 + teos-ldk-client/Cargo.toml | 1 + teos-ldk-client/src/constants.rs | 52 ----- teos-ldk-client/src/lib.rs | 1 - teos-ldk-client/src/net/http.rs | 62 +----- teos-ldk-client/src/net/mod.rs | 24 -- teos-ldk-client/src/retrier.rs | 314 +++++++++++++------------- teos-ldk-client/src/storage.rs | 365 ++++++++++++++++++------------- teos-ldk-client/src/wt_client.rs | 82 +++---- watchtower-plugin/src/lib.rs | 4 +- 10 files changed, 433 insertions(+), 482 deletions(-) delete mode 100644 teos-ldk-client/src/constants.rs diff --git a/Cargo.lock b/Cargo.lock index 41998338..cd68eca0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -280,6 +280,15 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bitcoin" version = "0.32.5" @@ -3057,6 +3066,7 @@ name = "teos-ldk-client" version = "0.1.0" dependencies = [ "backoff", + "bincode", "bitcoin", "hex", "home", diff --git a/teos-ldk-client/Cargo.toml b/teos-ldk-client/Cargo.toml index 5eebc9d9..38619371 100644 --- a/teos-ldk-client/Cargo.toml +++ b/teos-ldk-client/Cargo.toml @@ -15,6 +15,7 @@ serde = "1.0.130" serde_json = { version = "1.0", features = [ "preserve_order" ] } tonic = { version = "0.11", features = [ "tls", "transport" ] } tokio = { version = "1.5", features = [ "rt-multi-thread", "fs" ] } +bincode = "1.3.3" # Bitcoin and Lightning bitcoin = "0.32.0" diff --git a/teos-ldk-client/src/constants.rs b/teos-ldk-client/src/constants.rs deleted file mode 100644 index 90c50d8b..00000000 --- a/teos-ldk-client/src/constants.rs +++ /dev/null @@ -1,52 +0,0 @@ -// 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 = 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 = 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 = 900; -pub const DEV_WT_MAX_RETRY_INTERVAL_DESC: &str = - "maximum length (in seconds) for a retry interval. Defaults to 15 min"; - -/// 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"; -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 - -pub const HOOK_COMMITMENT_REVOCATION: &str = "commitment_revocation"; diff --git a/teos-ldk-client/src/lib.rs b/teos-ldk-client/src/lib.rs index 64f07525..2bb56c6b 100644 --- a/teos-ldk-client/src/lib.rs +++ b/teos-ldk-client/src/lib.rs @@ -8,7 +8,6 @@ use teos_common::net::NetAddr; use teos_common::receipts::AppointmentReceipt; use teos_common::TowerId; -pub mod constants; pub mod convert; pub mod storage; pub mod net; diff --git a/teos-ldk-client/src/net/http.rs b/teos-ldk-client/src/net/http.rs index 1a93633d..5e2bcc58 100644 --- a/teos-ldk-client/src/net/http.rs +++ b/teos-ldk-client/src/net/http.rs @@ -9,7 +9,6 @@ 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. @@ -60,7 +59,6 @@ pub async fn register( tower_id: TowerId, user_id: UserId, tower_net_addr: &NetAddr, - proxy: &Option, ) -> Result { log::info!("Registering in the Eye of Satoshi (tower_id={tower_id})"); process_post_response( @@ -70,7 +68,6 @@ pub async fn register( &common_msgs::RegisterRequest { user_id: user_id.to_vec(), }, - proxy, ) .await, ) @@ -90,7 +87,6 @@ pub async fn register( pub async fn add_appointment( tower_id: TowerId, tower_net_addr: &NetAddr, - proxy: &Option, appointment: &Appointment, signature: &str, ) -> Result<(u32, AppointmentReceipt), AddAppointmentError> { @@ -99,7 +95,7 @@ pub async fn add_appointment( appointment.locator ); let (response, receipt) = - send_appointment(tower_id, tower_net_addr, proxy, appointment, signature).await?; + send_appointment(tower_id, tower_net_addr, 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); @@ -111,7 +107,6 @@ pub async fn add_appointment( pub async fn send_appointment( tower_id: TowerId, tower_net_addr: &NetAddr, - proxy: &Option, appointment: &Appointment, signature: &str, ) -> Result<(common_msgs::AddAppointmentResponse, AppointmentReceipt), AddAppointmentError> { @@ -125,7 +120,6 @@ pub async fn send_appointment( tower_net_addr, Endpoint::AddAppointment, &request_data, - proxy, ) .await, ) @@ -158,31 +152,16 @@ pub async fn send_appointment( async fn request( tower_net_addr: &NetAddr, endpoint: Endpoint, - proxy: &Option, method: Method, data: Option, ) -> Result { - let client = if let Some(proxy) = proxy { - if proxy.always_use || tower_net_addr.is_onion() { - reqwest::Client::builder() - .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(), - )); - } - reqwest::Client::new() - }; + // 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(), + )); + } + let client = reqwest::Client::new(); let mut request_builder = client.request( method, @@ -209,17 +188,15 @@ 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 + request(tower_net_addr, endpoint, 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 + request::<()>(tower_net_addr, endpoint, Method::GET, None).await } /// Generic function to process the response of a given post request. @@ -285,7 +262,6 @@ mod tests { TowerId(tower_pk), registration_receipt.user_id(), &NetAddr::new(server.url()), - &None, ) .await .unwrap(); @@ -300,7 +276,6 @@ mod tests { get_random_user_id(), get_random_user_id(), &NetAddr::new("http://server_addr".to_owned()), - &None, ) .await .unwrap_err(); @@ -323,7 +298,6 @@ mod tests { get_random_user_id(), get_random_user_id(), &NetAddr::new(server.url()), - &None, ) .await .unwrap_err(); @@ -355,7 +329,6 @@ mod tests { let (response, receipt) = add_appointment( TowerId(tower_pk), &NetAddr::new(server.url()), - &None, &appointment, appointment_receipt.user_signature(), ) @@ -388,7 +361,6 @@ mod tests { let (response, receipt) = send_appointment( TowerId(tower_pk), &NetAddr::new(server.url()), - &None, &appointment, appointment_receipt.user_signature(), ) @@ -422,7 +394,6 @@ mod tests { let error = send_appointment( tower_id, &NetAddr::new(server.url()), - &None, &appointment, appointment_receipt.user_signature(), ) @@ -449,7 +420,6 @@ mod tests { let error = send_appointment( get_random_user_id(), &NetAddr::new("http://server_addr".to_owned()), - &None, &generate_random_appointment(None), "user_sig", ) @@ -477,7 +447,6 @@ mod tests { let error = send_appointment( get_random_user_id(), &NetAddr::new(server.url()), - &None, &generate_random_appointment(None), "user_sig", ) @@ -511,7 +480,6 @@ mod tests { let error = send_appointment( get_random_user_id(), &NetAddr::new(server.url()), - &None, &generate_random_appointment(None), "user_sig", ) @@ -537,7 +505,6 @@ mod tests { let response_post = request( &NetAddr::new(server.url()), Endpoint::Register, - &None, Method::POST, Some(json!("")), ) @@ -557,7 +524,6 @@ mod tests { let response_get = request::<()>( &NetAddr::new(server.url()), Endpoint::Ping, - &None, Method::GET, None, ) @@ -572,7 +538,6 @@ mod tests { assert!(request( &NetAddr::new("http://unreachable_url".to_owned()), Endpoint::Register, - &None, Method::POST, Some(json!("")), ) @@ -583,7 +548,6 @@ mod tests { assert!(request( &NetAddr::new("http://unreachable_url".to_owned()), Endpoint::Ping, - &None, Method::GET, None::<&str>, ) @@ -601,7 +565,7 @@ mod tests { .with_header("content-type", "application/json") .create_async() .await; - let response = get_request(&NetAddr::new(server.url()), Endpoint::Ping, &None).await; + let response = get_request(&NetAddr::new(server.url()), Endpoint::Ping).await; api_mock.assert_async().await; @@ -613,7 +577,6 @@ mod tests { assert!(get_request( &NetAddr::new("http://unreachable_url".to_owned()), Endpoint::Ping, - &None, ) .await .unwrap_err() @@ -634,7 +597,6 @@ mod tests { &NetAddr::new(server.url()), Endpoint::Register, json!(""), - &None, ) .await; @@ -648,7 +610,6 @@ mod tests { &NetAddr::new("http://unreachable_url".to_owned()), Endpoint::Register, json!(""), - &None, ) .await .unwrap_err() @@ -674,7 +635,6 @@ mod tests { &NetAddr::new(server.url()), Endpoint::GetAppointment, json!(""), - &None, ) .await, ) diff --git a/teos-ldk-client/src/net/mod.rs b/teos-ldk-client/src/net/mod.rs index cdd28059..3883215f 100644 --- a/teos-ldk-client/src/net/mod.rs +++ b/teos-ldk-client/src/net/mod.rs @@ -1,25 +1 @@ -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/teos-ldk-client/src/retrier.rs b/teos-ldk-client/src/retrier.rs index 049981a8..cdb5aecb 100644 --- a/teos-ldk-client/src/retrier.rs +++ b/teos-ldk-client/src/retrier.rs @@ -117,7 +117,7 @@ impl RetryManager { self.wt_client .lock() .unwrap() - .dbm + .storage .load_appointment_locators( retrier.tower_id, crate::AppointmentStatus::Pending, @@ -159,7 +159,7 @@ impl RetryManager { self.wt_client .lock() .unwrap() - .dbm + .storage .load_appointment_locators( retrier.tower_id, crate::AppointmentStatus::Pending, @@ -430,7 +430,7 @@ impl Retrier { 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 (tower_id, status, net_addr, user_id, user_sk) = { let wt_client = self.wt_client.lock().unwrap(); if !wt_client.towers.contains_key(&self.tower_id) { return Err(Error::permanent(RetryError::Abandoned)); @@ -443,13 +443,12 @@ impl Retrier { 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) + let receipt = http::register(tower_id, user_id, &net_addr) .await .map_err(|e| { log::debug!("Cannot renew registration with tower. Error: {e:?}"); @@ -482,14 +481,13 @@ impl Retrier { .wt_client .lock() .unwrap() - .dbm + .storage .load_appointment(locator) .unwrap(); match http::add_appointment( tower_id, &net_addr, - &proxy, &appointment, &cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(), ) @@ -1189,157 +1187,157 @@ 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_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()); - - 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 appointment2 = generate_random_appointment(None); - wt_client - .lock() - .unwrap() - .add_pending_appointment(tower_id, &appointment2); - tx.send((tower_id, RevocationData::Fresh(appointment2.locator))) - .unwrap(); - - { - 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(); - assert_eq!(tower.status, TowerStatus::Unreachable); - } - - // 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 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 - .lock() - .unwrap() - .towers - .get_mut(&tower_id) - .unwrap() - .set_net_addr(server.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(); - - // 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; - 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; - - task.abort(); - } + // #[tokio::test] + // async fn test_manage_retry_while_idle() { + // use crate::storage::Storage; + // // 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 storage = Storage::new().unwrap(); + // let receipt = get_random_registration_receipt(); + // storage.store_tower_record(tower_id, "http://unreachable.tower", &receipt) + // .unwrap(); + // + // let appointment = generate_random_appointment(None); + // storage.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_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()); + // + // 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 appointment2 = generate_random_appointment(None); + // wt_client + // .lock() + // .unwrap() + // .add_pending_appointment(tower_id, &appointment2); + // tx.send((tower_id, RevocationData::Fresh(appointment2.locator))) + // .unwrap(); + // + // { + // 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(); + // assert_eq!(tower.status, TowerStatus::Unreachable); + // } + // + // // 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 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 + // .lock() + // .unwrap() + // .towers + // .get_mut(&tower_id) + // .unwrap() + // .set_net_addr(server.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(); + // + // // 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; + // 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; + // + // task.abort(); + // } #[tokio::test] async fn test_retry_tower() { diff --git a/teos-ldk-client/src/storage.rs b/teos-ldk-client/src/storage.rs index 889d74b2..1956414d 100644 --- a/teos-ldk-client/src/storage.rs +++ b/teos-ldk-client/src/storage.rs @@ -1,4 +1,5 @@ use std::collections::{HashMap, HashSet}; +use std::io; use lightning::util::persist::KVStore; use lightning::io::Error; @@ -7,16 +8,50 @@ use serde::Serialize; use teos_common::{TowerId, UserId}; use teos_common::receipts::{AppointmentReceipt, RegistrationReceipt}; use teos_common::appointment::{Appointment, Locator}; +use bincode; use serde::Deserialize; use crate::{AppointmentStatus, MisbehaviorProof, TowerInfo, TowerStatus, TowerSummary}; +// Primary namespace for all watchtower-related data +const PRIMARY_NAMESPACE: &str = "watchtower"; + +// Secondary namespaces for different data types +const NS_TOWER_RECORDS: &str = "tower_records"; +const NS_REGISTRATION_RECEIPTS: &str = "registration_receipts"; +const NS_APPOINTMENT_RECEIPTS: &str = "appointment_receipts"; +const NS_PENDING_APPOINTMENTS: &str = "pending_appointments"; +const NS_INVALID_APPOINTMENTS: &str = "invalid_appointments"; +const NS_APPOINTMENTS: &str = "appointments"; +const NS_MISBEHAVIOR_PROOFS: &str = "misbehavior_proofs"; + +/// Trait for types that can be serialized to and from bytes +pub(crate) trait Serializable { + /// Convert the type to a byte vector + fn to_vec(&self) -> Result, io::Error>; + + /// Create an instance from a byte slice + fn from_slice(slice: &[u8]) -> Result where Self: Sized; +} + +impl Serializable for TowerInfo { + fn to_vec(&self) -> Result, io::Error> { + bincode::serialize(self).map_err(|e| io::Error::new(io::ErrorKind::Other, e)) + } + + fn from_slice(slice: &[u8]) -> Result { + bincode::deserialize(slice).map_err(|e| io::Error::new(io::ErrorKind::Other, e)) + } +} + #[derive(Serialize, Deserialize)] pub(crate) struct Storage { store: T, sk: String, } +// Implement methods to convert TowerInfo to vec and vice versa + impl Storage { pub(crate) fn new(store: T, sk: String) -> Self { Storage { @@ -25,16 +60,38 @@ impl Storage { } } + /// Creates a composite key from multiple components + fn make_key(components: &[&str]) -> String { + components.join(":") + } + + /// Gets the appropriate namespace based on appointment status + fn get_appointment_namespace(status: AppointmentStatus) -> &'static str { + match status { + AppointmentStatus::Accepted => NS_APPOINTMENT_RECEIPTS, + AppointmentStatus::Pending => NS_PENDING_APPOINTMENTS, + AppointmentStatus::Invalid => NS_INVALID_APPOINTMENTS, + } + } + /// Stores a tower record into the database alongside the corresponding registration receipt. /// /// This function MUST be guarded against inserting duplicate (tower_id, subscription_expiry) pairs. /// This is currently done in WTClient::add_update_tower. pub fn store_tower_record(&mut self, tower_id: TowerId, net_addr: &str, receipt: &RegistrationReceipt) -> Result<(), Error> { - // primary namespace: "watchtower" - // secondary namespance: "tower_record" - // key: - // value: ? - todo!(); + let key = Self::make_key(&[&tower_id.to_string()]); + + let value = TowerInfo::new( + net_addr.to_string(), + receipt.available_slots(), + receipt.subscription_start(), + receipt.subscription_expiry(), + HashMap::new(), + Vec::new(), + Vec::new(), + ).to_vec().unwrap(); + + self.store.write(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key, &value) } /// Loads a tower record from the database. @@ -43,58 +100,64 @@ impl Storage { /// 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) -> Option { - // primary namespace: "watchtower" - // secondary namespance: "tower_record" - // key: - todo!(); - } + let key = Self::make_key(&[&tower_id.to_string()]); + let value = match self.store.read(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key) { + Ok(v) => v, + Err(_) => return None, + }; - /// 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> { - // primary namespace: "watchtower" - // secondary namespance: "tower_record" - // key: - // value: ? - todo!(); - } + let mut tower_info = TowerInfo::from_slice(&value).unwrap(); + tower_info.appointments = self.load_appointment_receipts(tower_id); + tower_info.pending_appointments = self.load_appointments(tower_id, AppointmentStatus::Pending); + tower_info.invalid_appointments = self.load_appointments(tower_id, AppointmentStatus::Invalid); - /// Loads all tower records from the database. - pub fn load_towers(&self) -> HashMap { - // primary namespace: "watchtower" - // secondary namespance: "tower_record" - todo!(); - } - - /// Loads the latest registration receipt for a given tower. - /// - /// Latests is determined by the one with the `subscription_expiry` further into the future. - pub fn load_registration_receipt(&self, tower_id: TowerId, user_id: UserId) -> Option { - // primary namespace: "watchtower" - // secondary namespance: "registration_receipt" - // key: - todo!(); - } - - - /// Stores an appointments receipt into the database representing an appointment accepted by a given tower. - pub fn store_appointment_receipt(&mut self, tower_id: TowerId, locator: Locator, available_slots: u32, receipt: &AppointmentReceipt) -> Result<(), Error> { - // primary namespace: "watchtower" - // secondary namespance: "appointment_receipt" - // key: + - // value: ? - todo!(); - } - - /// Loads a given appointment receipt of a given tower from the database. - pub fn load_appointment_receipt(&self, tower_id: TowerId, locator: Locator) -> Option { - // primary namespace: "watchtower" - // secondary namespance: "appointment_receipt" - // key: + - todo!(); + Some(tower_info) } + // + // /// 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 key = Self::make_key(&[&tower_id.to_string()]); + // // primary namespace: "watchtower" + // // secondary namespance: "tower_record" + // // key: + // // value: ? + // self.store.write(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key, &value) + // } + // + // /// Loads all tower records from the database. + // pub fn load_towers(&self) -> HashMap { + // } + // + // /// Loads the latest registration receipt for a given tower. + // /// + // /// Latests is determined by the one with the `subscription_expiry` further into the future. + // pub fn load_registration_receipt(&self, tower_id: TowerId, user_id: UserId) -> Option { + // let key = Self::make_key(&[&tower_id.to_string()]); + // + // self.store.read(PRIMARY_NAMESPACE, NS_REGISTRATION_RECEIPTS, &key) + // } + // + // + // /// Stores an appointments receipt into the database representing an appointment accepted by a given tower. + // pub fn store_appointment_receipt(&mut self, tower_id: TowerId, locator: Locator, available_slots: u32, receipt: &AppointmentReceipt) -> Result<(), Error> { + // let key = Self::make_key(&[&tower_id.to_string(), &locator.to_string()]); + // // primary namespace: "watchtower" + // // secondary namespance: "appointment_receipt" + // // key: + + // // value: ? + // self.store.write(PRIMARY_NAMESPACE, NS_APPOINTMENT_RECEIPTS, &key, &value) + // } + // + // /// Loads a given appointment receipt of a given tower from the database. + // pub fn load_appointment_receipt(&self, tower_id: TowerId, locator: Locator) -> Option { + // let key = Self::make_key(&[&tower_id.to_string(), &locator.to_string()]); + // // primary namespace: "watchtower" + // // secondary namespance: "appointment_receipt" + // self.store.read(PRIMARY_NAMESPACE, NS_APPOINTMENT_RECEIPTS, &key) + // } /// Loads the appointment receipts associated to a given tower. /// @@ -107,78 +170,83 @@ impl Storage { todo!(); } - /// Loads a collection of locators from the database entry associated to a given tower. - /// - /// The loaded locators can be loaded either from appointment_receipts, pending_appointments or invalid_appointments - /// depending on `status`. - pub fn load_appointment_locators(&self, tower_id: TowerId, status: AppointmentStatus) -> HashSet { - // primary namespace: "watchtower" - // secondary namespance: "{status}_appointment_receipt" - todo!(); - } - - /// Loads an appointment from the database. - pub fn load_appointment(&self, locator: Locator) -> Option { - // primary namespace: "watchtower" - // secondary namespance: "appointment_receipt" - // key: - todo!(); - } - - /// Stores an appointment into the database. - /// - /// Appointments are only stored as a whole when they are pending or invalid. - /// Accepted appointments are simplified in the form of an appointment receipt. - fn store_appointment(&self, appointment: &Appointment) -> Result { - // primary namespace: "watchtower" - // secondary namespance: "appointment_receipt" - // key: ? - // value: ? - todo!(); - } - - /// Stores a pending appointment into the database. - /// - /// A pending appointment is an appointment that was sent to a tower when it was unreachable. - /// This data is stored so it can be resent once the tower comes back online. - /// Internally calls [Self::store_appointment]. - pub fn store_pending_appointment(&self, tower_id: TowerId, appointment: &Appointment) -> Result<(), Error> { - // primary namespace: "watchtower" - // secondary namespance: "pending_appointment_receipt" - // key: - // value: ? - todo!(); - } - - /// Removes a pending appointment from the database. - /// - /// If the pending appointment is the only instance of the appointment, the appointment will also be deleted form the appointments table. - pub fn delete_pending_appointment(&self, tower_id: TowerId, locator: Locator) -> Result<(), Error> { - // primary namespace: "watchtower" - // secondary namespance: "pending_appointment_receipt" - // key: ? - // value: ? - todo!(); - } - - /// Stores an invalid appointment into the database. - /// - /// An invalid appointment is an appointment that was rejected by the tower. - /// Storing this data may allow us to see what was the issue and send the data later on. - /// Internally calls [Self::store_appointment]. - pub fn store_invalid_appointment(&mut self, tower_id: TowerId, appointment: &Appointment) -> Result<(), Error> { - // primary namespace: "watchtower" - // secondary namespance: "invalid_appointment_receipt" - // key: ? - // value: ? - todo!(); - } + // /// Loads a collection of locators from the database entry associated to a given tower. + // /// + // /// The loaded locators can be loaded either from appointment_receipts, pending_appointments or invalid_appointments + // /// depending on `status`. + // pub fn load_appointment_locators(&self, tower_id: TowerId, status: AppointmentStatus) -> HashSet { + // // primary namespace: "watchtower" + // // secondary namespance: "{status}_appointment_receipt" + // todo!(); + // } + // + // /// Loads an appointment from the database. + // pub fn load_appointment(&self, locator: Locator) -> Option { + // // primary namespace: "watchtower" + // // secondary namespance: "appointment_receipt" + // // key: + // todo!(); + // } + // + // /// Stores an appointment into the database. + // /// + // /// Appointments are only stored as a whole when they are pending or invalid. + // /// Accepted appointments are simplified in the form of an appointment receipt. + // fn store_appointment(&self, appointment: &Appointment) -> Result { + // let key = Self::make_key(&[&tower_id.to_string(), &appointment.locator.to_string()]); + // // primary namespace: "watchtower" + // // secondary namespance: "appointment_receipt" + // // key: ? + // // value: ? + // todo!(); + // } + // + // /// Stores a pending appointment into the database. + // /// + // /// A pending appointment is an appointment that was sent to a tower when it was unreachable. + // /// This data is stored so it can be resent once the tower comes back online. + // /// Internally calls [Self::store_appointment]. + // pub fn store_pending_appointment(&self, tower_id: TowerId, appointment: &Appointment) -> Result<(), Error> { + // let key = Self::make_key(&[&tower_id.to_string(), &appointment.locator.to_string()]); + // // primary namespace: "watchtower" + // // secondary namespance: "pending_appointment_receipt" + // // key: + // // value: ? + // todo!(); + // } + // + // /// Removes a pending appointment from the database. + // /// + // /// If the pending appointment is the only instance of the appointment, the appointment will also be deleted form the appointments table. + // pub fn delete_pending_appointment(&self, tower_id: TowerId, locator: Locator) -> Result<(), Error> { + // let key = Self::make_key(&[&tower_id.to_string(), &appointment.locator.to_string()]); + // // primary namespace: "watchtower" + // // secondary namespance: "pending_appointment_receipt" + // // key: ? + // // value: ? + // todo!(); + // } + // + // /// Stores an invalid appointment into the database. + // /// + // /// An invalid appointment is an appointment that was rejected by the tower. + // /// Storing this data may allow us to see what was the issue and send the data later on. + // /// Internally calls [Self::store_appointment]. + // pub fn store_invalid_appointment(&mut self, tower_id: TowerId, appointment: &Appointment) -> Result<(), Error> { + // let key = Self::make_key(&[&tower_id.to_string(), &appointment.locator.to_string()]); + // // primary namespace: "watchtower" + // // secondary namespance: "invalid_appointment_receipt" + // // key: ? + // // value: ? + // todo!(); + // } /// Loads non finalized appointments from the database for a given tower based on a status flag. /// /// This is meant to be used only for pending and invalid appointments, if the method is called for /// accepted appointment, an empty collection will be returned. pub fn load_appointments(&self, tower_id: TowerId, status: AppointmentStatus) -> Vec { + let key = Self::make_key(&[&tower_id.to_string()]); // primary namespace: "watchtower" // secondary namespance: "{status}_appointment_receipt" // key: ? @@ -186,31 +254,34 @@ impl Storage { todo!(); } - /// Stores a misbehaving proof into the database. - /// - /// A misbehaving proof is proof that the tower has signed an appointment using a key different - /// than the one advertised to the user when they registered. - pub fn store_misbehaving_proof(&self, tower_id: TowerId, proof: &MisbehaviorProof) -> Result<(), Error> { - // primary namespace: "watchtower" - // secondary namespance: "misbehaving_proof" - // key: ? - // value: ? - todo!(); - } - - /// Loads the misbehaving proof for a given tower from the database (if found). - fn load_misbehaving_proof(&self, tower_id: TowerId) -> Option { - // primary namespace: "watchtower" - // secondary namespance: "misbehaving_proof" - // key: ? - todo!(); - } - - /// Checks whether a misbehaving proof exists for a given tower. - fn exists_misbehaving_proof(&self, tower_id: TowerId) -> bool { - // primary namespace: "watchtower" - // secondary namespance: "misbehaving_proof" - // key: ? - todo!(); - } + // /// Stores a misbehaving proof into the database. + // /// + // /// A misbehaving proof is proof that the tower has signed an appointment using a key different + // /// than the one advertised to the user when they registered. + // pub fn store_misbehaving_proof(&self, tower_id: TowerId, proof: &MisbehaviorProof) -> Result<(), Error> { + // let key = Self::make_key(&[&tower_id.to_string()]); + // // primary namespace: "watchtower" + // // secondary namespance: "misbehaving_proof" + // // key: ? + // // value: ? + // todo!(); + // } + // + // /// Loads the misbehaving proof for a given tower from the database (if found). + // fn load_misbehaving_proof(&self, tower_id: TowerId) -> Option { + // let key = Self::make_key(&[&tower_id.to_string()]); + // // primary namespace: "watchtower" + // // secondary namespance: "misbehaving_proof" + // // key: ? + // todo!(); + // } + // + // /// Checks whether a misbehaving proof exists for a given tower. + // fn exists_misbehaving_proof(&self, tower_id: TowerId) -> bool { + // let key = Self::make_key(&[&tower_id.to_string()]); + // // primary namespace: "watchtower" + // // secondary namespance: "misbehaving_proof" + // // key: ? + // todo!(); + // } } diff --git a/teos-ldk-client/src/wt_client.rs b/teos-ldk-client/src/wt_client.rs index 8bf82508..abd9ae85 100644 --- a/teos-ldk-client/src/wt_client.rs +++ b/teos-ldk-client/src/wt_client.rs @@ -8,12 +8,11 @@ use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; use teos_common::appointment::{Appointment, Locator}; use teos_common::cryptography; -use teos_common::dbm::Error as DBError; use teos_common::receipts::{AppointmentReceipt, RegistrationReceipt}; use teos_common::{TowerId, UserId}; -use crate::net::ProxyInfo; use crate::retrier::RetrierStatus; +use crate::storage::Storage; use crate::{MisbehaviorProof, SubscriptionError, TowerInfo, TowerStatus, TowerSummary}; #[derive(Eq, PartialEq)] @@ -58,8 +57,8 @@ impl std::fmt::Debug for RevocationData { /// Represents the watchtower client that is being used as the CoreLN plugin state. pub struct WTClient { - /// A [DBM] instance. - pub dbm: DBM, + /// A [Storage] instance. + pub storage: Storage, /// A collection of towers the client is registered to. pub towers: HashMap, /// Queue of unreachable towers. @@ -70,22 +69,12 @@ pub struct WTClient { pub user_sk: SecretKey, /// The user identifier. pub user_id: UserId, - /// Optional proxy - 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| { @@ -93,9 +82,9 @@ impl WTClient { std::process::exit(1); }); - let dbm = DBM::new(&data_dir.join("watchtowers_db.sql3")).unwrap(); + let storage = Storage::new().unwrap(); - let (user_sk, user_id) = if let Some(sk) = dbm.load_client_key() { + let (user_sk, user_id) = if let Some(sk) = storage.load_client_key() { ( sk, UserId(PublicKey::from_secret_key(&Secp256k1::new(), &sk)), @@ -103,11 +92,11 @@ impl WTClient { } 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(); + storage.store_client_key(&sk).unwrap(); (sk, UserId(pk)) }; - let towers = dbm.load_towers(); + let towers = storage.load_towers(); for (tower_id, tower) in towers.iter() { if tower.status.is_temporary_unreachable() { unreachable_towers @@ -125,10 +114,9 @@ impl WTClient { towers, unreachable_towers, retriers: HashMap::new(), - dbm, + storage, user_sk, user_id, - proxy, } } @@ -145,14 +133,14 @@ impl WTClient { if receipt.subscription_expiry() <= tower.subscription_expiry { return Err(SubscriptionError::Expiry); } else { - let tower_info = self.dbm.load_tower_record(tower_id).unwrap(); + let tower_info = self.storage.load_tower_record(tower_id).unwrap(); if receipt.available_slots() <= tower_info.available_slots { return Err(SubscriptionError::Slots); } } } - self.dbm + self.storage .store_tower_record(tower_id, tower_net_addr, receipt) .unwrap(); @@ -180,12 +168,12 @@ impl WTClient { /// Gets the latest registration receipt of a given tower. pub fn get_registration_receipt(&self, tower_id: TowerId) -> Option { - self.dbm.load_registration_receipt(tower_id, self.user_id) + self.storage.load_registration_receipt(tower_id, self.user_id) } /// Loads a tower record from the database. pub fn load_tower_info(&self, tower_id: TowerId) -> Option { - self.dbm.load_tower_record(tower_id) + self.storage.load_tower_record(tower_id) } /// Gets the given tower status (identified by tower_id), if found. @@ -223,7 +211,7 @@ impl WTClient { // DISCUSS: It may be nice to independently compute the slots and compare tower.available_slots = available_slots; - self.dbm + self.storage .store_appointment_receipt(tower_id, locator, available_slots, receipt) .unwrap(); } else { @@ -237,7 +225,7 @@ impl WTClient { tower_id: TowerId, locator: Locator, ) -> Option { - self.dbm.load_appointment_receipt(tower_id, locator) + self.storage.load_appointment_receipt(tower_id, locator) } /// Adds a pending appointment to the tower record. @@ -245,7 +233,7 @@ impl WTClient { if let Some(tower) = self.towers.get_mut(&tower_id) { tower.pending_appointments.insert(appointment.locator); - self.dbm + self.storage .store_pending_appointment(tower_id, appointment) .unwrap(); } else { @@ -258,7 +246,7 @@ impl WTClient { if let Some(tower) = self.towers.get_mut(&tower_id) { tower.pending_appointments.remove(&locator); - self.dbm + self.storage .delete_pending_appointment(tower_id, locator) .unwrap(); } else { @@ -271,7 +259,7 @@ impl WTClient { if let Some(tower) = self.towers.get_mut(&tower_id) { tower.invalid_appointments.insert(appointment.locator); - self.dbm + self.storage .store_invalid_appointment(tower_id, appointment) .unwrap(); } else { @@ -282,7 +270,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.store_misbehaving_proof(tower_id, &proof).unwrap(); + self.storage.store_misbehaving_proof(tower_id, &proof).unwrap(); tower.status = TowerStatus::Misbehaving; } else { log::error!("Cannot flag tower. Unknown tower_id: {tower_id}"); @@ -295,7 +283,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.remove_tower_record(tower_id) + self.storage.remove_tower_record(tower_id) } else { Err(DBError::NotFound) } @@ -582,8 +570,8 @@ mod tests { .unwrap() .pending_appointments .contains(&appointment.locator)); - // This bit is tested exhaustively in the DBM. - assert!(!wt_client.dbm.appointment_exists(appointment.locator)); + // This bit is tested exhaustively in the Storage. + assert!(!wt_client.storage.appointment_exists(appointment.locator)); } #[tokio::test] @@ -658,14 +646,14 @@ mod tests { .invalid_appointments .contains(&appointment.locator)); assert!(!wt_client - .dbm + .storage .load_appointment_locators(tower_id, crate::AppointmentStatus::Pending) .contains(&appointment.locator)); assert!(wt_client - .dbm + .storage .load_appointment_locators(tower_id, crate::AppointmentStatus::Invalid) .contains(&appointment.locator)); - assert!(wt_client.dbm.appointment_exists(appointment.locator)); + assert!(wt_client.storage.appointment_exists(appointment.locator)); } #[tokio::test] @@ -709,11 +697,11 @@ mod tests { .invalid_appointments .contains(&appointment.locator)); assert!(!wt_client - .dbm + .storage .load_appointment_locators(tower_id, crate::AppointmentStatus::Pending) .contains(&appointment.locator)); assert!(wt_client - .dbm + .storage .load_appointment_locators(tower_id, crate::AppointmentStatus::Invalid) .contains(&appointment.locator)); @@ -731,16 +719,16 @@ mod tests { .invalid_appointments .contains(&appointment.locator)); assert!(wt_client - .dbm + .storage .load_appointment_locators(another_tower_id, crate::AppointmentStatus::Pending) .contains(&appointment.locator)); assert!(!wt_client - .dbm + .storage .load_appointment_locators(another_tower_id, crate::AppointmentStatus::Invalid) .contains(&appointment.locator)); // GENERAL - assert!(wt_client.dbm.appointment_exists(appointment.locator)); + assert!(wt_client.storage.appointment_exists(appointment.locator)); } #[tokio::test] @@ -825,13 +813,13 @@ mod tests { registration_receipt.available_slots(), &appointment_receipt, ); - assert!(wt_client.dbm.appointment_receipt_exists(locator, tower_id)); + assert!(wt_client.storage.appointment_receipt_exists(locator, tower_id)); // Remove and check both the tower and the appointment wt_client.remove_tower(tower_id).unwrap(); 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)); + assert!(!wt_client.storage.appointment_receipt_exists(locator, tower_id)); } #[tokio::test] @@ -875,15 +863,15 @@ mod tests { ); // Check that the data exists in both towers - assert!(wt_client.dbm.appointment_receipt_exists(locator, tower1_id)); - assert!(wt_client.dbm.appointment_receipt_exists(locator, tower2_id)); + assert!(wt_client.storage.appointment_receipt_exists(locator, tower1_id)); + assert!(wt_client.storage.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!(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)); + assert!(!wt_client.storage.appointment_receipt_exists(locator, tower1_id)); + assert!(wt_client.storage.appointment_receipt_exists(locator, tower2_id)); } #[tokio::test] diff --git a/watchtower-plugin/src/lib.rs b/watchtower-plugin/src/lib.rs index 8f416586..af2358a2 100755 --- a/watchtower-plugin/src/lib.rs +++ b/watchtower-plugin/src/lib.rs @@ -1,7 +1,7 @@ use std::collections::{HashMap, HashSet}; use std::fmt; -use serde::Serialize; +use serde::{Serialize, Deserialize}; use teos_common::appointment::{Appointment, Locator}; use teos_common::net::NetAddr; @@ -195,7 +195,7 @@ impl From for TowerSummary { } /// Summarized data associated with a given tower. -#[derive(Clone, Serialize, Debug, PartialEq, Eq)] +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] pub struct TowerInfo { pub net_addr: String, pub available_slots: u32, From 39159c86cafb7a56c22ffc1ebf1bc7b3a811b88e Mon Sep 17 00:00:00 2001 From: dzdidi Date: Thu, 6 Feb 2025 18:36:48 +0100 Subject: [PATCH 40/85] Derive deserializaiton for AppointmentReceipt Signed-off-by: dzdidi --- teos-common/src/receipts.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/teos-common/src/receipts.rs b/teos-common/src/receipts.rs index 5dc0e00c..136dcd20 100644 --- a/teos-common/src/receipts.rs +++ b/teos-common/src/receipts.rs @@ -1,6 +1,6 @@ //! Receipts issued by towers and handed to users as commitment proof. -use serde::Serialize; +use serde::{Serialize, Deserialize}; use bitcoin::secp256k1::SecretKey; @@ -107,7 +107,7 @@ impl RegistrationReceipt { /// Proof that a certain state was backed up with the tower. /// /// Appointment receipts can be used alongside a registration receipt that covers it, and on chain data (a breach not being reacted with a penalty), to prove a tower has not reacted to a channel breach. -#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +#[derive(Debug, Deserialize, Clone, PartialEq, Eq, Serialize)] pub struct AppointmentReceipt { user_signature: String, start_block: u32, From d4213d7d30623ca857083650e8a59832be217f27 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Thu, 6 Feb 2025 18:37:07 +0100 Subject: [PATCH 41/85] draft based on wt p3 Signed-off-by: dzdidi --- teos-ldk-client/src/lib.rs | 19 +- teos-ldk-client/src/storage.rs | 327 ++++++++++++++++----------------- 2 files changed, 169 insertions(+), 177 deletions(-) diff --git a/teos-ldk-client/src/lib.rs b/teos-ldk-client/src/lib.rs index 2bb56c6b..5c657238 100644 --- a/teos-ldk-client/src/lib.rs +++ b/teos-ldk-client/src/lib.rs @@ -1,7 +1,10 @@ use std::collections::{HashMap, HashSet}; use std::fmt; +use std::io; -use serde::Serialize; +use serde::{Serialize, Deserialize}; + +use bincode; use teos_common::appointment::{Appointment, Locator}; use teos_common::net::NetAddr; @@ -19,7 +22,7 @@ pub mod wt_client; mod test_utils; /// The status the tower can be found at. -#[derive(Clone, Serialize, PartialEq, Eq, Copy, Debug)] +#[derive(Clone, Deserialize, Serialize, PartialEq, Eq, Copy, Debug)] #[serde(rename_all = "snake_case")] pub enum TowerStatus { Reachable, @@ -194,7 +197,7 @@ impl From for TowerSummary { } /// Summarized data associated with a given tower. -#[derive(Clone, Serialize, Debug, PartialEq, Eq)] +#[derive(Clone, Deserialize, Serialize, Debug, PartialEq, Eq)] pub struct TowerInfo { pub net_addr: String, pub available_slots: u32, @@ -245,10 +248,18 @@ impl TowerInfo { pub fn set_misbehaving_proof(&mut self, proof: MisbehaviorProof) { self.misbehaving_proof = Some(proof); } + + fn to_vec(&self) -> Result, io::Error> { + bincode::serialize(self).map_err(|e| io::Error::new(io::ErrorKind::Other, e)) + } + + fn from_slice(slice: &[u8]) -> Result { + bincode::deserialize(slice).map_err(|e| io::Error::new(io::ErrorKind::Other, e)) + } } /// A misbehaving proof. Contains proof of a tower replying with a public key different from the advertised one. -#[derive(Clone, Serialize, Debug, PartialEq, Eq)] +#[derive(Clone, Deserialize, Serialize, Debug, PartialEq, Eq)] pub struct MisbehaviorProof { #[serde(with = "hex::serde")] pub locator: Locator, diff --git a/teos-ldk-client/src/storage.rs b/teos-ldk-client/src/storage.rs index 1956414d..176e928f 100644 --- a/teos-ldk-client/src/storage.rs +++ b/teos-ldk-client/src/storage.rs @@ -1,16 +1,12 @@ use std::collections::{HashMap, HashSet}; -use std::io; use lightning::util::persist::KVStore; use lightning::io::Error; use bitcoin::secp256k1::SecretKey; -use serde::Serialize; use teos_common::{TowerId, UserId}; use teos_common::receipts::{AppointmentReceipt, RegistrationReceipt}; use teos_common::appointment::{Appointment, Locator}; -use bincode; -use serde::Deserialize; use crate::{AppointmentStatus, MisbehaviorProof, TowerInfo, TowerStatus, TowerSummary}; // Primary namespace for all watchtower-related data @@ -18,33 +14,13 @@ const PRIMARY_NAMESPACE: &str = "watchtower"; // Secondary namespaces for different data types const NS_TOWER_RECORDS: &str = "tower_records"; +const NS_APPOINTMENTS: &str = "appointments"; const NS_REGISTRATION_RECEIPTS: &str = "registration_receipts"; const NS_APPOINTMENT_RECEIPTS: &str = "appointment_receipts"; const NS_PENDING_APPOINTMENTS: &str = "pending_appointments"; const NS_INVALID_APPOINTMENTS: &str = "invalid_appointments"; -const NS_APPOINTMENTS: &str = "appointments"; const NS_MISBEHAVIOR_PROOFS: &str = "misbehavior_proofs"; -/// Trait for types that can be serialized to and from bytes -pub(crate) trait Serializable { - /// Convert the type to a byte vector - fn to_vec(&self) -> Result, io::Error>; - - /// Create an instance from a byte slice - fn from_slice(slice: &[u8]) -> Result where Self: Sized; -} - -impl Serializable for TowerInfo { - fn to_vec(&self) -> Result, io::Error> { - bincode::serialize(self).map_err(|e| io::Error::new(io::ErrorKind::Other, e)) - } - - fn from_slice(slice: &[u8]) -> Result { - bincode::deserialize(slice).map_err(|e| io::Error::new(io::ErrorKind::Other, e)) - } -} - -#[derive(Serialize, Deserialize)] pub(crate) struct Storage { store: T, sk: String, @@ -53,11 +29,11 @@ pub(crate) struct Storage { // Implement methods to convert TowerInfo to vec and vice versa impl Storage { - pub(crate) fn new(store: T, sk: String) -> Self { - Storage { + pub(crate) fn new(store: T, sk: String) -> Result { + Ok(Storage { store, sk - } + }) } /// Creates a composite key from multiple components @@ -91,6 +67,8 @@ impl Storage { Vec::new(), ).to_vec().unwrap(); + // TODO: encrypt + self.store.write(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key, &value) } @@ -106,6 +84,8 @@ impl Storage { Err(_) => return None, }; + // TODO: decrypt + let mut tower_info = TowerInfo::from_slice(&value).unwrap(); tower_info.appointments = self.load_appointment_receipts(tower_id); tower_info.pending_appointments = self.load_appointments(tower_id, AppointmentStatus::Pending); @@ -113,51 +93,52 @@ impl Storage { Some(tower_info) } - // - // /// 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 key = Self::make_key(&[&tower_id.to_string()]); - // // primary namespace: "watchtower" - // // secondary namespance: "tower_record" - // // key: - // // value: ? - // self.store.write(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key, &value) - // } - // - // /// Loads all tower records from the database. - // pub fn load_towers(&self) -> HashMap { - // } - // - // /// Loads the latest registration receipt for a given tower. - // /// - // /// Latests is determined by the one with the `subscription_expiry` further into the future. - // pub fn load_registration_receipt(&self, tower_id: TowerId, user_id: UserId) -> Option { - // let key = Self::make_key(&[&tower_id.to_string()]); - // - // self.store.read(PRIMARY_NAMESPACE, NS_REGISTRATION_RECEIPTS, &key) - // } - // - // - // /// Stores an appointments receipt into the database representing an appointment accepted by a given tower. - // pub fn store_appointment_receipt(&mut self, tower_id: TowerId, locator: Locator, available_slots: u32, receipt: &AppointmentReceipt) -> Result<(), Error> { - // let key = Self::make_key(&[&tower_id.to_string(), &locator.to_string()]); - // // primary namespace: "watchtower" - // // secondary namespance: "appointment_receipt" - // // key: + - // // value: ? - // self.store.write(PRIMARY_NAMESPACE, NS_APPOINTMENT_RECEIPTS, &key, &value) - // } - // - // /// Loads a given appointment receipt of a given tower from the database. - // pub fn load_appointment_receipt(&self, tower_id: TowerId, locator: Locator) -> Option { - // let key = Self::make_key(&[&tower_id.to_string(), &locator.to_string()]); - // // primary namespace: "watchtower" - // // secondary namespance: "appointment_receipt" - // self.store.read(PRIMARY_NAMESPACE, NS_APPOINTMENT_RECEIPTS, &key) - // } + + /// 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 key = Self::make_key(&[&tower_id.to_string()]); + // primary namespace: "watchtower" + // secondary namespance: "tower_record" + // key: + // value: ? + self.store.remove(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key, true) + } + + /// Loads all tower records from the database. + pub fn load_towers(&self) -> HashMap { + todo!() + } + + /// Loads the latest registration receipt for a given tower. + /// + /// Latests is determined by the one with the `subscription_expiry` further into the future. + pub fn load_registration_receipt(&self, tower_id: TowerId, user_id: UserId) -> Option { + let key = Self::make_key(&[&tower_id.to_string()]); + + self.store.read(PRIMARY_NAMESPACE, NS_REGISTRATION_RECEIPTS, &key) + } + + + /// Stores an appointments receipt into the database representing an appointment accepted by a given tower. + pub fn store_appointment_receipt(&mut self, tower_id: TowerId, locator: Locator, available_slots: u32, receipt: &AppointmentReceipt) -> Result<(), Error> { + let key = Self::make_key(&[&tower_id.to_string(), &locator.to_string()]); + // primary namespace: "watchtower" + // secondary namespance: "appointment_receipt" + // key: + + // value: ? + self.store.write(PRIMARY_NAMESPACE, NS_APPOINTMENT_RECEIPTS, &key, &value) + } + + /// Loads a given appointment receipt of a given tower from the database. + pub fn load_appointment_receipt(&self, tower_id: TowerId, locator: Locator) -> Option { + let key = Self::make_key(&[&tower_id.to_string(), &locator.to_string()]); + // primary namespace: "watchtower" + // secondary namespance: "appointment_receipt" + self.store.read(PRIMARY_NAMESPACE, NS_APPOINTMENT_RECEIPTS, &key) + } /// Loads the appointment receipts associated to a given tower. /// @@ -170,76 +151,76 @@ impl Storage { todo!(); } - // /// Loads a collection of locators from the database entry associated to a given tower. - // /// - // /// The loaded locators can be loaded either from appointment_receipts, pending_appointments or invalid_appointments - // /// depending on `status`. - // pub fn load_appointment_locators(&self, tower_id: TowerId, status: AppointmentStatus) -> HashSet { - // // primary namespace: "watchtower" - // // secondary namespance: "{status}_appointment_receipt" - // todo!(); - // } - // - // /// Loads an appointment from the database. - // pub fn load_appointment(&self, locator: Locator) -> Option { - // // primary namespace: "watchtower" - // // secondary namespance: "appointment_receipt" - // // key: - // todo!(); - // } - // - // /// Stores an appointment into the database. - // /// - // /// Appointments are only stored as a whole when they are pending or invalid. - // /// Accepted appointments are simplified in the form of an appointment receipt. - // fn store_appointment(&self, appointment: &Appointment) -> Result { - // let key = Self::make_key(&[&tower_id.to_string(), &appointment.locator.to_string()]); - // // primary namespace: "watchtower" - // // secondary namespance: "appointment_receipt" - // // key: ? - // // value: ? - // todo!(); - // } - // - // /// Stores a pending appointment into the database. - // /// - // /// A pending appointment is an appointment that was sent to a tower when it was unreachable. - // /// This data is stored so it can be resent once the tower comes back online. - // /// Internally calls [Self::store_appointment]. - // pub fn store_pending_appointment(&self, tower_id: TowerId, appointment: &Appointment) -> Result<(), Error> { - // let key = Self::make_key(&[&tower_id.to_string(), &appointment.locator.to_string()]); - // // primary namespace: "watchtower" - // // secondary namespance: "pending_appointment_receipt" - // // key: - // // value: ? - // todo!(); - // } - // - // /// Removes a pending appointment from the database. - // /// - // /// If the pending appointment is the only instance of the appointment, the appointment will also be deleted form the appointments table. - // pub fn delete_pending_appointment(&self, tower_id: TowerId, locator: Locator) -> Result<(), Error> { - // let key = Self::make_key(&[&tower_id.to_string(), &appointment.locator.to_string()]); - // // primary namespace: "watchtower" - // // secondary namespance: "pending_appointment_receipt" - // // key: ? - // // value: ? - // todo!(); - // } - // - // /// Stores an invalid appointment into the database. - // /// - // /// An invalid appointment is an appointment that was rejected by the tower. - // /// Storing this data may allow us to see what was the issue and send the data later on. - // /// Internally calls [Self::store_appointment]. - // pub fn store_invalid_appointment(&mut self, tower_id: TowerId, appointment: &Appointment) -> Result<(), Error> { - // let key = Self::make_key(&[&tower_id.to_string(), &appointment.locator.to_string()]); - // // primary namespace: "watchtower" - // // secondary namespance: "invalid_appointment_receipt" - // // key: ? - // // value: ? - // todo!(); - // } + /// Loads a collection of locators from the database entry associated to a given tower. + /// + /// The loaded locators can be loaded either from appointment_receipts, pending_appointments or invalid_appointments + /// depending on `status`. + pub fn load_appointment_locators(&self, tower_id: TowerId, status: AppointmentStatus) -> HashSet { + // primary namespace: "watchtower" + // secondary namespance: "{status}_appointment_receipt" + todo!(); + } + + /// Loads an appointment from the database. + pub fn load_appointment(&self, locator: Locator) -> Option { + // primary namespace: "watchtower" + // secondary namespance: "appointment_receipt" + // key: + todo!(); + } + + /// Stores an appointment into the database. + /// + /// Appointments are only stored as a whole when they are pending or invalid. + /// Accepted appointments are simplified in the form of an appointment receipt. + fn store_appointment(&self, appointment: &Appointment) -> Result { + let key = Self::make_key(&[&appointment.locator.to_string()]); + // primary namespace: "watchtower" + // secondary namespance: "appointment" + // key: + // value: appointment.encrypted_blob() + todo!(); + } + + /// Stores a pending appointment into the database. + /// + /// A pending appointment is an appointment that was sent to a tower when it was unreachable. + /// This data is stored so it can be resent once the tower comes back online. + /// Internally calls [Self::store_appointment]. + pub fn store_pending_appointment(&self, tower_id: TowerId, appointment: &Appointment) -> Result<(), Error> { + let key = Self::make_key(&[&tower_id.to_string(), &appointment.locator.to_string()]); + // primary namespace: "watchtower" + // secondary namespance: "pending_appointment" + // key: + // value: ? + todo!(); + } + + /// Removes a pending appointment from the database. + /// + /// If the pending appointment is the only instance of the appointment, the appointment will also be deleted form the appointments table. + pub fn delete_pending_appointment(&self, tower_id: TowerId, locator: Locator) -> Result<(), Error> { + let key = Self::make_key(&[&tower_id.to_string(), &locator.to_string()]); + // primary namespace: "watchtower" + // secondary namespance: "pending_appointment" + // key: ? + // value: ? + todo!(); + } + + /// Stores an invalid appointment into the database. + /// + /// An invalid appointment is an appointment that was rejected by the tower. + /// Storing this data may allow us to see what was the issue and send the data later on. + /// Internally calls [Self::store_appointment]. + pub fn store_invalid_appointment(&mut self, tower_id: TowerId, appointment: &Appointment) -> Result<(), Error> { + let key = Self::make_key(&[&tower_id.to_string(), &appointment.locator.to_string()]); + // primary namespace: "watchtower" + // secondary namespance: "invalid_appointment" + // key: ? + // value: ? + todo!(); + } /// Loads non finalized appointments from the database for a given tower based on a status flag. /// @@ -254,34 +235,34 @@ impl Storage { todo!(); } - // /// Stores a misbehaving proof into the database. - // /// - // /// A misbehaving proof is proof that the tower has signed an appointment using a key different - // /// than the one advertised to the user when they registered. - // pub fn store_misbehaving_proof(&self, tower_id: TowerId, proof: &MisbehaviorProof) -> Result<(), Error> { - // let key = Self::make_key(&[&tower_id.to_string()]); - // // primary namespace: "watchtower" - // // secondary namespance: "misbehaving_proof" - // // key: ? - // // value: ? - // todo!(); - // } - // - // /// Loads the misbehaving proof for a given tower from the database (if found). - // fn load_misbehaving_proof(&self, tower_id: TowerId) -> Option { - // let key = Self::make_key(&[&tower_id.to_string()]); - // // primary namespace: "watchtower" - // // secondary namespance: "misbehaving_proof" - // // key: ? - // todo!(); - // } - // - // /// Checks whether a misbehaving proof exists for a given tower. - // fn exists_misbehaving_proof(&self, tower_id: TowerId) -> bool { - // let key = Self::make_key(&[&tower_id.to_string()]); - // // primary namespace: "watchtower" - // // secondary namespance: "misbehaving_proof" - // // key: ? - // todo!(); - // } + /// Stores a misbehaving proof into the database. + /// + /// A misbehaving proof is proof that the tower has signed an appointment using a key different + /// than the one advertised to the user when they registered. + pub fn store_misbehaving_proof(&self, tower_id: TowerId, proof: &MisbehaviorProof) -> Result<(), Error> { + let key = Self::make_key(&[&tower_id.to_string()]); + // primary namespace: "watchtower" + // secondary namespance: "misbehaving_proof" + // key: ? + // value: ? + todo!(); + } + + /// Loads the misbehaving proof for a given tower from the database (if found). + fn load_misbehaving_proof(&self, tower_id: TowerId) -> Option { + let key = Self::make_key(&[&tower_id.to_string()]); + // primary namespace: "watchtower" + // secondary namespance: "misbehaving_proof" + // key: ? + todo!(); + } + + /// Checks whether a misbehaving proof exists for a given tower. + fn exists_misbehaving_proof(&self, tower_id: TowerId) -> bool { + let key = Self::make_key(&[&tower_id.to_string()]); + // primary namespace: "watchtower" + // secondary namespance: "misbehaving_proof" + // key: ? + todo!(); + } } From 32133ec833aadabd980f16a8deacd867736bf6e2 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Fri, 7 Feb 2025 09:46:00 +0100 Subject: [PATCH 42/85] feature flag for storage; sqlite Signed-off-by: dzdidi --- teos-ldk-client/Cargo.toml | 5 + teos-ldk-client/src/lib.rs | 4 +- teos-ldk-client/src/net/http.rs | 15 +- teos-ldk-client/src/retrier.rs | 477 +++--- teos-ldk-client/src/storage/dbm.rs | 1298 +++++++++++++++++ .../src/{storage.rs => storage/kv.rs} | 104 +- teos-ldk-client/src/storage/mod.rs | 19 + teos-ldk-client/src/wt_client.rs | 239 ++- 8 files changed, 1878 insertions(+), 283 deletions(-) create mode 100644 teos-ldk-client/src/storage/dbm.rs rename teos-ldk-client/src/{storage.rs => storage/kv.rs} (80%) create mode 100644 teos-ldk-client/src/storage/mod.rs diff --git a/teos-ldk-client/Cargo.toml b/teos-ldk-client/Cargo.toml index 38619371..35d21545 100644 --- a/teos-ldk-client/Cargo.toml +++ b/teos-ldk-client/Cargo.toml @@ -3,6 +3,11 @@ name = "teos-ldk-client" version = "0.1.0" edition = "2021" +[features] +default = ["sqlite"] +sqlite = [] +kv = [] + [dependencies] # General backoff = { version = "0.4.0", features = ["tokio"] } diff --git a/teos-ldk-client/src/lib.rs b/teos-ldk-client/src/lib.rs index 5c657238..b787bffa 100644 --- a/teos-ldk-client/src/lib.rs +++ b/teos-ldk-client/src/lib.rs @@ -2,7 +2,7 @@ use std::collections::{HashMap, HashSet}; use std::fmt; use std::io; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; use bincode; @@ -12,10 +12,10 @@ use teos_common::receipts::AppointmentReceipt; use teos_common::TowerId; pub mod convert; -pub mod storage; pub mod net; pub mod retrier; mod ser; +pub mod storage; pub mod wt_client; #[cfg(test)] diff --git a/teos-ldk-client/src/net/http.rs b/teos-ldk-client/src/net/http.rs index 5e2bcc58..d8e8307e 100644 --- a/teos-ldk-client/src/net/http.rs +++ b/teos-ldk-client/src/net/http.rs @@ -116,12 +116,7 @@ pub async fn send_appointment( }; match process_post_response( - post_request( - tower_net_addr, - Endpoint::AddAppointment, - &request_data, - ) - .await, + post_request(tower_net_addr, Endpoint::AddAppointment, &request_data).await, ) .await? { @@ -593,12 +588,8 @@ mod tests { .create_async() .await; - let response = post_request( - &NetAddr::new(server.url()), - Endpoint::Register, - json!(""), - ) - .await; + let response = + post_request(&NetAddr::new(server.url()), Endpoint::Register, json!("")).await; api_mock.assert_async().await; assert!(matches!(response, Ok(Response { .. }))); diff --git a/teos-ldk-client/src/retrier.rs b/teos-ldk-client/src/retrier.rs index cdb5aecb..c097c299 100644 --- a/teos-ldk-client/src/retrier.rs +++ b/teos-ldk-client/src/retrier.rs @@ -10,7 +10,7 @@ use backoff::{Error, ExponentialBackoff}; use teos_common::appointment::Locator; use teos_common::cryptography; use teos_common::errors; -use teos_common::UserId as TowerId; +use teos_common::TowerId; use crate::net::http::{self, AddAppointmentError}; use crate::wt_client::{RevocationData, WTClient}; @@ -574,6 +574,7 @@ impl Retrier { mod tests { use super::*; + use bitcoin::secp256k1::{PublicKey, Secp256k1}; use serde_json::json; use tempdir::TempDir; use tokio::sync::mpsc::unbounded_channel; @@ -583,9 +584,10 @@ mod tests { 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, + generate_random_appointment, get_random_registration_receipt, get_registration_receipt_from_previous, }; + use teos_common::UserId; use crate::net::http::ApiError; use crate::test_utils::get_dummy_add_appointment_response; @@ -623,12 +625,19 @@ mod tests { #[tokio::test] async fn test_manage_retry_reachable() { - let tmp_path = TempDir::new(&format!("watchtower_{}", get_random_user_id())).unwrap(); let (tx, rx) = unbounded_channel(); + let keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let wt_client = Arc::new(Mutex::new( - WTClient::new(tmp_path.path().to_path_buf(), tx.clone()).await, + WTClient::new( + tmp_path.path().to_path_buf(), + keypair.0, + unbounded_channel().0, + ) + .await, )); - let mut server = mockito::Server::new_async().await; // Add a tower with pending appointments @@ -717,10 +726,18 @@ mod tests { #[tokio::test] async fn test_manage_retry_unreachable() { - let tmp_path = TempDir::new(&format!("watchtower_{}", get_random_user_id())).unwrap(); let (tx, rx) = unbounded_channel(); + let keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let wt_client = Arc::new(Mutex::new( - WTClient::new(tmp_path.path().to_path_buf(), tx.clone()).await, + WTClient::new( + tmp_path.path().to_path_buf(), + keypair.0, + unbounded_channel().0, + ) + .await, )); // Add a tower with pending appointments @@ -843,10 +860,18 @@ mod tests { #[tokio::test] async fn test_manage_retry_rejected() { - let tmp_path = TempDir::new(&format!("watchtower_{}", get_random_user_id())).unwrap(); let (tx, rx) = unbounded_channel(); + let keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let wt_client = Arc::new(Mutex::new( - WTClient::new(tmp_path.path().to_path_buf(), tx.clone()).await, + WTClient::new( + tmp_path.path().to_path_buf(), + keypair.0, + unbounded_channel().0, + ) + .await, )); let mut server = mockito::Server::new_async().await; @@ -946,10 +971,18 @@ mod tests { #[tokio::test] async fn test_manage_retry_misbehaving() { - let tmp_path = TempDir::new(&format!("watchtower_{}", get_random_user_id())).unwrap(); let (tx, rx) = unbounded_channel(); + let keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let wt_client = Arc::new(Mutex::new( - WTClient::new(tmp_path.path().to_path_buf(), tx.clone()).await, + WTClient::new( + tmp_path.path().to_path_buf(), + keypair.0, + unbounded_channel().0, + ) + .await, )); let mut server = mockito::Server::new_async().await; @@ -1037,10 +1070,18 @@ mod tests { #[tokio::test] async fn test_manage_retry_abandoned() { - let tmp_path = TempDir::new(&format!("watchtower_{}", get_random_user_id())).unwrap(); + let keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", 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, + WTClient::new( + tmp_path.path().to_path_buf(), + keypair.0, + unbounded_channel().0, + ) + .await, )); let server = mockito::Server::new_async().await; @@ -1079,10 +1120,18 @@ mod tests { #[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 keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let wt_client = Arc::new(Mutex::new( - WTClient::new(tmp_path.path().to_path_buf(), tx.clone()).await, + WTClient::new( + tmp_path.path().to_path_buf(), + keypair.0, + unbounded_channel().0, + ) + .await, )); let mut server = mockito::Server::new_async().await; @@ -1187,166 +1236,184 @@ mod tests { task.abort(); } - // #[tokio::test] - // async fn test_manage_retry_while_idle() { - // use crate::storage::Storage; - // // 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 storage = Storage::new().unwrap(); - // let receipt = get_random_registration_receipt(); - // storage.store_tower_record(tower_id, "http://unreachable.tower", &receipt) - // .unwrap(); - // - // let appointment = generate_random_appointment(None); - // storage.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_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()); - // - // 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 appointment2 = generate_random_appointment(None); - // wt_client - // .lock() - // .unwrap() - // .add_pending_appointment(tower_id, &appointment2); - // tx.send((tower_id, RevocationData::Fresh(appointment2.locator))) - // .unwrap(); - // - // { - // 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(); - // assert_eq!(tower.status, TowerStatus::Unreachable); - // } - // - // // 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 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 - // .lock() - // .unwrap() - // .towers - // .get_mut(&tower_id) - // .unwrap() - // .set_net_addr(server.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(); - // - // // 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; - // 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; - // - // task.abort(); - // } + #[tokio::test] + async fn test_manage_retry_while_idle() { + use crate::storage::Storage; + // 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 keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", 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 storage = Storage::new(&tmp_path.path().join("watchtower.db")).unwrap(); + let receipt = get_random_registration_receipt(); + storage + .store_tower_record(tower_id, "http://unreachable.tower", &receipt) + .unwrap(); + + let appointment = generate_random_appointment(None); + storage + .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(), + keypair.0, + unbounded_channel().0, + ) + .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_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()); + + 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 appointment2 = generate_random_appointment(None); + wt_client + .lock() + .unwrap() + .add_pending_appointment(tower_id, &appointment2); + tx.send((tower_id, RevocationData::Fresh(appointment2.locator))) + .unwrap(); + + { + 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(); + assert_eq!(tower.status, TowerStatus::Unreachable); + } + + // 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 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 + .lock() + .unwrap() + .towers + .get_mut(&tower_id) + .unwrap() + .set_net_addr(server.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(); + + // 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; + 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; + + task.abort(); + } #[tokio::test] 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(); + let keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let wt_client = Arc::new(Mutex::new( - WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await, + WTClient::new( + tmp_path.path().to_path_buf(), + keypair.0, + unbounded_channel().0, + ) + .await, )); + let mut server = mockito::Server::new_async().await; // The tower we'd like to retry sending appointments to has to exist within the plugin @@ -1391,9 +1458,17 @@ mod tests { 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(); + let keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let wt_client = Arc::new(Mutex::new( - WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await, + WTClient::new( + tmp_path.path().to_path_buf(), + keypair.0, + unbounded_channel().0, + ) + .await, )); let server = mockito::Server::new_async().await; @@ -1414,9 +1489,17 @@ mod tests { 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(); + let keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let wt_client = Arc::new(Mutex::new( - WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await, + WTClient::new( + tmp_path.path().to_path_buf(), + keypair.0, + unbounded_channel().0, + ) + .await, )); let mut server = mockito::Server::new_async().await; @@ -1465,9 +1548,17 @@ mod tests { 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(); + let keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let wt_client = Arc::new(Mutex::new( - WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await, + WTClient::new( + tmp_path.path().to_path_buf(), + keypair.0, + unbounded_channel().0, + ) + .await, )); // The tower we'd like to retry sending appointments to has to exist within the plugin @@ -1496,10 +1587,19 @@ mod tests { 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(); + let keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let wt_client = Arc::new(Mutex::new( - WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await, + WTClient::new( + tmp_path.path().to_path_buf(), + keypair.0, + unbounded_channel().0, + ) + .await, )); + let mut server = mockito::Server::new_async().await; // The tower we'd like to retry sending appointments to has to exist within the plugin @@ -1549,10 +1649,19 @@ mod tests { 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(); + let keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let wt_client = Arc::new(Mutex::new( - WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await, + WTClient::new( + tmp_path.path().to_path_buf(), + keypair.0, + unbounded_channel().0, + ) + .await, )); + let mut server = mockito::Server::new_async().await; // The tower we'd like to retry sending appointments to has to exist within the plugin @@ -1608,9 +1717,17 @@ mod tests { 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(); + let keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let wt_client = Arc::new(Mutex::new( - WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await, + WTClient::new( + tmp_path.path().to_path_buf(), + keypair.0, + unbounded_channel().0, + ) + .await, )); // The tower we'd like to retry sending appointments to has to exist within the plugin diff --git a/teos-ldk-client/src/storage/dbm.rs b/teos-ldk-client/src/storage/dbm.rs new file mode 100644 index 00000000..14a6a6cd --- /dev/null +++ b/teos-ldk-client/src/storage/dbm.rs @@ -0,0 +1,1298 @@ +#[cfg(feature = "sqlite")] +use std::collections::{HashMap, HashSet}; +use std::iter::FromIterator; +use std::path::PathBuf; + +use rusqlite::{params, Connection, Error as SqliteError}; + +use teos_common::appointment::{Appointment, Locator}; +use teos_common::dbm::{DatabaseConnection, DatabaseManager, Error}; +use teos_common::receipts::{AppointmentReceipt, RegistrationReceipt}; +use teos_common::{TowerId, UserId}; + +use crate::{AppointmentStatus, MisbehaviorProof, TowerInfo, TowerStatus, TowerSummary}; +pub use teos_common::dbm::Error as DBError; + +const TABLES: [&str; 8] = [ + "CREATE TABLE IF NOT EXISTS towers ( + tower_id INT PRIMARY KEY, + net_addr TEXT NOT NULL, + available_slots INT NOT NULL +)", + "CREATE TABLE IF NOT EXISTS appointments ( + locator INT PRIMARY KEY, + encrypted_blob BLOB, + to_self_delay INT +)", + "CREATE TABLE IF NOT EXISTS pending_appointments ( + locator INT NOT NULL, + tower_id INT NOT NULL, + PRIMARY KEY (locator, tower_id), + FOREIGN KEY(locator) + REFERENCES appointments(locator) + ON DELETE CASCADE + FOREIGN KEY(tower_id) + REFERENCES towers(tower_id) + ON DELETE CASCADE +)", + "CREATE TABLE IF NOT EXISTS invalid_appointments ( + locator INT NOT NULL, + tower_id INT NOT NULL, + PRIMARY KEY (locator, tower_id), + FOREIGN KEY(locator) + REFERENCES appointments(locator) + ON DELETE CASCADE + FOREIGN KEY(tower_id) + REFERENCES towers(tower_id) + ON DELETE CASCADE +)", + "CREATE TABLE IF NOT EXISTS registration_receipts ( + tower_id INT NOT NULL, + available_slots INT NOT NULL, + subscription_start INT NOT NULL, + subscription_expiry INT NOT NULL, + signature BLOB NOT NULL, + PRIMARY KEY (tower_id, subscription_expiry), + FOREIGN KEY(tower_id) + REFERENCES towers(tower_id) + ON DELETE CASCADE +)", + "CREATE TABLE IF NOT EXISTS appointment_receipts ( + locator INT NOT NULL, + tower_id INT NOT NULL, + start_block INT NOT NULL, + user_signature BLOB NOT NULL, + tower_signature BLOB NOT NULL, + PRIMARY KEY (locator, tower_id), + FOREIGN KEY(tower_id) + REFERENCES towers(tower_id) + ON DELETE CASCADE +)", + "CREATE TABLE IF NOT EXISTS misbehaving_proofs ( + tower_id INT PRIMARY KEY, + locator INT NOT NULL, + recovered_id INT NOT NULL, + FOREIGN KEY(locator, tower_id) + REFERENCES appointment_receipts(locator, tower_id) + ON DELETE CASCADE +)", + "CREATE TABLE IF NOT EXISTS keys ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + key INT NOT NULL +)", +]; + +/// Component in charge of interacting with the underlying database. +/// +/// Currently works for `SQLite`. `PostgreSQL` should also be added in the future. +#[derive(Debug)] +pub struct DBM { + /// The underlying database connection. + connection: Connection, +} + +impl DatabaseConnection for DBM { + fn get_connection(&self) -> &Connection { + &self.connection + } + + fn get_mut_connection(&mut self) -> &mut Connection { + &mut self.connection + } +} + +impl DBM { + /// Creates a new [DBM] instance. + pub fn new(db_path: &PathBuf) -> Result { + let connection = Connection::open(db_path)?; + connection.execute("PRAGMA foreign_keys=1;", [])?; + let mut dbm = Self { connection }; + dbm.create_tables(Vec::from_iter(TABLES))?; + + Ok(dbm) + } + + /// Stores a tower record into the database alongside the corresponding registration receipt. + /// + /// This function MUST be guarded against inserting duplicate (tower_id, subscription_expiry) pairs. + /// This is currently done in WTClient::add_update_tower. + pub fn store_tower_record( + &mut self, + tower_id: TowerId, + net_addr: &str, + receipt: &RegistrationReceipt, + ) -> Result<(), Error> { + let tx = self.get_mut_connection().transaction().unwrap(); + tx.execute( + "INSERT INTO towers (tower_id, net_addr, available_slots) + VALUES (?1, ?2, ?3) + ON CONFLICT (tower_id) DO UPDATE SET net_addr = ?2, available_slots = ?3", + params![tower_id.to_vec(), net_addr, receipt.available_slots()], + ) + .map_err(Error::Unknown)?; + tx.execute( + "INSERT INTO registration_receipts (tower_id, available_slots, subscription_start, subscription_expiry, signature) + VALUES (?1, ?2, ?3, ?4, ?5)", + params![tower_id.to_vec(), receipt.available_slots(), receipt.subscription_start(), receipt.subscription_expiry(), receipt.signature()]).map_err( Error::Unknown)?; + + tx.commit().map_err(Error::Unknown) + } + + /// Loads a tower record from the database. + /// + /// 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) -> Option { + let mut stmt = self + .connection + .prepare("SELECT t.net_addr, t.available_slots, r.subscription_start, r.subscription_expiry + FROM towers as t, registration_receipts as r + WHERE t.tower_id = r.tower_id AND t.tower_id = ?1 AND r.subscription_expiry = (SELECT MAX(subscription_expiry) + FROM registration_receipts + WHERE tower_id = ?1)") + .unwrap(); + + let mut tower = stmt + .query_row([tower_id.to_vec()], |row| { + let net_addr: String = row.get(0).unwrap(); + let available_slots: u32 = row.get(1).unwrap(); + let subscription_start: u32 = row.get(2).unwrap(); + let subscription_expiry: u32 = row.get(3).unwrap(); + Ok(TowerInfo::new( + net_addr, + available_slots, + subscription_start, + subscription_expiry, + self.load_appointment_receipts(tower_id), + self.load_appointments(tower_id, AppointmentStatus::Pending), + self.load_appointments(tower_id, AppointmentStatus::Invalid), + )) + }) + .ok()?; + + 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; + } + + Some(tower) + } + + /// Loads the latest registration receipt for a given tower. + /// + /// Latests is determined by the one with the `subscription_expiry` further into the future. + pub fn load_registration_receipt( + &self, + tower_id: TowerId, + user_id: UserId, + ) -> Option { + let mut stmt = self + .connection + .prepare( + "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 + WHERE tower_id = ?1)", + ) + .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, + )) + }) + .ok() + } + + /// 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(); + let mut stmt = self + .connection + .prepare("SELECT tw.tower_id, tw.net_addr, tw.available_slots, rr.subscription_start, rr.subscription_expiry + FROM towers AS tw + JOIN registration_receipts AS rr + JOIN (SELECT tower_id, MAX(subscription_expiry) AS max_se + FROM registration_receipts + GROUP BY tower_id) AS max_rrs ON (tw.tower_id = rr.tower_id) + AND (rr.tower_id = max_rrs.tower_id) + AND (rr.subscription_expiry = max_rrs.max_se)") + .unwrap(); + let mut rows = stmt.query([]).unwrap(); + + while let Ok(Some(row)) = rows.next() { + let raw_towerid: Vec = row.get(0).unwrap(); + let tower_id = TowerId::from_slice(&raw_towerid).unwrap(); + let net_addr: String = row.get(1).unwrap(); + let available_slots: u32 = row.get(2).unwrap(); + let start: u32 = row.get(3).unwrap(); + let expiry: u32 = row.get(4).unwrap(); + + let mut tower = TowerSummary::with_appointments( + net_addr, + available_slots, + start, + expiry, + self.load_appointment_locators(tower_id, AppointmentStatus::Pending), + self.load_appointment_locators(tower_id, AppointmentStatus::Invalid), + ); + + 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; + } + + towers.insert(tower_id, tower); + } + + towers + } + + /// Stores an appointments receipt into the database representing an appointment accepted by a given tower. + pub fn store_appointment_receipt( + &mut self, + tower_id: TowerId, + locator: Locator, + available_slots: u32, + receipt: &AppointmentReceipt, + ) -> Result<(), SqliteError> { + let tx = self.get_mut_connection().transaction().unwrap(); + tx.execute( + "INSERT INTO appointment_receipts (locator, tower_id, start_block, user_signature, tower_signature) + VALUES (?1, ?2, ?3, ?4, ?5)", + params![ + locator.to_vec(), + tower_id.to_vec(), + receipt.start_block(), + receipt.user_signature(), + receipt.signature() + ], + )?; + tx.execute( + "UPDATE towers SET available_slots=?1 WHERE tower_id=?2", + params![available_slots, tower_id.to_vec()], + )?; + tx.commit() + } + + /// Loads a given appointment receipt of a given tower from the database. + pub fn load_appointment_receipt( + &self, + tower_id: TowerId, + locator: Locator, + ) -> Option { + let mut stmt = self + .connection + .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>(0).unwrap(); + let user_sig = row.get::<_, String>(1).unwrap(); + let tower_sig = row.get::<_, String>(2).unwrap(); + + Ok(AppointmentReceipt::with_signature( + user_sig, + start_block, + tower_sig, + )) + }) + .ok() + } + + /// 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. + pub fn load_appointment_receipts(&self, tower_id: TowerId) -> HashMap { + let mut receipts = HashMap::new(); + let mut stmt = self + .connection + .prepare("SELECT locator, tower_signature FROM appointment_receipts WHERE tower_id = ?") + .unwrap(); + let mut rows = stmt.query([tower_id.to_vec()]).unwrap(); + + while let Ok(Some(row)) = rows.next() { + let locator = Locator::from_slice(&row.get::<_, Vec>(0).unwrap()).unwrap(); + let signature = row.get::<_, String>(1).unwrap(); + + receipts.insert(locator, signature); + } + + receipts + } + + /// Loads a collection of locators from the database entry associated to a given tower. + /// + /// The loaded locators can be loaded either from appointment_receipts, pending_appointments or invalid_appointments + /// depending on `status`. + pub fn load_appointment_locators( + &self, + tower_id: TowerId, + status: AppointmentStatus, + ) -> HashSet { + let status = match status { + AppointmentStatus::Accepted => "appointment_receipts", + AppointmentStatus::Pending => "pending_appointments", + AppointmentStatus::Invalid => "invalid_appointments", + }; + let mut appointments = HashSet::new(); + // TODO: Can this be prepared instead of formatted (using ?1 seems to fail)? + let mut stmt = self + .connection + .prepare(&format!("SELECT locator FROM {status} WHERE tower_id = ?")) + .unwrap(); + + let mut rows = stmt.query(params![tower_id.to_vec()]).unwrap(); + while let Ok(Some(inner_row)) = rows.next() { + appointments + .insert(Locator::from_slice(&inner_row.get::<_, Vec>(0).unwrap()).unwrap()); + } + + appointments + } + + /// Loads an appointment from the database. + pub fn load_appointment(&self, locator: Locator) -> Option { + 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)) + }) + .ok() + } + + /// Stores an appointment into the database. + /// + /// Appointments are only stored as a whole when they are pending or invalid. + /// Accepted appointments are simplified in the form of an appointment receipt. + fn store_appointment( + tx: &rusqlite::Transaction, + appointment: &Appointment, + ) -> Result { + tx.execute( + "INSERT INTO appointments (locator, encrypted_blob, to_self_delay) VALUES (?1, ?2, ?3)", + params![ + appointment.locator.to_vec(), + appointment.encrypted_blob, + appointment.to_self_delay + ], + ) + } + + /// Stores a pending appointment into the database. + /// + /// A pending appointment is an appointment that was sent to a tower when it was unreachable. + /// This data is stored so it can be resent once the tower comes back online. + /// Internally calls [Self::store_appointment]. + pub fn store_pending_appointment( + &mut self, + tower_id: TowerId, + appointment: &Appointment, + ) -> Result<(), SqliteError> { + let tx = self.get_mut_connection().transaction().unwrap(); + + // If the appointment already exists (because it was added by another tower as either pending or invalid) we simply + // ignore the error. + Self::store_appointment(&tx, appointment).ok(); + tx.execute( + "INSERT INTO pending_appointments (locator, tower_id) VALUES (?1, ?2)", + params![appointment.locator.to_vec(), tower_id.to_vec(),], + )?; + + tx.commit() + } + + /// Removes a pending appointment from the database. + /// + /// If the pending appointment is the only instance of the appointment, the appointment will also be deleted form the appointments table. + pub fn delete_pending_appointment( + &mut self, + tower_id: TowerId, + locator: Locator, + ) -> Result<(), SqliteError> { + // We will delete data from pending_appointments or from appointments depending on whether the later has a single reference + // to it or not. If that's the case, deleting the entry from appointments will trigger a cascade deletion of the entry in pending. + // If there are other references, this will be deleted when removing the last one. + let count = { + let mut stmt = self + .connection + .prepare("SELECT COUNT(*) FROM pending_appointments WHERE locator=?") + .unwrap(); + let pending = stmt + .query_row(params![locator.to_vec()], |row| row.get::<_, u32>(0)) + .unwrap(); + + let mut stmt = self + .connection + .prepare("SELECT COUNT(*) FROM invalid_appointments WHERE locator=?") + .unwrap(); + let invalid = stmt + .query_row(params![locator.to_vec()], |row| row.get::<_, u32>(0)) + .unwrap_or(0); + + pending + invalid + }; + + let tx = self.get_mut_connection().transaction().unwrap(); + if count == 1 { + tx.execute( + "DELETE FROM appointments WHERE locator=?", + params![locator.to_vec()], + )?; + } else { + tx.execute( + "DELETE FROM pending_appointments WHERE locator=?1 AND tower_id=?2", + params![locator.to_vec(), tower_id.to_vec()], + )?; + }; + tx.commit() + } + + /// Stores an invalid appointment into the database. + /// + /// An invalid appointment is an appointment that was rejected by the tower. + /// Storing this data may allow us to see what was the issue and send the data later on. + /// Internally calls [Self::store_appointment]. + pub fn store_invalid_appointment( + &mut self, + tower_id: TowerId, + appointment: &Appointment, + ) -> Result<(), SqliteError> { + let tx = self.get_mut_connection().transaction().unwrap(); + + // If the appointment already exists (because it was added by another tower as either pending or invalid) we simply + // ignore the error. + Self::store_appointment(&tx, appointment).ok(); + tx.execute( + "INSERT INTO invalid_appointments (locator, tower_id) VALUES (?1, ?2)", + params![appointment.locator.to_vec(), tower_id.to_vec(),], + )?; + + tx.commit() + } + + /// Loads non finalized appointments from the database for a given tower based on a status flag. + /// + /// This is meant to be used only for pending and invalid appointments, if the method is called for + /// accepted appointment, an empty collection will be returned. + pub fn load_appointments( + &self, + tower_id: TowerId, + status: AppointmentStatus, + ) -> Vec { + let table = match status { + AppointmentStatus::Accepted => return Vec::new(), + AppointmentStatus::Pending => "pending_appointments", + AppointmentStatus::Invalid => "invalid_appointments", + }; + + 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, {table} as t WHERE a.locator = t.locator AND t.tower_id = ?")) + .unwrap(); + let mut rows = stmt.query([tower_id.to_vec()]).unwrap(); + + while let Ok(Some(row)) = rows.next() { + let locator = Locator::from_slice(&row.get::<_, Vec>(0).unwrap()).unwrap(); + let encrypted_blob = row.get::<_, Vec>(1).unwrap(); + let to_self_delay = row.get::<_, u32>(2).unwrap(); + + appointments.push(Appointment::new(locator, encrypted_blob, to_self_delay)); + } + + appointments + } + + /// Stores a misbehaving proof into the database. + /// + /// A misbehaving proof is proof that the tower has signed an appointment using a key different + /// than the one advertised to the user when they registered. + pub fn store_misbehaving_proof( + &mut self, + tower_id: TowerId, + proof: &MisbehaviorProof, + ) -> Result<(), SqliteError> { + let tx = self.get_mut_connection().transaction().unwrap(); + tx.execute( + "INSERT INTO appointment_receipts (tower_id, locator, start_block, user_signature, tower_signature) + VALUES (?1, ?2, ?3, ?4, ?5)", + params![ + tower_id.to_vec(), + proof.locator.to_vec(), + proof.appointment_receipt.start_block(), + proof.appointment_receipt.user_signature(), + proof.appointment_receipt.signature() + ], + )?; + tx.execute( + "INSERT INTO misbehaving_proofs (tower_id, locator, recovered_id) VALUES (?1, ?2, ?3)", + params![ + tower_id.to_vec(), + proof.locator.to_vec(), + proof.recovered_id.to_vec() + ], + )?; + + tx.commit() + } + + /// Loads the misbehaving proof for a given tower from the database (if found). + 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 = ?") + .unwrap(); + + misbehaving_stmt + .query_row([tower_id.to_vec()], |row| { + let locator = Locator::from_slice(&row.get::<_, Vec>(0).unwrap()).unwrap(); + let recovered_id = TowerId::from_slice(&row.get::<_, Vec>(1).unwrap()).unwrap(); + Ok((locator, recovered_id)) + }) + .map(|(locator, recovered_id)| { + let mut receipt_stmt = self + .connection + .prepare( + "SELECT start_block, user_signature, tower_signature + FROM appointment_receipts + WHERE locator = ?1 AND tower_id = ?2", + ) + .unwrap(); + let receipt = receipt_stmt + .query_row([locator.to_vec(), tower_id.to_vec()], |row| { + let start_block = row.get::<_, u32>(0).unwrap(); + let user_signature = row.get::<_, String>(1).unwrap(); + let tower_signature = row.get::<_, String>(2).unwrap(); + Ok(AppointmentReceipt::with_signature( + user_signature, + start_block, + tower_signature, + )) + }) + .unwrap(); + MisbehaviorProof::new(locator, receipt, recovered_id) + }) + .ok() + } + + /// Checks whether a misbehaving proof exists for a given tower. + fn exists_misbehaving_proof(&self, tower_id: TowerId) -> bool { + let mut misbehaving_stmt = self + .connection + .prepare("SELECT tower_id FROM misbehaving_proofs WHERE tower_id = ?") + .unwrap(); + misbehaving_stmt.exists([tower_id.to_vec()]).unwrap() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use teos_common::test_utils::{ + generate_random_appointment, get_random_registration_receipt, get_random_user_id, + get_registration_receipt_from_previous, + }; + + impl DBM { + pub(crate) fn in_memory() -> Result { + let connection = Connection::open_in_memory()?; + connection.execute("PRAGMA foreign_keys=1;", [])?; + let mut dbm = Self { connection }; + dbm.create_tables(Vec::from_iter(TABLES))?; + + Ok(dbm) + } + + pub(crate) fn appointment_exists(&self, locator: Locator) -> bool { + let mut stmt = self + .connection + .prepare("SELECT * FROM appointments WHERE locator=? ") + .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] + fn test_create_tables() { + let connection = Connection::open_in_memory().unwrap(); + let mut dbm = DBM { connection }; + dbm.create_tables(Vec::from_iter(TABLES)).unwrap(); + } + + #[test] + fn test_store_load_tower_record() { + let mut dbm = DBM::in_memory().unwrap(); + + // In order to add a tower record we need to associated registration receipt. + let tower_id = get_random_user_id(); + let net_addr = "talaia.watch"; + + let receipt = get_random_registration_receipt(); + let tower_info = TowerInfo::new( + net_addr.to_owned(), + receipt.available_slots(), + receipt.subscription_start(), + receipt.subscription_expiry(), + HashMap::new(), + Vec::new(), + Vec::new(), + ); + + // Check the loaded data matches the in memory data + dbm.store_tower_record(tower_id, net_addr, &receipt) + .unwrap(); + assert_eq!(dbm.load_tower_record(tower_id).unwrap(), tower_info); + } + + #[test] + fn test_load_registration_receipt() { + let mut dbm = DBM::in_memory().unwrap(); + + // Registration receipts are stored alongside tower records when the register command is called + let tower_id = get_random_user_id(); + let net_addr = "talaia.watch"; + let receipt = get_random_registration_receipt(); + + // Check the receipt was stored + dbm.store_tower_record(tower_id, net_addr, &receipt) + .unwrap(); + assert_eq!( + dbm.load_registration_receipt(tower_id, receipt.user_id()) + .unwrap(), + receipt + ); + + // Add another receipt for the same tower with a higher expiry and check this last one is loaded + let middle_receipt = get_registration_receipt_from_previous(&receipt); + let latest_receipt = get_registration_receipt_from_previous(&middle_receipt); + + dbm.store_tower_record(tower_id, net_addr, &latest_receipt) + .unwrap(); + assert_eq!( + dbm.load_registration_receipt(tower_id, latest_receipt.user_id()) + .unwrap(), + latest_receipt + ); + + // Add a final one with a lower expiry and check the last is still loaded + dbm.store_tower_record(tower_id, net_addr, &middle_receipt) + .unwrap(); + assert_eq!( + dbm.load_registration_receipt(tower_id, latest_receipt.user_id()) + .unwrap(), + latest_receipt + ); + } + + #[test] + fn test_load_same_registration_receipt() { + let mut dbm = DBM::in_memory().unwrap(); + + // Registration receipts are stored alongside tower records when the register command is called + let tower_id = get_random_user_id(); + let net_addr = "talaia.watch"; + let receipt = get_random_registration_receipt(); + + // Store it once + dbm.store_tower_record(tower_id, net_addr, &receipt) + .unwrap(); + assert_eq!( + dbm.load_registration_receipt(tower_id, receipt.user_id()) + .unwrap(), + receipt + ); + + // Store the same again, this should fail due to UNIQUE PK constrains. + // Notice store_tower_record is guarded against this by WTClient::add_update_tower though. + assert!(matches!( + dbm.store_tower_record(tower_id, net_addr, &receipt), + Err { .. } + )); + } + + #[test] + fn test_load_nonexistent_tower_record() { + let dbm = DBM::in_memory().unwrap(); + + // If the tower does not exists, `load_tower` will fail. + let tower_id = get_random_user_id(); + assert!(dbm.load_tower_record(tower_id).is_none()); + } + + #[test] + fn test_store_load_towers() { + let mut dbm = DBM::in_memory().unwrap(); + let mut towers = HashMap::new(); + + // In order to add a tower record we need to associated registration receipt. + for _ in 0..10 { + let tower_id = get_random_user_id(); + let net_addr = "talaia.watch"; + let mut receipt = get_random_registration_receipt(); + dbm.store_tower_record(tower_id, net_addr, &receipt) + .unwrap(); + + // Add not only one registration receipt to test if the tower retrieves the one with furthest expiry date. + for _ in 0..10 { + receipt = get_registration_receipt_from_previous(&receipt); + dbm.store_tower_record(tower_id, net_addr, &receipt) + .unwrap(); + } + + towers.insert( + tower_id, + TowerSummary::new( + net_addr.to_owned(), + receipt.available_slots(), + receipt.subscription_start(), + receipt.subscription_expiry(), + ), + ); + } + + assert_eq!(dbm.load_towers(), towers); + } + + #[test] + fn test_load_towers_empty() { + // If there are no towers in the database, `load_towers` should return an empty map. + let dbm = DBM::in_memory().unwrap(); + 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(); + + // In order to add a tower record we need to associated registration receipt. + let tower_id = get_random_user_id(); + let net_addr = "talaia.watch"; + + let receipt = get_random_registration_receipt(); + let mut tower_summary = TowerSummary::new( + net_addr.to_owned(), + receipt.available_slots(), + receipt.subscription_start(), + receipt.subscription_expiry(), + ); + dbm.store_tower_record(tower_id, net_addr, &receipt) + .unwrap(); + + // Add some appointment receipts and check they match + let mut receipts = HashMap::new(); + for _ in 0..5 { + let appointment = generate_random_appointment(None); + let user_signature = "user_signature"; + let appointment_receipt = AppointmentReceipt::with_signature( + user_signature.to_owned(), + 42, + "tower_signature".to_owned(), + ); + + tower_summary.available_slots -= 1; + + dbm.store_appointment_receipt( + tower_id, + appointment.locator, + tower_summary.available_slots, + &appointment_receipt, + ) + .unwrap(); + receipts.insert( + appointment.locator, + appointment_receipt.signature().unwrap(), + ); + } + + 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!(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"; + let receipt = get_random_registration_receipt(); + dbm.store_tower_record(tower_id, net_addr, &receipt) + .unwrap(); + + assert!(dbm + .load_appointment_receipt(tower_id, appointment.locator) + .is_none()); + + // Add both + let tower_summary = TowerSummary::new( + net_addr.to_owned(), + receipt.available_slots(), + receipt.subscription_start(), + receipt.subscription_expiry(), + ); + let appointment_receipt = AppointmentReceipt::with_signature( + "user_signature".to_owned(), + 42, + "tower_signature".to_owned(), + ); + 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` + let mut dbm = DBM::in_memory().unwrap(); + + // We first need to add a tower record to the database so we can add some associated data. + let tower_id = get_random_user_id(); + let net_addr = "talaia.watch"; + + let receipt = get_random_registration_receipt(); + let tower_summary = TowerSummary::new( + net_addr.to_owned(), + receipt.available_slots(), + receipt.subscription_start(), + receipt.subscription_expiry(), + ); + dbm.store_tower_record(tower_id, net_addr, &receipt) + .unwrap(); + + // Create all types of appointments and store them in the db. + let user_signature = "user_signature"; + let mut receipts = HashSet::new(); + let mut pending_appointments = HashSet::new(); + let mut invalid_appointments = HashSet::new(); + for _ in 0..5 { + let appointment = generate_random_appointment(None); + let appointment_receipt = AppointmentReceipt::with_signature( + user_signature.to_owned(), + 42, + "tower_signature".to_owned(), + ); + let pending_appointment = generate_random_appointment(None); + let invalid_appointment = generate_random_appointment(None); + + dbm.store_appointment_receipt( + tower_id, + appointment.locator, + tower_summary.available_slots, + &appointment_receipt, + ) + .unwrap(); + dbm.store_pending_appointment(tower_id, &pending_appointment) + .unwrap(); + dbm.store_invalid_appointment(tower_id, &invalid_appointment) + .unwrap(); + + receipts.insert(appointment.locator); + pending_appointments.insert(pending_appointment.locator); + invalid_appointments.insert(invalid_appointment.locator); + } + + // Pull data from the db and check it matches the expected data + assert_eq!( + dbm.load_appointment_locators(tower_id, AppointmentStatus::Accepted), + receipts + ); + assert_eq!( + dbm.load_appointment_locators(tower_id, AppointmentStatus::Pending), + pending_appointments + ); + assert_eq!( + dbm.load_appointment_locators(tower_id, AppointmentStatus::Invalid), + invalid_appointments + ); + } + + #[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!(loaded_appointment.is_none()); + } + + #[test] + fn test_store_pending_appointment() { + let mut dbm = DBM::in_memory().unwrap(); + + // In order to add a tower record we need to associated registration receipt. + let tower_id = get_random_user_id(); + let net_addr = "talaia.watch"; + + let receipt = get_random_registration_receipt(); + let mut tower_summary = TowerSummary::new( + net_addr.to_owned(), + receipt.available_slots(), + receipt.subscription_start(), + receipt.subscription_expiry(), + ) + .with_status(TowerStatus::TemporaryUnreachable); + dbm.store_tower_record(tower_id, net_addr, &receipt) + .unwrap(); + + // Add some pending appointments and check they match + for _ in 0..5 { + let appointment = generate_random_appointment(None); + + tower_summary + .pending_appointments + .insert(appointment.locator); + + dbm.store_pending_appointment(tower_id, &appointment) + .unwrap(); + assert_eq!( + TowerSummary::from(dbm.load_tower_record(tower_id).unwrap()), + tower_summary + ); + } + } + + #[test] + fn test_store_pending_appointment_twice() { + let mut dbm = DBM::in_memory().unwrap(); + + // In order to add a tower record we need to associated registration receipt. + let tower_id_1 = get_random_user_id(); + let tower_id_2 = get_random_user_id(); + let net_addr = "talaia.watch"; + + let receipt = get_random_registration_receipt(); + dbm.store_tower_record(tower_id_1, net_addr, &receipt) + .unwrap(); + dbm.store_tower_record(tower_id_2, net_addr, &receipt) + .unwrap(); + + // If the same appointment is stored twice (by different towers) it should go through + // Since the appointment data will be stored only once and this will create two references + let appointment = generate_random_appointment(None); + dbm.store_pending_appointment(tower_id_1, &appointment) + .unwrap(); + dbm.store_pending_appointment(tower_id_2, &appointment) + .unwrap(); + + // If this is called twice with for the same tower it will fail, since two identical references + // can not exist. This is intended behavior and should not happen + assert!(dbm + .store_pending_appointment(tower_id_2, &appointment) + .is_err()); + } + + #[test] + fn test_delete_pending_appointment() { + let mut dbm = DBM::in_memory().unwrap(); + + // In order to add a tower record we need to associated registration receipt. + 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(); + + // Add a single one, remove it later + let appointment = generate_random_appointment(None); + dbm.store_pending_appointment(tower_id, &appointment) + .unwrap(); + assert!(dbm + .delete_pending_appointment(tower_id, appointment.locator) + .is_ok()); + + // The appointment should be completely gone + assert!(!dbm + .load_appointment_locators(tower_id, AppointmentStatus::Pending) + .contains(&appointment.locator)); + assert!(!dbm.appointment_exists(appointment.locator)); + + // Try again with more than one reference + let another_tower_id = get_random_user_id(); + dbm.store_tower_record(another_tower_id, net_addr, &receipt) + .unwrap(); + + // Add two + dbm.store_pending_appointment(tower_id, &appointment) + .unwrap(); + dbm.store_pending_appointment(another_tower_id, &appointment) + .unwrap(); + // Delete one + assert!(dbm + .delete_pending_appointment(tower_id, appointment.locator) + .is_ok()); + // Check + assert!(!dbm + .load_appointment_locators(tower_id, AppointmentStatus::Pending) + .contains(&appointment.locator)); + assert!(dbm + .load_appointment_locators(another_tower_id, AppointmentStatus::Pending) + .contains(&appointment.locator)); + assert!(dbm.appointment_exists(appointment.locator)); + + // Add an invalid reference and check again + dbm.store_invalid_appointment(tower_id, &appointment) + .unwrap(); + assert!(dbm + .delete_pending_appointment(another_tower_id, appointment.locator) + .is_ok()); + assert!(!dbm + .load_appointment_locators(another_tower_id, AppointmentStatus::Pending) + .contains(&appointment.locator)); + assert!(dbm + .load_appointment_locators(tower_id, AppointmentStatus::Invalid) + .contains(&appointment.locator)); + assert!(dbm.appointment_exists(appointment.locator)); + } + + #[test] + fn test_store_invalid_appointment() { + let mut dbm = DBM::in_memory().unwrap(); + + // In order to add a tower record we need to associated registration receipt. + let tower_id = get_random_user_id(); + let net_addr = "talaia.watch"; + + let receipt = get_random_registration_receipt(); + let mut tower_summary = TowerSummary::new( + net_addr.to_owned(), + receipt.available_slots(), + receipt.subscription_start(), + receipt.subscription_expiry(), + ); + dbm.store_tower_record(tower_id, net_addr, &receipt) + .unwrap(); + + // Add some invalid appointments and check they match + for _ in 0..5 { + let appointment = generate_random_appointment(None); + + tower_summary + .invalid_appointments + .insert(appointment.locator); + + dbm.store_invalid_appointment(tower_id, &appointment) + .unwrap(); + assert_eq!( + TowerSummary::from(dbm.load_tower_record(tower_id).unwrap()), + tower_summary + ); + } + } + + #[test] + fn test_store_invalid_appointment_twice() { + let mut dbm = DBM::in_memory().unwrap(); + + // In order to add a tower record we need to associated registration receipt. + let tower_id_1 = get_random_user_id(); + let tower_id_2 = get_random_user_id(); + let net_addr = "talaia.watch"; + + let receipt = get_random_registration_receipt(); + dbm.store_tower_record(tower_id_1, net_addr, &receipt) + .unwrap(); + dbm.store_tower_record(tower_id_2, net_addr, &receipt) + .unwrap(); + + // Same as with pending appointments. Two references from different towers is allowed + let appointment = generate_random_appointment(None); + dbm.store_invalid_appointment(tower_id_1, &appointment) + .unwrap(); + dbm.store_invalid_appointment(tower_id_2, &appointment) + .unwrap(); + + // Two references from the same tower is not. + assert!(dbm + .store_invalid_appointment(tower_id_2, &appointment) + .is_err()); + } + + #[test] + fn test_store_load_misbehaving_proof() { + let mut dbm = DBM::in_memory().unwrap(); + + // In order to add a tower record we need to associated registration receipt. + let tower_id = get_random_user_id(); + let net_addr = "talaia.watch"; + + let receipt = get_random_registration_receipt(); + let tower_summary = TowerSummary::new( + net_addr.to_owned(), + receipt.available_slots(), + receipt.subscription_start(), + receipt.subscription_expiry(), + ); + dbm.store_tower_record(tower_id, net_addr, &receipt) + .unwrap(); + assert_eq!( + TowerSummary::from(dbm.load_tower_record(tower_id).unwrap()), + tower_summary + ); + + // Store a misbehaving proof and load it back + let appointment = generate_random_appointment(None); + let appointment_receipt = AppointmentReceipt::with_signature( + "user_signature".to_owned(), + 42, + "tower_signature".to_owned(), + ); + + let proof = MisbehaviorProof::new( + appointment.locator, + appointment_receipt, + get_random_user_id(), + ); + + dbm.store_misbehaving_proof(tower_id, &proof).unwrap(); + assert_eq!(dbm.load_misbehaving_proof(tower_id).unwrap(), proof); + } + + #[test] + fn test_store_load_non_existing_misbehaving_proof() { + let dbm = DBM::in_memory().unwrap(); + assert!(dbm.load_misbehaving_proof(get_random_user_id()).is_none()); + } + + #[test] + fn test_store_exists_misbehaving_proof() { + let mut dbm = DBM::in_memory().unwrap(); + + // In order to add a tower record we need to associated registration receipt. + let tower_id = get_random_user_id(); + let net_addr = "talaia.watch"; + + let receipt = get_random_registration_receipt(); + let tower_summary = TowerSummary::new( + net_addr.to_owned(), + receipt.available_slots(), + receipt.subscription_start(), + receipt.subscription_expiry(), + ); + dbm.store_tower_record(tower_id, net_addr, &receipt) + .unwrap(); + assert_eq!( + TowerSummary::from(dbm.load_tower_record(tower_id).unwrap()), + tower_summary + ); + + // // Store a misbehaving proof check + let appointment = generate_random_appointment(None); + let appointment_receipt = AppointmentReceipt::with_signature( + "user_signature".to_owned(), + 42, + "tower_signature".to_owned(), + ); + + let proof = MisbehaviorProof::new( + appointment.locator, + appointment_receipt, + get_random_user_id(), + ); + + dbm.store_misbehaving_proof(tower_id, &proof).unwrap(); + assert!(dbm.exists_misbehaving_proof(tower_id)); + } + + #[test] + fn test_exists_misbehaving_proof_false() { + let dbm = DBM::in_memory().unwrap(); + assert!(!dbm.exists_misbehaving_proof(get_random_user_id())); + } +} diff --git a/teos-ldk-client/src/storage.rs b/teos-ldk-client/src/storage/kv.rs similarity index 80% rename from teos-ldk-client/src/storage.rs rename to teos-ldk-client/src/storage/kv.rs index 176e928f..c6116145 100644 --- a/teos-ldk-client/src/storage.rs +++ b/teos-ldk-client/src/storage/kv.rs @@ -1,11 +1,11 @@ use std::collections::{HashMap, HashSet}; -use lightning::util::persist::KVStore; -use lightning::io::Error; use bitcoin::secp256k1::SecretKey; -use teos_common::{TowerId, UserId}; -use teos_common::receipts::{AppointmentReceipt, RegistrationReceipt}; +use lightning::io::Error; +use lightning::util::persist::KVStore; use teos_common::appointment::{Appointment, Locator}; +use teos_common::receipts::{AppointmentReceipt, RegistrationReceipt}; +use teos_common::{TowerId, UserId}; use crate::{AppointmentStatus, MisbehaviorProof, TowerInfo, TowerStatus, TowerSummary}; @@ -30,10 +30,7 @@ pub(crate) struct Storage { impl Storage { pub(crate) fn new(store: T, sk: String) -> Result { - Ok(Storage { - store, - sk - }) + Ok(Storage { store, sk }) } /// Creates a composite key from multiple components @@ -54,7 +51,12 @@ impl Storage { /// /// This function MUST be guarded against inserting duplicate (tower_id, subscription_expiry) pairs. /// This is currently done in WTClient::add_update_tower. - pub fn store_tower_record(&mut self, tower_id: TowerId, net_addr: &str, receipt: &RegistrationReceipt) -> Result<(), Error> { + pub fn store_tower_record( + &mut self, + tower_id: TowerId, + net_addr: &str, + receipt: &RegistrationReceipt, + ) -> Result<(), Error> { let key = Self::make_key(&[&tower_id.to_string()]); let value = TowerInfo::new( @@ -65,11 +67,14 @@ impl Storage { HashMap::new(), Vec::new(), Vec::new(), - ).to_vec().unwrap(); + ) + .to_vec() + .unwrap(); // TODO: encrypt - self.store.write(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key, &value) + self.store + .write(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key, &value) } /// Loads a tower record from the database. @@ -88,8 +93,10 @@ impl Storage { let mut tower_info = TowerInfo::from_slice(&value).unwrap(); tower_info.appointments = self.load_appointment_receipts(tower_id); - tower_info.pending_appointments = self.load_appointments(tower_id, AppointmentStatus::Pending); - tower_info.invalid_appointments = self.load_appointments(tower_id, AppointmentStatus::Invalid); + tower_info.pending_appointments = + self.load_appointments(tower_id, AppointmentStatus::Pending); + tower_info.invalid_appointments = + self.load_appointments(tower_id, AppointmentStatus::Invalid); Some(tower_info) } @@ -104,7 +111,8 @@ impl Storage { // secondary namespance: "tower_record" // key: // value: ? - self.store.remove(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key, true) + self.store + .remove(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key, true) } /// Loads all tower records from the database. @@ -115,29 +123,45 @@ impl Storage { /// Loads the latest registration receipt for a given tower. /// /// Latests is determined by the one with the `subscription_expiry` further into the future. - pub fn load_registration_receipt(&self, tower_id: TowerId, user_id: UserId) -> Option { + pub fn load_registration_receipt( + &self, + tower_id: TowerId, + user_id: UserId, + ) -> Option { let key = Self::make_key(&[&tower_id.to_string()]); - self.store.read(PRIMARY_NAMESPACE, NS_REGISTRATION_RECEIPTS, &key) + self.store + .read(PRIMARY_NAMESPACE, NS_REGISTRATION_RECEIPTS, &key) } - /// Stores an appointments receipt into the database representing an appointment accepted by a given tower. - pub fn store_appointment_receipt(&mut self, tower_id: TowerId, locator: Locator, available_slots: u32, receipt: &AppointmentReceipt) -> Result<(), Error> { + pub fn store_appointment_receipt( + &mut self, + tower_id: TowerId, + locator: Locator, + available_slots: u32, + receipt: &AppointmentReceipt, + ) -> Result<(), Error> { let key = Self::make_key(&[&tower_id.to_string(), &locator.to_string()]); // primary namespace: "watchtower" // secondary namespance: "appointment_receipt" // key: + // value: ? - self.store.write(PRIMARY_NAMESPACE, NS_APPOINTMENT_RECEIPTS, &key, &value) + self.store + .write(PRIMARY_NAMESPACE, NS_APPOINTMENT_RECEIPTS, &key, &value) } /// Loads a given appointment receipt of a given tower from the database. - pub fn load_appointment_receipt(&self, tower_id: TowerId, locator: Locator) -> Option { + pub fn load_appointment_receipt( + &self, + tower_id: TowerId, + locator: Locator, + ) -> Option { let key = Self::make_key(&[&tower_id.to_string(), &locator.to_string()]); // primary namespace: "watchtower" // secondary namespance: "appointment_receipt" - self.store.read(PRIMARY_NAMESPACE, NS_APPOINTMENT_RECEIPTS, &key) + self.store + .read(PRIMARY_NAMESPACE, NS_APPOINTMENT_RECEIPTS, &key) } /// Loads the appointment receipts associated to a given tower. @@ -155,7 +179,11 @@ impl Storage { /// /// The loaded locators can be loaded either from appointment_receipts, pending_appointments or invalid_appointments /// depending on `status`. - pub fn load_appointment_locators(&self, tower_id: TowerId, status: AppointmentStatus) -> HashSet { + pub fn load_appointment_locators( + &self, + tower_id: TowerId, + status: AppointmentStatus, + ) -> HashSet { // primary namespace: "watchtower" // secondary namespance: "{status}_appointment_receipt" todo!(); @@ -187,11 +215,15 @@ impl Storage { /// A pending appointment is an appointment that was sent to a tower when it was unreachable. /// This data is stored so it can be resent once the tower comes back online. /// Internally calls [Self::store_appointment]. - pub fn store_pending_appointment(&self, tower_id: TowerId, appointment: &Appointment) -> Result<(), Error> { + pub fn store_pending_appointment( + &self, + tower_id: TowerId, + appointment: &Appointment, + ) -> Result<(), Error> { let key = Self::make_key(&[&tower_id.to_string(), &appointment.locator.to_string()]); // primary namespace: "watchtower" // secondary namespance: "pending_appointment" - // key: + // key: // value: ? todo!(); } @@ -199,7 +231,11 @@ impl Storage { /// Removes a pending appointment from the database. /// /// If the pending appointment is the only instance of the appointment, the appointment will also be deleted form the appointments table. - pub fn delete_pending_appointment(&self, tower_id: TowerId, locator: Locator) -> Result<(), Error> { + pub fn delete_pending_appointment( + &self, + tower_id: TowerId, + locator: Locator, + ) -> Result<(), Error> { let key = Self::make_key(&[&tower_id.to_string(), &locator.to_string()]); // primary namespace: "watchtower" // secondary namespance: "pending_appointment" @@ -213,7 +249,11 @@ impl Storage { /// An invalid appointment is an appointment that was rejected by the tower. /// Storing this data may allow us to see what was the issue and send the data later on. /// Internally calls [Self::store_appointment]. - pub fn store_invalid_appointment(&mut self, tower_id: TowerId, appointment: &Appointment) -> Result<(), Error> { + pub fn store_invalid_appointment( + &mut self, + tower_id: TowerId, + appointment: &Appointment, + ) -> Result<(), Error> { let key = Self::make_key(&[&tower_id.to_string(), &appointment.locator.to_string()]); // primary namespace: "watchtower" // secondary namespance: "invalid_appointment" @@ -226,7 +266,11 @@ impl Storage { /// /// This is meant to be used only for pending and invalid appointments, if the method is called for /// accepted appointment, an empty collection will be returned. - pub fn load_appointments(&self, tower_id: TowerId, status: AppointmentStatus) -> Vec { + pub fn load_appointments( + &self, + tower_id: TowerId, + status: AppointmentStatus, + ) -> Vec { let key = Self::make_key(&[&tower_id.to_string()]); // primary namespace: "watchtower" // secondary namespance: "{status}_appointment_receipt" @@ -239,7 +283,11 @@ impl Storage { /// /// A misbehaving proof is proof that the tower has signed an appointment using a key different /// than the one advertised to the user when they registered. - pub fn store_misbehaving_proof(&self, tower_id: TowerId, proof: &MisbehaviorProof) -> Result<(), Error> { + pub fn store_misbehaving_proof( + &self, + tower_id: TowerId, + proof: &MisbehaviorProof, + ) -> Result<(), Error> { let key = Self::make_key(&[&tower_id.to_string()]); // primary namespace: "watchtower" // secondary namespance: "misbehaving_proof" diff --git a/teos-ldk-client/src/storage/mod.rs b/teos-ldk-client/src/storage/mod.rs new file mode 100644 index 00000000..d376d553 --- /dev/null +++ b/teos-ldk-client/src/storage/mod.rs @@ -0,0 +1,19 @@ +#[cfg(all(not(feature = "sqlite"), not(feature = "kv")))] +compile_error!( + "No storage backend enabled. Please enable one of the following features: sqlite, kv" +); + +#[cfg(all(feature = "sqlite", feature = "kv"))] +compile_error!("Only one of 'sqlite' or 'kv' features can be enabled"); + +#[cfg(feature = "sqlite")] +mod dbm; +#[cfg(feature = "sqlite")] +pub use dbm::DBError; +#[cfg(feature = "sqlite")] +pub use dbm::DBM as Storage; + +#[cfg(feature = "kv")] +mod kv; +#[cfg(feature = "kv")] +pub use kv::Storage; diff --git a/teos-ldk-client/src/wt_client.rs b/teos-ldk-client/src/wt_client.rs index abd9ae85..bca3c32a 100644 --- a/teos-ldk-client/src/wt_client.rs +++ b/teos-ldk-client/src/wt_client.rs @@ -12,7 +12,7 @@ use teos_common::receipts::{AppointmentReceipt, RegistrationReceipt}; use teos_common::{TowerId, UserId}; use crate::retrier::RetrierStatus; -use crate::storage::Storage; +use crate::storage::{DBError, Storage}; use crate::{MisbehaviorProof, SubscriptionError, TowerInfo, TowerStatus, TowerSummary}; #[derive(Eq, PartialEq)] @@ -58,7 +58,7 @@ impl std::fmt::Debug for RevocationData { /// Represents the watchtower client that is being used as the CoreLN plugin state. pub struct WTClient { /// A [Storage] instance. - pub storage: Storage, + pub storage: Storage, // should support both slqite or KVStorage? /// A collection of towers the client is registered to. pub towers: HashMap, /// Queue of unreachable towers. @@ -72,8 +72,10 @@ pub struct WTClient { } impl WTClient { + #[cfg(feature = "sqlite")] pub async fn new( data_dir: PathBuf, + user_sk: SecretKey, unreachable_towers: UnboundedSender<(TowerId, RevocationData)>, ) -> Self { // Create data dir if it does not exist @@ -82,19 +84,41 @@ impl WTClient { std::process::exit(1); }); - let storage = Storage::new().unwrap(); + let storage = Storage::new(&data_dir.join("watchtower.db")).unwrap(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &user_sk)); - let (user_sk, user_id) = if let Some(sk) = storage.load_client_key() { - ( - sk, - UserId(PublicKey::from_secret_key(&Secp256k1::new(), &sk)), - ) - } else { - log::info!("Watchtower client keys not found. Creating a fresh set"); - let (sk, pk) = cryptography::get_random_keypair(); - storage.store_client_key(&sk).unwrap(); - (sk, UserId(pk)) - }; + let towers = storage.load_towers(); + for (tower_id, tower) in towers.iter() { + if tower.status.is_temporary_unreachable() { + unreachable_towers + .send(( + *tower_id, + RevocationData::Stale(tower.pending_appointments.iter().cloned().collect()), + )) + .unwrap(); + } + } + + log::info!("Plugin watchtower client initialized. User id = {user_id}"); + + WTClient { + towers, + unreachable_towers, + retriers: HashMap::new(), + storage, + user_sk, + user_id, + } + } + + #[cfg(feature = "kv")] + pub async fn new( + store: T, + user_sk: SecretKey, + unreachable_towers: UnboundedSender<(TowerId, RevocationData)>, + ) -> Self { + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &user_sk)); + let storage = Storage::new(store, user_sk).unwrap(); let towers = storage.load_towers(); for (tower_id, tower) in towers.iter() { @@ -168,7 +192,8 @@ impl WTClient { /// Gets the latest registration receipt of a given tower. pub fn get_registration_receipt(&self, tower_id: TowerId) -> Option { - self.storage.load_registration_receipt(tower_id, self.user_id) + self.storage + .load_registration_receipt(tower_id, self.user_id) } /// Loads a tower record from the database. @@ -270,7 +295,9 @@ 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.storage.store_misbehaving_proof(tower_id, &proof).unwrap(); + self.storage + .store_misbehaving_proof(tower_id, &proof) + .unwrap(); tower.status = TowerStatus::Misbehaving; } else { log::error!("Cannot flag tower. Unknown tower_id: {tower_id}"); @@ -305,9 +332,15 @@ mod tests { #[tokio::test] async fn test_add_update_load_tower() { - 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 keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let mut wt_client = WTClient::new( + tmp_path.path().to_path_buf(), + keypair.0, + unbounded_channel().0, + ) + .await; // Adding a new tower will add a summary to towers and the full data to the let mut receipt = get_random_registration_receipt(); @@ -399,9 +432,15 @@ mod tests { #[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; + let keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let mut wt_client = WTClient::new( + tmp_path.path().to_path_buf(), + keypair.0, + unbounded_channel().0, + ) + .await; // If the tower is unknown, get_tower_status returns None let tower_id = get_random_user_id(); @@ -422,9 +461,15 @@ mod tests { #[tokio::test] async fn test_set_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; + let keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let mut wt_client = WTClient::new( + tmp_path.path().to_path_buf(), + keypair.0, + unbounded_channel().0, + ) + .await; // If the tower is unknown nothing will happen let unknown_tower = get_random_user_id(); @@ -452,9 +497,15 @@ mod tests { #[tokio::test] async fn test_add_appointment_receipt() { - 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 keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let mut wt_client = WTClient::new( + tmp_path.path().to_path_buf(), + keypair.0, + unbounded_channel().0, + ) + .await; let (tower_sk, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); @@ -502,9 +553,15 @@ mod tests { #[tokio::test] async fn test_add_pending_appointment() { - 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 keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let mut wt_client = WTClient::new( + tmp_path.path().to_path_buf(), + keypair.0, + unbounded_channel().0, + ) + .await; let tower_id = get_random_user_id(); @@ -545,9 +602,15 @@ mod tests { #[tokio::test] async fn test_remove_pending_appointment() { - 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 keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let mut wt_client = WTClient::new( + tmp_path.path().to_path_buf(), + keypair.0, + unbounded_channel().0, + ) + .await; let tower_id = get_random_user_id(); @@ -576,9 +639,15 @@ mod tests { #[tokio::test] async fn test_add_invalid_appointment() { - 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 keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let mut wt_client = WTClient::new( + tmp_path.path().to_path_buf(), + keypair.0, + unbounded_channel().0, + ) + .await; let tower_id = get_random_user_id(); @@ -615,9 +684,15 @@ mod tests { #[tokio::test] async fn test_move_pending_appointment_to_invalid() { - 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 keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let mut wt_client = WTClient::new( + tmp_path.path().to_path_buf(), + keypair.0, + unbounded_channel().0, + ) + .await; let tower_id = get_random_user_id(); @@ -659,9 +734,15 @@ mod tests { #[tokio::test] async fn test_move_pending_appointment_to_invalid_multiple_towers() { // Check that moving an appointment from pending to invalid can be done even if multiple towers have a reference to it - 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 keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let mut wt_client = WTClient::new( + tmp_path.path().to_path_buf(), + keypair.0, + unbounded_channel().0, + ) + .await; let tower_id = get_random_user_id(); let another_tower_id = get_random_user_id(); @@ -733,9 +814,15 @@ mod tests { #[tokio::test] async fn test_flag_misbehaving_tower() { - 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 keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let mut wt_client = WTClient::new( + tmp_path.path().to_path_buf(), + keypair.0, + unbounded_channel().0, + ) + .await; let (tower_sk, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); @@ -768,9 +855,15 @@ mod tests { #[tokio::test] async fn test_remove_tower() { - 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 keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let mut wt_client = WTClient::new( + tmp_path.path().to_path_buf(), + keypair.0, + unbounded_channel().0, + ) + .await; let receipt = get_random_registration_receipt(); let (tower_sk, tower_pk) = cryptography::get_random_keypair(); @@ -813,13 +906,17 @@ mod tests { registration_receipt.available_slots(), &appointment_receipt, ); - assert!(wt_client.storage.appointment_receipt_exists(locator, tower_id)); + assert!(wt_client + .storage + .appointment_receipt_exists(locator, tower_id)); // Remove and check both the tower and the appointment wt_client.remove_tower(tower_id).unwrap(); assert!(wt_client.load_tower_info(tower_id).is_none()); assert!(!wt_client.towers.contains_key(&tower_id)); - assert!(!wt_client.storage.appointment_receipt_exists(locator, tower_id)); + assert!(!wt_client + .storage + .appointment_receipt_exists(locator, tower_id)); } #[tokio::test] @@ -827,9 +924,15 @@ 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 = 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 keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let mut wt_client = WTClient::new( + tmp_path.path().to_path_buf(), + keypair.0, + unbounded_channel().0, + ) + .await; let receipt = get_random_registration_receipt(); let (tower1_sk, tower1_pk) = cryptography::get_random_keypair(); @@ -863,22 +966,36 @@ mod tests { ); // Check that the data exists in both towers - assert!(wt_client.storage.appointment_receipt_exists(locator, tower1_id)); - assert!(wt_client.storage.appointment_receipt_exists(locator, tower2_id)); + assert!(wt_client + .storage + .appointment_receipt_exists(locator, tower1_id)); + assert!(wt_client + .storage + .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!(wt_client.load_tower_info(tower1_id).is_none()); - assert!(!wt_client.storage.appointment_receipt_exists(locator, tower1_id)); - assert!(wt_client.storage.appointment_receipt_exists(locator, tower2_id)); + assert!(!wt_client + .storage + .appointment_receipt_exists(locator, tower1_id)); + assert!(wt_client + .storage + .appointment_receipt_exists(locator, tower2_id)); } #[tokio::test] async fn test_remove_inexistent_tower() { - 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 keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let mut wt_client = WTClient::new( + tmp_path.path().to_path_buf(), + keypair.0, + unbounded_channel().0, + ) + .await; assert!(matches!( wt_client.remove_tower(get_random_user_id()), From 43beb12b2f5b9d0deb441d400fcc063e0ce548ff Mon Sep 17 00:00:00 2001 From: dzdidi Date: Fri, 7 Feb 2025 17:10:12 +0100 Subject: [PATCH 43/85] in memory kv store Signed-off-by: dzdidi --- teos-ldk-client/Cargo.toml | 4 +- teos-ldk-client/src/storage/kv.rs | 769 +++++++++- teos-ldk-client/src/storage/memory_store.rs | 117 ++ teos-ldk-client/src/storage/mod.rs | 11 +- teos-ldk-client/src/wt_client.rs | 1386 ++++++++++--------- 5 files changed, 1564 insertions(+), 723 deletions(-) create mode 100644 teos-ldk-client/src/storage/memory_store.rs diff --git a/teos-ldk-client/Cargo.toml b/teos-ldk-client/Cargo.toml index 35d21545..c3f95e9c 100644 --- a/teos-ldk-client/Cargo.toml +++ b/teos-ldk-client/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [features] -default = ["sqlite"] +default = ["kv"] sqlite = [] kv = [] @@ -28,6 +28,8 @@ lightning = "0.1.0" # Local teos-common = { path = "../teos-common" } +chacha20poly1305 = "0.10.1" +rand = "0.9.0" [dev-dependencies] mockito = "0.32.4" diff --git a/teos-ldk-client/src/storage/kv.rs b/teos-ldk-client/src/storage/kv.rs index c6116145..28ca1101 100644 --- a/teos-ldk-client/src/storage/kv.rs +++ b/teos-ldk-client/src/storage/kv.rs @@ -1,11 +1,24 @@ use std::collections::{HashMap, HashSet}; -use bitcoin::secp256k1::SecretKey; -use lightning::io::Error; +// use bitcoin::secp256k1::SecretKey; +pub use lightning::io::Error as DBError; use lightning::util::persist::KVStore; use teos_common::appointment::{Appointment, Locator}; use teos_common::receipts::{AppointmentReceipt, RegistrationReceipt}; use teos_common::{TowerId, UserId}; +// use teos_common::cryptography; + +// use chacha20poly1305::aead::{Aead, NewAead}; +use chacha20poly1305::{ChaCha20Poly1305, Key, KeyInit, Nonce}; +// use rand::distributions::Uniform; +// use rand::Rng; + +// use bitcoin::consensus; +use bitcoin::hashes::{sha256, Hash}; +// use bitcoin::secp256k1::{Error, PublicKey, Secp256k1, SecretKey}; +// use bitcoin::{Transaction, Txid}; +// use lightning::util::message_signing; + use crate::{AppointmentStatus, MisbehaviorProof, TowerInfo, TowerStatus, TowerSummary}; @@ -21,15 +34,15 @@ const NS_PENDING_APPOINTMENTS: &str = "pending_appointments"; const NS_INVALID_APPOINTMENTS: &str = "invalid_appointments"; const NS_MISBEHAVIOR_PROOFS: &str = "misbehavior_proofs"; -pub(crate) struct Storage { +pub struct Storage { store: T, - sk: String, + sk: Vec, } // Implement methods to convert TowerInfo to vec and vice versa impl Storage { - pub(crate) fn new(store: T, sk: String) -> Result { + pub(crate) fn new(store: T, sk: Vec) -> Result { Ok(Storage { store, sk }) } @@ -56,7 +69,7 @@ impl Storage { tower_id: TowerId, net_addr: &str, receipt: &RegistrationReceipt, - ) -> Result<(), Error> { + ) -> Result<(), DBError> { let key = Self::make_key(&[&tower_id.to_string()]); let value = TowerInfo::new( @@ -71,10 +84,10 @@ impl Storage { .to_vec() .unwrap(); - // TODO: encrypt + let encrypted = encrypt(&value, &self.sk).unwrap(); self.store - .write(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key, &value) + .write(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key, &encrypted) } /// Loads a tower record from the database. @@ -89,14 +102,14 @@ impl Storage { Err(_) => return None, }; - // TODO: decrypt + let decrypted = decrypt(&value, &self.sk).unwrap(); - let mut tower_info = TowerInfo::from_slice(&value).unwrap(); - tower_info.appointments = self.load_appointment_receipts(tower_id); - tower_info.pending_appointments = - self.load_appointments(tower_id, AppointmentStatus::Pending); - tower_info.invalid_appointments = - self.load_appointments(tower_id, AppointmentStatus::Invalid); + let mut tower_info = TowerInfo::from_slice(&decrypted).unwrap(); + // tower_info.appointments = self.load_appointment_receipts(tower_id); + // tower_info.pending_appointments = + // self.load_appointments(tower_id, AppointmentStatus::Pending); + // tower_info.invalid_appointments = + // self.load_appointments(tower_id, AppointmentStatus::Invalid); Some(tower_info) } @@ -105,7 +118,7 @@ impl Storage { /// /// 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> { + pub fn remove_tower_record(&self, tower_id: TowerId) -> Result<(), DBError> { let key = Self::make_key(&[&tower_id.to_string()]); // primary namespace: "watchtower" // secondary namespance: "tower_record" @@ -128,10 +141,11 @@ impl Storage { tower_id: TowerId, user_id: UserId, ) -> Option { - let key = Self::make_key(&[&tower_id.to_string()]); - - self.store - .read(PRIMARY_NAMESPACE, NS_REGISTRATION_RECEIPTS, &key) + // let key = Self::make_key(&[&tower_id.to_string()]); + // + // self.store + // .read(PRIMARY_NAMESPACE, NS_REGISTRATION_RECEIPTS, &key); + todo!(); } /// Stores an appointments receipt into the database representing an appointment accepted by a given tower. @@ -141,14 +155,14 @@ impl Storage { locator: Locator, available_slots: u32, receipt: &AppointmentReceipt, - ) -> Result<(), Error> { + ) -> Result<(), DBError> { let key = Self::make_key(&[&tower_id.to_string(), &locator.to_string()]); // primary namespace: "watchtower" // secondary namespance: "appointment_receipt" // key: + // value: ? - self.store - .write(PRIMARY_NAMESPACE, NS_APPOINTMENT_RECEIPTS, &key, &value) + // self.store.write(PRIMARY_NAMESPACE, NS_APPOINTMENT_RECEIPTS, &key, &value); + todo!(); } /// Loads a given appointment receipt of a given tower from the database. @@ -157,11 +171,12 @@ impl Storage { tower_id: TowerId, locator: Locator, ) -> Option { - let key = Self::make_key(&[&tower_id.to_string(), &locator.to_string()]); - // primary namespace: "watchtower" - // secondary namespance: "appointment_receipt" - self.store - .read(PRIMARY_NAMESPACE, NS_APPOINTMENT_RECEIPTS, &key) + // let key = Self::make_key(&[&tower_id.to_string(), &locator.to_string()]); + // // primary namespace: "watchtower" + // // secondary namespance: "appointment_receipt" + // self.store + // .read(PRIMARY_NAMESPACE, NS_APPOINTMENT_RECEIPTS, &key) + todo!(); } /// Loads the appointment receipts associated to a given tower. @@ -201,7 +216,7 @@ impl Storage { /// /// Appointments are only stored as a whole when they are pending or invalid. /// Accepted appointments are simplified in the form of an appointment receipt. - fn store_appointment(&self, appointment: &Appointment) -> Result { + fn store_appointment(&self, appointment: &Appointment) -> Result { let key = Self::make_key(&[&appointment.locator.to_string()]); // primary namespace: "watchtower" // secondary namespance: "appointment" @@ -219,7 +234,7 @@ impl Storage { &self, tower_id: TowerId, appointment: &Appointment, - ) -> Result<(), Error> { + ) -> Result<(), DBError> { let key = Self::make_key(&[&tower_id.to_string(), &appointment.locator.to_string()]); // primary namespace: "watchtower" // secondary namespance: "pending_appointment" @@ -235,7 +250,7 @@ impl Storage { &self, tower_id: TowerId, locator: Locator, - ) -> Result<(), Error> { + ) -> Result<(), DBError> { let key = Self::make_key(&[&tower_id.to_string(), &locator.to_string()]); // primary namespace: "watchtower" // secondary namespance: "pending_appointment" @@ -253,7 +268,7 @@ impl Storage { &mut self, tower_id: TowerId, appointment: &Appointment, - ) -> Result<(), Error> { + ) -> Result<(), DBError> { let key = Self::make_key(&[&tower_id.to_string(), &appointment.locator.to_string()]); // primary namespace: "watchtower" // secondary namespance: "invalid_appointment" @@ -287,7 +302,7 @@ impl Storage { &self, tower_id: TowerId, proof: &MisbehaviorProof, - ) -> Result<(), Error> { + ) -> Result<(), DBError> { let key = Self::make_key(&[&tower_id.to_string()]); // primary namespace: "watchtower" // secondary namespance: "misbehaving_proof" @@ -314,3 +329,691 @@ impl Storage { todo!(); } } + +/// Encrypts a given message under a given secret using `chacha20poly1305`. +/// +/// The key material used is: +/// - The dispute txid as encryption key. +/// - `[0; 12]` as IV. +/// +/// The message to be encrypted is expected to be the penalty transaction. +fn encrypt( + message: &Vec, + secret: &Vec, +) -> Result, chacha20poly1305::aead::Error> { + // Defaults is [0; 12] + let nonce = Nonce::default(); + let k = sha256::Hash::hash(secret); + let key = Key::from_slice(k.as_byte_array()); + + let cypher = ChaCha20Poly1305::new(key); + + cypher.encrypt(&nonce, message.to_vec().as_ref()) +} + +/// Decrypts an encrypted blob of data using `chacha20poly1305` and a given secret. +/// +/// The key material used is: +/// - The dispute txid as decryption key. +/// - `[0; 12]` as IV. +/// +/// The result is expected to be a penalty transaction. +fn decrypt(encrypted_blob: &[u8], secret: &Vec) -> Result, chacha20poly1305::aead::Error> { + // Defaults is [0; 12] + let nonce = Nonce::default(); + let k = sha256::Hash::hash(secret); + let key = Key::from_slice(k.as_byte_array()); + + let cypher = ChaCha20Poly1305::new(key); + + cypher.decrypt(&nonce, encrypted_blob.as_ref()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::storage::MemoryStore; + + use teos_common::test_utils::{ + generate_random_appointment, get_random_registration_receipt, get_random_user_id, + get_registration_receipt_from_previous, + }; + + fn create_test_storage() -> Storage { + let store = MemoryStore::new(); + let sk = vec![0u8; 32]; // Test secret key + Storage::new(store, sk).unwrap() + } + + #[test] + fn test_store_load_tower_record() { + let mut storage = create_test_storage(); + + // In order to add a tower record we need to associated registration receipt. + let tower_id = get_random_user_id(); + let net_addr = "talaia.watch"; + + let receipt = get_random_registration_receipt(); + let tower_info = TowerInfo::new( + net_addr.to_owned(), + receipt.available_slots(), + receipt.subscription_start(), + receipt.subscription_expiry(), + HashMap::new(), + Vec::new(), + Vec::new(), + ); + + // Check the loaded data matches the in memory data + storage.store_tower_record(tower_id, net_addr, &receipt) + .unwrap(); + assert_eq!(storage.load_tower_record(tower_id).unwrap(), tower_info); + } + + // #[test] + // fn test_load_registration_receipt() { + // let mut dbm = DBM::in_memory().unwrap(); + // + // // Registration receipts are stored alongside tower records when the register command is called + // let tower_id = get_random_user_id(); + // let net_addr = "talaia.watch"; + // let receipt = get_random_registration_receipt(); + // + // // Check the receipt was stored + // dbm.store_tower_record(tower_id, net_addr, &receipt) + // .unwrap(); + // assert_eq!( + // dbm.load_registration_receipt(tower_id, receipt.user_id()) + // .unwrap(), + // receipt + // ); + // + // // Add another receipt for the same tower with a higher expiry and check this last one is loaded + // let middle_receipt = get_registration_receipt_from_previous(&receipt); + // let latest_receipt = get_registration_receipt_from_previous(&middle_receipt); + // + // dbm.store_tower_record(tower_id, net_addr, &latest_receipt) + // .unwrap(); + // assert_eq!( + // dbm.load_registration_receipt(tower_id, latest_receipt.user_id()) + // .unwrap(), + // latest_receipt + // ); + // + // // Add a final one with a lower expiry and check the last is still loaded + // dbm.store_tower_record(tower_id, net_addr, &middle_receipt) + // .unwrap(); + // assert_eq!( + // dbm.load_registration_receipt(tower_id, latest_receipt.user_id()) + // .unwrap(), + // latest_receipt + // ); + // } + // + // #[test] + // fn test_load_same_registration_receipt() { + // let mut dbm = DBM::in_memory().unwrap(); + // + // // Registration receipts are stored alongside tower records when the register command is called + // let tower_id = get_random_user_id(); + // let net_addr = "talaia.watch"; + // let receipt = get_random_registration_receipt(); + // + // // Store it once + // dbm.store_tower_record(tower_id, net_addr, &receipt) + // .unwrap(); + // assert_eq!( + // dbm.load_registration_receipt(tower_id, receipt.user_id()) + // .unwrap(), + // receipt + // ); + // + // // Store the same again, this should fail due to UNIQUE PK constrains. + // // Notice store_tower_record is guarded against this by WTClient::add_update_tower though. + // assert!(matches!( + // dbm.store_tower_record(tower_id, net_addr, &receipt), + // Err { .. } + // )); + // } + // + // #[test] + // fn test_load_nonexistent_tower_record() { + // let dbm = DBM::in_memory().unwrap(); + // + // // If the tower does not exists, `load_tower` will fail. + // let tower_id = get_random_user_id(); + // assert!(dbm.load_tower_record(tower_id).is_none()); + // } + // + // #[test] + // fn test_store_load_towers() { + // let mut dbm = DBM::in_memory().unwrap(); + // let mut towers = HashMap::new(); + // + // // In order to add a tower record we need to associated registration receipt. + // for _ in 0..10 { + // let tower_id = get_random_user_id(); + // let net_addr = "talaia.watch"; + // let mut receipt = get_random_registration_receipt(); + // dbm.store_tower_record(tower_id, net_addr, &receipt) + // .unwrap(); + // + // // Add not only one registration receipt to test if the tower retrieves the one with furthest expiry date. + // for _ in 0..10 { + // receipt = get_registration_receipt_from_previous(&receipt); + // dbm.store_tower_record(tower_id, net_addr, &receipt) + // .unwrap(); + // } + // + // towers.insert( + // tower_id, + // TowerSummary::new( + // net_addr.to_owned(), + // receipt.available_slots(), + // receipt.subscription_start(), + // receipt.subscription_expiry(), + // ), + // ); + // } + // + // assert_eq!(dbm.load_towers(), towers); + // } + // + // #[test] + // fn test_load_towers_empty() { + // // If there are no towers in the database, `load_towers` should return an empty map. + // let dbm = DBM::in_memory().unwrap(); + // 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(); + // + // // In order to add a tower record we need to associated registration receipt. + // let tower_id = get_random_user_id(); + // let net_addr = "talaia.watch"; + // + // let receipt = get_random_registration_receipt(); + // let mut tower_summary = TowerSummary::new( + // net_addr.to_owned(), + // receipt.available_slots(), + // receipt.subscription_start(), + // receipt.subscription_expiry(), + // ); + // dbm.store_tower_record(tower_id, net_addr, &receipt) + // .unwrap(); + // + // // Add some appointment receipts and check they match + // let mut receipts = HashMap::new(); + // for _ in 0..5 { + // let appointment = generate_random_appointment(None); + // let user_signature = "user_signature"; + // let appointment_receipt = AppointmentReceipt::with_signature( + // user_signature.to_owned(), + // 42, + // "tower_signature".to_owned(), + // ); + // + // tower_summary.available_slots -= 1; + // + // dbm.store_appointment_receipt( + // tower_id, + // appointment.locator, + // tower_summary.available_slots, + // &appointment_receipt, + // ) + // .unwrap(); + // receipts.insert( + // appointment.locator, + // appointment_receipt.signature().unwrap(), + // ); + // } + // + // 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!(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"; + // let receipt = get_random_registration_receipt(); + // dbm.store_tower_record(tower_id, net_addr, &receipt) + // .unwrap(); + // + // assert!(dbm + // .load_appointment_receipt(tower_id, appointment.locator) + // .is_none()); + // + // // Add both + // let tower_summary = TowerSummary::new( + // net_addr.to_owned(), + // receipt.available_slots(), + // receipt.subscription_start(), + // receipt.subscription_expiry(), + // ); + // let appointment_receipt = AppointmentReceipt::with_signature( + // "user_signature".to_owned(), + // 42, + // "tower_signature".to_owned(), + // ); + // 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` + // let mut dbm = DBM::in_memory().unwrap(); + // + // // We first need to add a tower record to the database so we can add some associated data. + // let tower_id = get_random_user_id(); + // let net_addr = "talaia.watch"; + // + // let receipt = get_random_registration_receipt(); + // let tower_summary = TowerSummary::new( + // net_addr.to_owned(), + // receipt.available_slots(), + // receipt.subscription_start(), + // receipt.subscription_expiry(), + // ); + // dbm.store_tower_record(tower_id, net_addr, &receipt) + // .unwrap(); + // + // // Create all types of appointments and store them in the db. + // let user_signature = "user_signature"; + // let mut receipts = HashSet::new(); + // let mut pending_appointments = HashSet::new(); + // let mut invalid_appointments = HashSet::new(); + // for _ in 0..5 { + // let appointment = generate_random_appointment(None); + // let appointment_receipt = AppointmentReceipt::with_signature( + // user_signature.to_owned(), + // 42, + // "tower_signature".to_owned(), + // ); + // let pending_appointment = generate_random_appointment(None); + // let invalid_appointment = generate_random_appointment(None); + // + // dbm.store_appointment_receipt( + // tower_id, + // appointment.locator, + // tower_summary.available_slots, + // &appointment_receipt, + // ) + // .unwrap(); + // dbm.store_pending_appointment(tower_id, &pending_appointment) + // .unwrap(); + // dbm.store_invalid_appointment(tower_id, &invalid_appointment) + // .unwrap(); + // + // receipts.insert(appointment.locator); + // pending_appointments.insert(pending_appointment.locator); + // invalid_appointments.insert(invalid_appointment.locator); + // } + // + // // Pull data from the db and check it matches the expected data + // assert_eq!( + // dbm.load_appointment_locators(tower_id, AppointmentStatus::Accepted), + // receipts + // ); + // assert_eq!( + // dbm.load_appointment_locators(tower_id, AppointmentStatus::Pending), + // pending_appointments + // ); + // assert_eq!( + // dbm.load_appointment_locators(tower_id, AppointmentStatus::Invalid), + // invalid_appointments + // ); + // } + // + // #[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!(loaded_appointment.is_none()); + // } + // + // #[test] + // fn test_store_pending_appointment() { + // let mut dbm = DBM::in_memory().unwrap(); + // + // // In order to add a tower record we need to associated registration receipt. + // let tower_id = get_random_user_id(); + // let net_addr = "talaia.watch"; + // + // let receipt = get_random_registration_receipt(); + // let mut tower_summary = TowerSummary::new( + // net_addr.to_owned(), + // receipt.available_slots(), + // receipt.subscription_start(), + // receipt.subscription_expiry(), + // ) + // .with_status(TowerStatus::TemporaryUnreachable); + // dbm.store_tower_record(tower_id, net_addr, &receipt) + // .unwrap(); + // + // // Add some pending appointments and check they match + // for _ in 0..5 { + // let appointment = generate_random_appointment(None); + // + // tower_summary + // .pending_appointments + // .insert(appointment.locator); + // + // dbm.store_pending_appointment(tower_id, &appointment) + // .unwrap(); + // assert_eq!( + // TowerSummary::from(dbm.load_tower_record(tower_id).unwrap()), + // tower_summary + // ); + // } + // } + // + // #[test] + // fn test_store_pending_appointment_twice() { + // let mut dbm = DBM::in_memory().unwrap(); + // + // // In order to add a tower record we need to associated registration receipt. + // let tower_id_1 = get_random_user_id(); + // let tower_id_2 = get_random_user_id(); + // let net_addr = "talaia.watch"; + // + // let receipt = get_random_registration_receipt(); + // dbm.store_tower_record(tower_id_1, net_addr, &receipt) + // .unwrap(); + // dbm.store_tower_record(tower_id_2, net_addr, &receipt) + // .unwrap(); + // + // // If the same appointment is stored twice (by different towers) it should go through + // // Since the appointment data will be stored only once and this will create two references + // let appointment = generate_random_appointment(None); + // dbm.store_pending_appointment(tower_id_1, &appointment) + // .unwrap(); + // dbm.store_pending_appointment(tower_id_2, &appointment) + // .unwrap(); + // + // // If this is called twice with for the same tower it will fail, since two identical references + // // can not exist. This is intended behavior and should not happen + // assert!(dbm + // .store_pending_appointment(tower_id_2, &appointment) + // .is_err()); + // } + // + // #[test] + // fn test_delete_pending_appointment() { + // let mut dbm = DBM::in_memory().unwrap(); + // + // // In order to add a tower record we need to associated registration receipt. + // 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(); + // + // // Add a single one, remove it later + // let appointment = generate_random_appointment(None); + // dbm.store_pending_appointment(tower_id, &appointment) + // .unwrap(); + // assert!(dbm + // .delete_pending_appointment(tower_id, appointment.locator) + // .is_ok()); + // + // // The appointment should be completely gone + // assert!(!dbm + // .load_appointment_locators(tower_id, AppointmentStatus::Pending) + // .contains(&appointment.locator)); + // // assert!(!dbm.appointment_exists(appointment.locator)); + // + // // Try again with more than one reference + // let another_tower_id = get_random_user_id(); + // dbm.store_tower_record(another_tower_id, net_addr, &receipt) + // .unwrap(); + // + // // Add two + // dbm.store_pending_appointment(tower_id, &appointment) + // .unwrap(); + // dbm.store_pending_appointment(another_tower_id, &appointment) + // .unwrap(); + // // Delete one + // assert!(dbm + // .delete_pending_appointment(tower_id, appointment.locator) + // .is_ok()); + // // Check + // assert!(!dbm + // .load_appointment_locators(tower_id, AppointmentStatus::Pending) + // .contains(&appointment.locator)); + // assert!(dbm + // .load_appointment_locators(another_tower_id, AppointmentStatus::Pending) + // .contains(&appointment.locator)); + // // assert!(dbm.appointment_exists(appointment.locator)); + // + // // Add an invalid reference and check again + // dbm.store_invalid_appointment(tower_id, &appointment) + // .unwrap(); + // assert!(dbm + // .delete_pending_appointment(another_tower_id, appointment.locator) + // .is_ok()); + // assert!(!dbm + // .load_appointment_locators(another_tower_id, AppointmentStatus::Pending) + // .contains(&appointment.locator)); + // assert!(dbm + // .load_appointment_locators(tower_id, AppointmentStatus::Invalid) + // .contains(&appointment.locator)); + // // assert!(dbm.appointment_exists(appointment.locator)); + // } + // + // #[test] + // fn test_store_invalid_appointment() { + // let mut dbm = DBM::in_memory().unwrap(); + // + // // In order to add a tower record we need to associated registration receipt. + // let tower_id = get_random_user_id(); + // let net_addr = "talaia.watch"; + // + // let receipt = get_random_registration_receipt(); + // let mut tower_summary = TowerSummary::new( + // net_addr.to_owned(), + // receipt.available_slots(), + // receipt.subscription_start(), + // receipt.subscription_expiry(), + // ); + // dbm.store_tower_record(tower_id, net_addr, &receipt) + // .unwrap(); + // + // // Add some invalid appointments and check they match + // for _ in 0..5 { + // let appointment = generate_random_appointment(None); + // + // tower_summary + // .invalid_appointments + // .insert(appointment.locator); + // + // dbm.store_invalid_appointment(tower_id, &appointment) + // .unwrap(); + // assert_eq!( + // TowerSummary::from(dbm.load_tower_record(tower_id).unwrap()), + // tower_summary + // ); + // } + // } + // + // #[test] + // fn test_store_invalid_appointment_twice() { + // let mut dbm = DBM::in_memory().unwrap(); + // + // // In order to add a tower record we need to associated registration receipt. + // let tower_id_1 = get_random_user_id(); + // let tower_id_2 = get_random_user_id(); + // let net_addr = "talaia.watch"; + // + // let receipt = get_random_registration_receipt(); + // dbm.store_tower_record(tower_id_1, net_addr, &receipt) + // .unwrap(); + // dbm.store_tower_record(tower_id_2, net_addr, &receipt) + // .unwrap(); + // + // // Same as with pending appointments. Two references from different towers is allowed + // let appointment = generate_random_appointment(None); + // dbm.store_invalid_appointment(tower_id_1, &appointment) + // .unwrap(); + // dbm.store_invalid_appointment(tower_id_2, &appointment) + // .unwrap(); + // + // // Two references from the same tower is not. + // assert!(dbm + // .store_invalid_appointment(tower_id_2, &appointment) + // .is_err()); + // } + // + // #[test] + // fn test_store_load_misbehaving_proof() { + // let mut dbm = DBM::in_memory().unwrap(); + // + // // In order to add a tower record we need to associated registration receipt. + // let tower_id = get_random_user_id(); + // let net_addr = "talaia.watch"; + // + // let receipt = get_random_registration_receipt(); + // let tower_summary = TowerSummary::new( + // net_addr.to_owned(), + // receipt.available_slots(), + // receipt.subscription_start(), + // receipt.subscription_expiry(), + // ); + // dbm.store_tower_record(tower_id, net_addr, &receipt) + // .unwrap(); + // assert_eq!( + // TowerSummary::from(dbm.load_tower_record(tower_id).unwrap()), + // tower_summary + // ); + // + // // Store a misbehaving proof and load it back + // let appointment = generate_random_appointment(None); + // let appointment_receipt = AppointmentReceipt::with_signature( + // "user_signature".to_owned(), + // 42, + // "tower_signature".to_owned(), + // ); + // + // let proof = MisbehaviorProof::new( + // appointment.locator, + // appointment_receipt, + // get_random_user_id(), + // ); + // + // dbm.store_misbehaving_proof(tower_id, &proof).unwrap(); + // assert_eq!(dbm.load_misbehaving_proof(tower_id).unwrap(), proof); + // } + // + // #[test] + // fn test_store_load_non_existing_misbehaving_proof() { + // let dbm = DBM::in_memory().unwrap(); + // assert!(dbm.load_misbehaving_proof(get_random_user_id()).is_none()); + // } + // + // #[test] + // fn test_store_exists_misbehaving_proof() { + // let mut dbm = DBM::in_memory().unwrap(); + // + // // In order to add a tower record we need to associated registration receipt. + // let tower_id = get_random_user_id(); + // let net_addr = "talaia.watch"; + // + // let receipt = get_random_registration_receipt(); + // let tower_summary = TowerSummary::new( + // net_addr.to_owned(), + // receipt.available_slots(), + // receipt.subscription_start(), + // receipt.subscription_expiry(), + // ); + // dbm.store_tower_record(tower_id, net_addr, &receipt) + // .unwrap(); + // assert_eq!( + // TowerSummary::from(dbm.load_tower_record(tower_id).unwrap()), + // tower_summary + // ); + // + // // // Store a misbehaving proof check + // let appointment = generate_random_appointment(None); + // let appointment_receipt = AppointmentReceipt::with_signature( + // "user_signature".to_owned(), + // 42, + // "tower_signature".to_owned(), + // ); + // + // let proof = MisbehaviorProof::new( + // appointment.locator, + // appointment_receipt, + // get_random_user_id(), + // ); + // + // dbm.store_misbehaving_proof(tower_id, &proof).unwrap(); + // assert!(dbm.exists_misbehaving_proof(tower_id)); + // } + // + // #[test] + // fn test_exists_misbehaving_proof_false() { + // let dbm = DBM::in_memory().unwrap(); + // assert!(!dbm.exists_misbehaving_proof(get_random_user_id())); + // } +} diff --git a/teos-ldk-client/src/storage/memory_store.rs b/teos-ldk-client/src/storage/memory_store.rs new file mode 100644 index 00000000..c6383b4c --- /dev/null +++ b/teos-ldk-client/src/storage/memory_store.rs @@ -0,0 +1,117 @@ + +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; +use lightning::io::Error as DBError; +use lightning::util::persist::KVStore; + +/// In-memory key-value store implementation for testing +#[derive(Clone, Debug)] +pub struct MemoryStore { + data: Arc>>>>, +} + +impl MemoryStore { + /// Creates a new empty MemoryStore + pub fn new() -> Self { + MemoryStore { + data: Arc::new(Mutex::new(HashMap::new())), + } + } + + /// Creates the composite key used for storage + fn make_key(namespace: &str, key: &str) -> String { + format!("{}:{}", namespace, key) + } +} + +impl Default for MemoryStore { + fn default() -> Self { + Self::new() + } +} + +impl KVStore for MemoryStore { + fn read(&self, primary_namespace: &str, secondary_namespace: &str, key: &str) -> Result, DBError> { + let data = self.data.lock().map_err(|_| DBError::new(bitcoin::io::ErrorKind::AddrInUse, "Lock poisoned"))?; + + let namespace = Self::make_key(primary_namespace, secondary_namespace); + data.get(&namespace) + .and_then(|ns| ns.get(key)) + .cloned() + .ok_or_else(|| DBError::new(bitcoin::io::ErrorKind::NotFound, "Key not found")) + } + + fn write(&self, primary_namespace: &str, secondary_namespace: &str, key: &str, value: &[u8]) -> Result<(), DBError> { + let mut data = self.data.lock().map_err(|_| DBError::new(bitcoin::io::ErrorKind::AddrInUse, "Lock poisoned"))?; + + let namespace = Self::make_key(primary_namespace, secondary_namespace); + let ns_map = data.entry(namespace).or_default(); + ns_map.insert(key.to_string(), value.to_vec()); + + Ok(()) + } + + fn remove(&self, primary_namespace: &str, secondary_namespace: &str, key: &str, _lazy: bool) -> Result<(), DBError> { + let mut data = self.data.lock().map_err(|_| DBError::new(bitcoin::io::ErrorKind::AddrInUse, "Lock poisoned"))?; + + let namespace = Self::make_key(primary_namespace, secondary_namespace); + if let Some(ns_map) = data.get_mut(&namespace) { + ns_map.remove(key); + Ok(()) + } else { + Err(DBError::new(bitcoin::io::ErrorKind::NotFound, "Key not found")) + } + } + + fn list(&self, primary_namespace: &str, secondary_namespace: &str) -> Result, DBError> { + let data = self.data.lock().map_err(|_| DBError::new(bitcoin::io::ErrorKind::AddrInUse, "Lock poisoned"))?; + + let namespace = Self::make_key(primary_namespace, secondary_namespace); + let res = data.get(&namespace) + .map(|ns_map| ns_map.keys().cloned().collect()) + .unwrap(); + + Ok(res) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_basic_operations() { + let store = MemoryStore::new(); + + // Test write and read + store.write("primary", "secondary", "key1", b"value1").unwrap(); + assert_eq!(store.read("primary", "secondary", "key1").unwrap(), b"value1"); + + // Test remove + store.remove("primary", "secondary", "key1", false).unwrap(); + assert!(store.read("primary", "secondary", "key1").is_err()); + + // Test list + store.write("primary", "secondary", "key1", b"value1").unwrap(); + store.write("primary", "secondary", "key2", b"value2").unwrap(); + let keys = store.list("primary", "secondary").unwrap(); + assert_eq!(keys.len(), 2); + assert!(keys.contains(&"key1".to_string())); + assert!(keys.contains(&"key2".to_string())); + } + + #[test] + fn test_namespacing() { + let store = MemoryStore::new(); + + // Write same key to different namespaces + store.write("primary1", "secondary1", "key", b"value1").unwrap(); + store.write("primary1", "secondary2", "key", b"value2").unwrap(); + store.write("primary2", "secondary1", "key", b"value3").unwrap(); + + // Verify they don't interfere + assert_eq!(store.read("primary1", "secondary1", "key").unwrap(), b"value1"); + assert_eq!(store.read("primary1", "secondary2", "key").unwrap(), b"value2"); + assert_eq!(store.read("primary2", "secondary1", "key").unwrap(), b"value3"); + } +} diff --git a/teos-ldk-client/src/storage/mod.rs b/teos-ldk-client/src/storage/mod.rs index d376d553..9ce6f816 100644 --- a/teos-ldk-client/src/storage/mod.rs +++ b/teos-ldk-client/src/storage/mod.rs @@ -16,4 +16,13 @@ pub use dbm::DBM as Storage; #[cfg(feature = "kv")] mod kv; #[cfg(feature = "kv")] -pub use kv::Storage; +pub(crate) use kv::Storage; +#[cfg(feature = "kv")] +pub use kv::DBError; + +#[cfg(test)] +#[cfg(feature = "kv")] +mod memory_store; +#[cfg(test)] +#[cfg(feature = "kv")] +pub use memory_store::MemoryStore; diff --git a/teos-ldk-client/src/wt_client.rs b/teos-ldk-client/src/wt_client.rs index bca3c32a..59669306 100644 --- a/teos-ldk-client/src/wt_client.rs +++ b/teos-ldk-client/src/wt_client.rs @@ -1,18 +1,24 @@ use std::collections::{HashMap, HashSet}; use std::iter::FromIterator; + +#[cfg(feature = "sqlite")] use std::path::PathBuf; +#[cfg(feature = "sqlite")] use tokio::fs; use tokio::sync::mpsc::UnboundedSender; use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; use teos_common::appointment::{Appointment, Locator}; +#[cfg(feature = "sqlite")] use teos_common::cryptography; use teos_common::receipts::{AppointmentReceipt, RegistrationReceipt}; use teos_common::{TowerId, UserId}; use crate::retrier::RetrierStatus; use crate::storage::{DBError, Storage}; +#[cfg(feature = "kv")] +use lightning::util::persist::KVStore; use crate::{MisbehaviorProof, SubscriptionError, TowerInfo, TowerStatus, TowerSummary}; #[derive(Eq, PartialEq)] @@ -58,7 +64,11 @@ impl std::fmt::Debug for RevocationData { /// Represents the watchtower client that is being used as the CoreLN plugin state. pub struct WTClient { /// A [Storage] instance. + #[cfg(feature = "sqlite")] pub storage: Storage, // should support both slqite or KVStorage? + /// A [Storage] instance. + #[cfg(feature = "kv")] + pub storage: Storage, // should support both slqite or KVStorage? /// A collection of towers the client is registered to. pub towers: HashMap, /// Queue of unreachable towers. @@ -118,7 +128,7 @@ impl WTClient { unreachable_towers: UnboundedSender<(TowerId, RevocationData)>, ) -> Self { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &user_sk)); - let storage = Storage::new(store, user_sk).unwrap(); + let storage = Storage::new(store, user_sk.secret_bytes().to_vec()).unwrap(); let towers = storage.load_towers(); for (tower_id, tower) in towers.iter() { @@ -316,690 +326,690 @@ impl WTClient { } } } - -#[cfg(test)] -mod tests { - use super::*; - - use tempdir::TempDir; - use tokio::sync::mpsc::unbounded_channel; - - use teos_common::test_utils::{ - generate_random_appointment, get_random_appointment_receipt, - get_random_registration_receipt, get_random_user_id, - get_registration_receipt_from_previous, - }; - - #[tokio::test] - async fn test_add_update_load_tower() { - let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let mut wt_client = WTClient::new( - tmp_path.path().to_path_buf(), - keypair.0, - unbounded_channel().0, - ) - .await; - - // 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_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(), - receipt.subscription_start(), - receipt.subscription_expiry(), - ); - - wt_client - .add_update_tower(tower_id, &tower_info.net_addr, &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); - - // Calling the method again with updated information should also updated the records in memory and the database - receipt = get_registration_receipt_from_previous(&receipt); - - let updated_tower_info = TowerInfo::empty( - "talaia.watch".to_owned(), - receipt.available_slots(), - receipt.subscription_start(), - receipt.subscription_expiry(), - ); - wt_client - .add_update_tower(tower_id, &updated_tower_info.net_addr, &receipt) - .unwrap(); - - assert_eq!( - wt_client.towers.get(&tower_id), - Some(&TowerSummary::from(updated_tower_info.clone())) - ); - assert_eq!( - wt_client.load_tower_info(tower_id).unwrap(), - updated_tower_info - ); - - // If we try to update without increasing both the end_time and the slots, this will fail - let mut receipt_same_slots = RegistrationReceipt::new( - receipt.user_id(), - receipt.available_slots(), - receipt.subscription_start(), - receipt.subscription_expiry() + 1, - ); - 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), - 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, - &receipt_same_expiry - ), - 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] - async fn test_get_tower_status() { - let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let mut wt_client = WTClient::new( - tmp_path.path().to_path_buf(), - keypair.0, - 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 keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let mut wt_client = WTClient::new( - tmp_path.path().to_path_buf(), - keypair.0, - unbounded_channel().0, - ) - .await; - - // If the tower is unknown nothing will happen - let unknown_tower = get_random_user_id(); - wt_client.set_tower_status(unknown_tower, TowerStatus::Reachable); - assert!(!wt_client.towers.contains_key(&unknown_tower)); - - // If the tower is known, the status will be updated. - let receipt = get_random_registration_receipt(); - let tower_id = get_random_user_id(); - wt_client - .add_update_tower(tower_id, "talaia.watch", &receipt) - .unwrap(); - - for status in [ - TowerStatus::Reachable, - TowerStatus::TemporaryUnreachable, - TowerStatus::Unreachable, - TowerStatus::SubscriptionError, - TowerStatus::Misbehaving, - ] { - wt_client.set_tower_status(tower_id, status); - assert_eq!(status, wt_client.get_tower_status(&tower_id).unwrap()); - } - } - - #[tokio::test] - async fn test_add_appointment_receipt() { - let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let mut wt_client = WTClient::new( - tmp_path.path().to_path_buf(), - keypair.0, - unbounded_channel().0, - ) - .await; - - let (tower_sk, tower_pk) = cryptography::get_random_keypair(); - let tower_id = TowerId(tower_pk); - - 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.towers.contains_key(&tower_id)); - - // Add the tower to the state and try again - let tower_info = TowerInfo::new( - "talaia.watch".to_owned(), - registration_receipt.available_slots(), - registration_receipt.subscription_start(), - registration_receipt.subscription_expiry(), - HashMap::from([(locator, appointment_receipt.signature().unwrap())]), - Vec::new(), - Vec::new(), - ); - wt_client - .add_update_tower(tower_id, &tower_info.net_addr, ®istration_receipt) - .unwrap(); - wt_client.add_appointment_receipt( - tower_id, - locator, - registration_receipt.available_slots(), - &appointment_receipt, - ); - - assert!(wt_client.towers.contains_key(&tower_id)); - assert_eq!( - wt_client.towers.get(&tower_id).unwrap(), - &TowerSummary::from(tower_info.clone()) - ); - assert_eq!(wt_client.load_tower_info(tower_id).unwrap(), tower_info); - } - - #[tokio::test] - async fn test_add_pending_appointment() { - let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let mut wt_client = WTClient::new( - tmp_path.path().to_path_buf(), - keypair.0, - unbounded_channel().0, - ) - .await; - - let tower_id = get_random_user_id(); - - let registration_receipt = get_random_registration_receipt(); - let appointment = generate_random_appointment(None); - - // If we call this on an unknown tower it will simply do nothing - wt_client.add_pending_appointment(tower_id, &appointment); - assert!(!wt_client.towers.contains_key(&tower_id)); - - // Add the tower to the state and try again - let tower_info = TowerInfo::new( - "talaia.watch".to_owned(), - registration_receipt.available_slots(), - registration_receipt.subscription_start(), - registration_receipt.subscription_expiry(), - HashMap::new(), - vec![appointment.clone()], - Vec::new(), - ); - - wt_client - .add_update_tower(tower_id, &tower_info.net_addr, ®istration_receipt) - .unwrap(); - wt_client.add_pending_appointment(tower_id, &appointment); - - assert!(wt_client.towers.contains_key(&tower_id)); - assert_eq!( - wt_client.towers.get(&tower_id).unwrap(), - &TowerSummary::from(tower_info.clone()) - ); - // When towers data is loaded from the database, it is assumed to be reachable. - assert_eq!( - wt_client.load_tower_info(tower_id).unwrap(), - tower_info.with_status(TowerStatus::TemporaryUnreachable) - ); - } - - #[tokio::test] - async fn test_remove_pending_appointment() { - let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let mut wt_client = WTClient::new( - tmp_path.path().to_path_buf(), - keypair.0, - unbounded_channel().0, - ) - .await; - - let tower_id = get_random_user_id(); - - let registration_receipt = get_random_registration_receipt(); - let appointment = generate_random_appointment(None); - - // If we call this on an unknown tower it will simply do nothing - wt_client.remove_pending_appointment(tower_id, appointment.locator); - - // Add the tower to the state and try again - wt_client - .add_update_tower(tower_id, "talaia.watch", ®istration_receipt) - .unwrap(); - wt_client.add_pending_appointment(tower_id, &appointment); - - wt_client.remove_pending_appointment(tower_id, appointment.locator); - assert!(!wt_client - .towers - .get(&tower_id) - .unwrap() - .pending_appointments - .contains(&appointment.locator)); - // This bit is tested exhaustively in the Storage. - assert!(!wt_client.storage.appointment_exists(appointment.locator)); - } - - #[tokio::test] - async fn test_add_invalid_appointment() { - let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let mut wt_client = WTClient::new( - tmp_path.path().to_path_buf(), - keypair.0, - unbounded_channel().0, - ) - .await; - - let tower_id = get_random_user_id(); - - let registration_receipt = get_random_registration_receipt(); - let appointment = generate_random_appointment(None); - - // If we call this on an unknown tower it will simply do nothing - wt_client.add_invalid_appointment(tower_id, &appointment); - assert!(!wt_client.towers.contains_key(&tower_id)); - - // Add the tower to the state and try again - let tower_info = TowerInfo::new( - "talaia.watch".to_owned(), - registration_receipt.available_slots(), - registration_receipt.subscription_start(), - registration_receipt.subscription_expiry(), - HashMap::new(), - Vec::new(), - vec![appointment.clone()], - ); - - wt_client - .add_update_tower(tower_id, &tower_info.net_addr, ®istration_receipt) - .unwrap(); - wt_client.add_invalid_appointment(tower_id, &appointment); - - assert!(wt_client.towers.contains_key(&tower_id)); - assert_eq!( - wt_client.towers.get(&tower_id).unwrap(), - &TowerSummary::from(tower_info.clone()) - ); - assert_eq!(wt_client.load_tower_info(tower_id).unwrap(), tower_info); - } - - #[tokio::test] - async fn test_move_pending_appointment_to_invalid() { - let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let mut wt_client = WTClient::new( - tmp_path.path().to_path_buf(), - keypair.0, - unbounded_channel().0, - ) - .await; - - let tower_id = get_random_user_id(); - - let registration_receipt = get_random_registration_receipt(); - let appointment = generate_random_appointment(None); - - wt_client - .add_update_tower(tower_id, "talaia.watch", ®istration_receipt) - .unwrap(); - wt_client.add_pending_appointment(tower_id, &appointment); - - // Check that the appointment can be moved from pending to invalid - wt_client.add_invalid_appointment(tower_id, &appointment); - wt_client.remove_pending_appointment(tower_id, appointment.locator); - - assert!(!wt_client - .towers - .get(&tower_id) - .unwrap() - .pending_appointments - .contains(&appointment.locator)); - assert!(wt_client - .towers - .get(&tower_id) - .unwrap() - .invalid_appointments - .contains(&appointment.locator)); - assert!(!wt_client - .storage - .load_appointment_locators(tower_id, crate::AppointmentStatus::Pending) - .contains(&appointment.locator)); - assert!(wt_client - .storage - .load_appointment_locators(tower_id, crate::AppointmentStatus::Invalid) - .contains(&appointment.locator)); - assert!(wt_client.storage.appointment_exists(appointment.locator)); - } - - #[tokio::test] - async fn test_move_pending_appointment_to_invalid_multiple_towers() { - // Check that moving an appointment from pending to invalid can be done even if multiple towers have a reference to it - let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let mut wt_client = WTClient::new( - tmp_path.path().to_path_buf(), - keypair.0, - unbounded_channel().0, - ) - .await; - - let tower_id = get_random_user_id(); - let another_tower_id = get_random_user_id(); - 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) - .unwrap(); - wt_client - .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); - - // Check that the appointment can be moved from pending to invalid - wt_client.add_invalid_appointment(tower_id, &appointment); - wt_client.remove_pending_appointment(tower_id, appointment.locator); - - // TOWER_ID CHECKS - assert!(!wt_client - .towers - .get(&tower_id) - .unwrap() - .pending_appointments - .contains(&appointment.locator)); - assert!(wt_client - .towers - .get(&tower_id) - .unwrap() - .invalid_appointments - .contains(&appointment.locator)); - assert!(!wt_client - .storage - .load_appointment_locators(tower_id, crate::AppointmentStatus::Pending) - .contains(&appointment.locator)); - assert!(wt_client - .storage - .load_appointment_locators(tower_id, crate::AppointmentStatus::Invalid) - .contains(&appointment.locator)); - - // ANOTHER_TOWER_ID CHECKS - assert!(wt_client - .towers - .get(&another_tower_id) - .unwrap() - .pending_appointments - .contains(&appointment.locator)); - assert!(!wt_client - .towers - .get(&another_tower_id) - .unwrap() - .invalid_appointments - .contains(&appointment.locator)); - assert!(wt_client - .storage - .load_appointment_locators(another_tower_id, crate::AppointmentStatus::Pending) - .contains(&appointment.locator)); - assert!(!wt_client - .storage - .load_appointment_locators(another_tower_id, crate::AppointmentStatus::Invalid) - .contains(&appointment.locator)); - - // GENERAL - assert!(wt_client.storage.appointment_exists(appointment.locator)); - } - - #[tokio::test] - async fn test_flag_misbehaving_tower() { - let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let mut wt_client = WTClient::new( - tmp_path.path().to_path_buf(), - keypair.0, - unbounded_channel().0, - ) - .await; - - let (tower_sk, tower_pk) = cryptography::get_random_keypair(); - let tower_id = TowerId(tower_pk); - - // If we call this on an unknown tower it will simply do nothing - let appointment = generate_random_appointment(None); - let receipt = get_random_appointment_receipt(tower_sk); - let proof = MisbehaviorProof::new(appointment.locator, receipt, get_random_user_id()); - wt_client.flag_misbehaving_tower(tower_id, proof.clone()); - assert!(!wt_client.towers.contains_key(&tower_id)); - - // // Add the tower to the state and try again - let registration_receipt = get_random_registration_receipt(); - wt_client - .add_update_tower(tower_id, "talaia.watch", ®istration_receipt) - .unwrap(); - wt_client.flag_misbehaving_tower(tower_id, proof.clone()); - - // Check data in memory - let tower_summary = wt_client.towers.get(&tower_id); - assert!(tower_summary.is_some()); - assert!(tower_summary.unwrap().status.is_misbehaving()); - - // Check data in DB - let loaded_info = wt_client.load_tower_info(tower_id).unwrap(); - assert!(loaded_info.status.is_misbehaving()); - assert_eq!(loaded_info.misbehaving_proof, Some(proof)); - assert!(loaded_info.appointments.contains_key(&appointment.locator)); - } - - #[tokio::test] - async fn test_remove_tower() { - let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let mut wt_client = WTClient::new( - tmp_path.path().to_path_buf(), - keypair.0, - 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".to_owned(), - 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, &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!(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 - 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 - .storage - .appointment_receipt_exists(locator, tower_id)); - - // Remove and check both the tower and the appointment - wt_client.remove_tower(tower_id).unwrap(); - assert!(wt_client.load_tower_info(tower_id).is_none()); - assert!(!wt_client.towers.contains_key(&tower_id)); - assert!(!wt_client - .storage - .appointment_receipt_exists(locator, tower_id)); - } - - #[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 keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let mut wt_client = WTClient::new( - tmp_path.path().to_path_buf(), - keypair.0, - 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); - - wt_client - .add_update_tower(tower1_id, "talaia.watch", &receipt) - .unwrap(); - wt_client - .add_update_tower(tower2_id, "talaia.watch", &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 - .storage - .appointment_receipt_exists(locator, tower1_id)); - assert!(wt_client - .storage - .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!(wt_client.load_tower_info(tower1_id).is_none()); - - assert!(!wt_client - .storage - .appointment_receipt_exists(locator, tower1_id)); - assert!(wt_client - .storage - .appointment_receipt_exists(locator, tower2_id)); - } - - #[tokio::test] - async fn test_remove_inexistent_tower() { - let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let mut wt_client = WTClient::new( - tmp_path.path().to_path_buf(), - keypair.0, - unbounded_channel().0, - ) - .await; - - assert!(matches!( - wt_client.remove_tower(get_random_user_id()), - Err(DBError::NotFound) - )); - } -} +// +// #[cfg(test)] +// mod tests { +// use super::*; +// +// use tempdir::TempDir; +// use tokio::sync::mpsc::unbounded_channel; +// +// use teos_common::test_utils::{ +// generate_random_appointment, get_random_appointment_receipt, +// get_random_registration_receipt, get_random_user_id, +// get_registration_receipt_from_previous, +// }; +// +// #[tokio::test] +// async fn test_add_update_load_tower() { +// let keypair = cryptography::get_random_keypair(); +// let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); +// let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); +// let mut wt_client = WTClient::new( +// tmp_path.path().to_path_buf(), +// keypair.0, +// unbounded_channel().0, +// ) +// .await; +// +// // 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_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(), +// receipt.subscription_start(), +// receipt.subscription_expiry(), +// ); +// +// wt_client +// .add_update_tower(tower_id, &tower_info.net_addr, &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); +// +// // Calling the method again with updated information should also updated the records in memory and the database +// receipt = get_registration_receipt_from_previous(&receipt); +// +// let updated_tower_info = TowerInfo::empty( +// "talaia.watch".to_owned(), +// receipt.available_slots(), +// receipt.subscription_start(), +// receipt.subscription_expiry(), +// ); +// wt_client +// .add_update_tower(tower_id, &updated_tower_info.net_addr, &receipt) +// .unwrap(); +// +// assert_eq!( +// wt_client.towers.get(&tower_id), +// Some(&TowerSummary::from(updated_tower_info.clone())) +// ); +// assert_eq!( +// wt_client.load_tower_info(tower_id).unwrap(), +// updated_tower_info +// ); +// +// // If we try to update without increasing both the end_time and the slots, this will fail +// let mut receipt_same_slots = RegistrationReceipt::new( +// receipt.user_id(), +// receipt.available_slots(), +// receipt.subscription_start(), +// receipt.subscription_expiry() + 1, +// ); +// 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), +// 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, +// &receipt_same_expiry +// ), +// 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] +// async fn test_get_tower_status() { +// let keypair = cryptography::get_random_keypair(); +// let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); +// let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); +// let mut wt_client = WTClient::new( +// tmp_path.path().to_path_buf(), +// keypair.0, +// 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 keypair = cryptography::get_random_keypair(); +// let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); +// let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); +// let mut wt_client = WTClient::new( +// tmp_path.path().to_path_buf(), +// keypair.0, +// unbounded_channel().0, +// ) +// .await; +// +// // If the tower is unknown nothing will happen +// let unknown_tower = get_random_user_id(); +// wt_client.set_tower_status(unknown_tower, TowerStatus::Reachable); +// assert!(!wt_client.towers.contains_key(&unknown_tower)); +// +// // If the tower is known, the status will be updated. +// let receipt = get_random_registration_receipt(); +// let tower_id = get_random_user_id(); +// wt_client +// .add_update_tower(tower_id, "talaia.watch", &receipt) +// .unwrap(); +// +// for status in [ +// TowerStatus::Reachable, +// TowerStatus::TemporaryUnreachable, +// TowerStatus::Unreachable, +// TowerStatus::SubscriptionError, +// TowerStatus::Misbehaving, +// ] { +// wt_client.set_tower_status(tower_id, status); +// assert_eq!(status, wt_client.get_tower_status(&tower_id).unwrap()); +// } +// } +// +// #[tokio::test] +// async fn test_add_appointment_receipt() { +// let keypair = cryptography::get_random_keypair(); +// let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); +// let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); +// let mut wt_client = WTClient::new( +// tmp_path.path().to_path_buf(), +// keypair.0, +// unbounded_channel().0, +// ) +// .await; +// +// let (tower_sk, tower_pk) = cryptography::get_random_keypair(); +// let tower_id = TowerId(tower_pk); +// +// 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.towers.contains_key(&tower_id)); +// +// // Add the tower to the state and try again +// let tower_info = TowerInfo::new( +// "talaia.watch".to_owned(), +// registration_receipt.available_slots(), +// registration_receipt.subscription_start(), +// registration_receipt.subscription_expiry(), +// HashMap::from([(locator, appointment_receipt.signature().unwrap())]), +// Vec::new(), +// Vec::new(), +// ); +// wt_client +// .add_update_tower(tower_id, &tower_info.net_addr, ®istration_receipt) +// .unwrap(); +// wt_client.add_appointment_receipt( +// tower_id, +// locator, +// registration_receipt.available_slots(), +// &appointment_receipt, +// ); +// +// assert!(wt_client.towers.contains_key(&tower_id)); +// assert_eq!( +// wt_client.towers.get(&tower_id).unwrap(), +// &TowerSummary::from(tower_info.clone()) +// ); +// assert_eq!(wt_client.load_tower_info(tower_id).unwrap(), tower_info); +// } +// +// #[tokio::test] +// async fn test_add_pending_appointment() { +// let keypair = cryptography::get_random_keypair(); +// let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); +// let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); +// let mut wt_client = WTClient::new( +// tmp_path.path().to_path_buf(), +// keypair.0, +// unbounded_channel().0, +// ) +// .await; +// +// let tower_id = get_random_user_id(); +// +// let registration_receipt = get_random_registration_receipt(); +// let appointment = generate_random_appointment(None); +// +// // If we call this on an unknown tower it will simply do nothing +// wt_client.add_pending_appointment(tower_id, &appointment); +// assert!(!wt_client.towers.contains_key(&tower_id)); +// +// // Add the tower to the state and try again +// let tower_info = TowerInfo::new( +// "talaia.watch".to_owned(), +// registration_receipt.available_slots(), +// registration_receipt.subscription_start(), +// registration_receipt.subscription_expiry(), +// HashMap::new(), +// vec![appointment.clone()], +// Vec::new(), +// ); +// +// wt_client +// .add_update_tower(tower_id, &tower_info.net_addr, ®istration_receipt) +// .unwrap(); +// wt_client.add_pending_appointment(tower_id, &appointment); +// +// assert!(wt_client.towers.contains_key(&tower_id)); +// assert_eq!( +// wt_client.towers.get(&tower_id).unwrap(), +// &TowerSummary::from(tower_info.clone()) +// ); +// // When towers data is loaded from the database, it is assumed to be reachable. +// assert_eq!( +// wt_client.load_tower_info(tower_id).unwrap(), +// tower_info.with_status(TowerStatus::TemporaryUnreachable) +// ); +// } +// +// #[tokio::test] +// async fn test_remove_pending_appointment() { +// let keypair = cryptography::get_random_keypair(); +// let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); +// let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); +// let mut wt_client = WTClient::new( +// tmp_path.path().to_path_buf(), +// keypair.0, +// unbounded_channel().0, +// ) +// .await; +// +// let tower_id = get_random_user_id(); +// +// let registration_receipt = get_random_registration_receipt(); +// let appointment = generate_random_appointment(None); +// +// // If we call this on an unknown tower it will simply do nothing +// wt_client.remove_pending_appointment(tower_id, appointment.locator); +// +// // Add the tower to the state and try again +// wt_client +// .add_update_tower(tower_id, "talaia.watch", ®istration_receipt) +// .unwrap(); +// wt_client.add_pending_appointment(tower_id, &appointment); +// +// wt_client.remove_pending_appointment(tower_id, appointment.locator); +// assert!(!wt_client +// .towers +// .get(&tower_id) +// .unwrap() +// .pending_appointments +// .contains(&appointment.locator)); +// // This bit is tested exhaustively in the Storage. +// assert!(!wt_client.storage.appointment_exists(appointment.locator)); +// } +// +// #[tokio::test] +// async fn test_add_invalid_appointment() { +// let keypair = cryptography::get_random_keypair(); +// let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); +// let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); +// let mut wt_client = WTClient::new( +// tmp_path.path().to_path_buf(), +// keypair.0, +// unbounded_channel().0, +// ) +// .await; +// +// let tower_id = get_random_user_id(); +// +// let registration_receipt = get_random_registration_receipt(); +// let appointment = generate_random_appointment(None); +// +// // If we call this on an unknown tower it will simply do nothing +// wt_client.add_invalid_appointment(tower_id, &appointment); +// assert!(!wt_client.towers.contains_key(&tower_id)); +// +// // Add the tower to the state and try again +// let tower_info = TowerInfo::new( +// "talaia.watch".to_owned(), +// registration_receipt.available_slots(), +// registration_receipt.subscription_start(), +// registration_receipt.subscription_expiry(), +// HashMap::new(), +// Vec::new(), +// vec![appointment.clone()], +// ); +// +// wt_client +// .add_update_tower(tower_id, &tower_info.net_addr, ®istration_receipt) +// .unwrap(); +// wt_client.add_invalid_appointment(tower_id, &appointment); +// +// assert!(wt_client.towers.contains_key(&tower_id)); +// assert_eq!( +// wt_client.towers.get(&tower_id).unwrap(), +// &TowerSummary::from(tower_info.clone()) +// ); +// assert_eq!(wt_client.load_tower_info(tower_id).unwrap(), tower_info); +// } +// +// #[tokio::test] +// async fn test_move_pending_appointment_to_invalid() { +// let keypair = cryptography::get_random_keypair(); +// let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); +// let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); +// let mut wt_client = WTClient::new( +// tmp_path.path().to_path_buf(), +// keypair.0, +// unbounded_channel().0, +// ) +// .await; +// +// let tower_id = get_random_user_id(); +// +// let registration_receipt = get_random_registration_receipt(); +// let appointment = generate_random_appointment(None); +// +// wt_client +// .add_update_tower(tower_id, "talaia.watch", ®istration_receipt) +// .unwrap(); +// wt_client.add_pending_appointment(tower_id, &appointment); +// +// // Check that the appointment can be moved from pending to invalid +// wt_client.add_invalid_appointment(tower_id, &appointment); +// wt_client.remove_pending_appointment(tower_id, appointment.locator); +// +// assert!(!wt_client +// .towers +// .get(&tower_id) +// .unwrap() +// .pending_appointments +// .contains(&appointment.locator)); +// assert!(wt_client +// .towers +// .get(&tower_id) +// .unwrap() +// .invalid_appointments +// .contains(&appointment.locator)); +// assert!(!wt_client +// .storage +// .load_appointment_locators(tower_id, crate::AppointmentStatus::Pending) +// .contains(&appointment.locator)); +// assert!(wt_client +// .storage +// .load_appointment_locators(tower_id, crate::AppointmentStatus::Invalid) +// .contains(&appointment.locator)); +// assert!(wt_client.storage.appointment_exists(appointment.locator)); +// } +// +// #[tokio::test] +// async fn test_move_pending_appointment_to_invalid_multiple_towers() { +// // Check that moving an appointment from pending to invalid can be done even if multiple towers have a reference to it +// let keypair = cryptography::get_random_keypair(); +// let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); +// let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); +// let mut wt_client = WTClient::new( +// tmp_path.path().to_path_buf(), +// keypair.0, +// unbounded_channel().0, +// ) +// .await; +// +// let tower_id = get_random_user_id(); +// let another_tower_id = get_random_user_id(); +// 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) +// .unwrap(); +// wt_client +// .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); +// +// // Check that the appointment can be moved from pending to invalid +// wt_client.add_invalid_appointment(tower_id, &appointment); +// wt_client.remove_pending_appointment(tower_id, appointment.locator); +// +// // TOWER_ID CHECKS +// assert!(!wt_client +// .towers +// .get(&tower_id) +// .unwrap() +// .pending_appointments +// .contains(&appointment.locator)); +// assert!(wt_client +// .towers +// .get(&tower_id) +// .unwrap() +// .invalid_appointments +// .contains(&appointment.locator)); +// assert!(!wt_client +// .storage +// .load_appointment_locators(tower_id, crate::AppointmentStatus::Pending) +// .contains(&appointment.locator)); +// assert!(wt_client +// .storage +// .load_appointment_locators(tower_id, crate::AppointmentStatus::Invalid) +// .contains(&appointment.locator)); +// +// // ANOTHER_TOWER_ID CHECKS +// assert!(wt_client +// .towers +// .get(&another_tower_id) +// .unwrap() +// .pending_appointments +// .contains(&appointment.locator)); +// assert!(!wt_client +// .towers +// .get(&another_tower_id) +// .unwrap() +// .invalid_appointments +// .contains(&appointment.locator)); +// assert!(wt_client +// .storage +// .load_appointment_locators(another_tower_id, crate::AppointmentStatus::Pending) +// .contains(&appointment.locator)); +// assert!(!wt_client +// .storage +// .load_appointment_locators(another_tower_id, crate::AppointmentStatus::Invalid) +// .contains(&appointment.locator)); +// +// // GENERAL +// assert!(wt_client.storage.appointment_exists(appointment.locator)); +// } +// +// #[tokio::test] +// async fn test_flag_misbehaving_tower() { +// let keypair = cryptography::get_random_keypair(); +// let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); +// let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); +// let mut wt_client = WTClient::new( +// tmp_path.path().to_path_buf(), +// keypair.0, +// unbounded_channel().0, +// ) +// .await; +// +// let (tower_sk, tower_pk) = cryptography::get_random_keypair(); +// let tower_id = TowerId(tower_pk); +// +// // If we call this on an unknown tower it will simply do nothing +// let appointment = generate_random_appointment(None); +// let receipt = get_random_appointment_receipt(tower_sk); +// let proof = MisbehaviorProof::new(appointment.locator, receipt, get_random_user_id()); +// wt_client.flag_misbehaving_tower(tower_id, proof.clone()); +// assert!(!wt_client.towers.contains_key(&tower_id)); +// +// // // Add the tower to the state and try again +// let registration_receipt = get_random_registration_receipt(); +// wt_client +// .add_update_tower(tower_id, "talaia.watch", ®istration_receipt) +// .unwrap(); +// wt_client.flag_misbehaving_tower(tower_id, proof.clone()); +// +// // Check data in memory +// let tower_summary = wt_client.towers.get(&tower_id); +// assert!(tower_summary.is_some()); +// assert!(tower_summary.unwrap().status.is_misbehaving()); +// +// // Check data in DB +// let loaded_info = wt_client.load_tower_info(tower_id).unwrap(); +// assert!(loaded_info.status.is_misbehaving()); +// assert_eq!(loaded_info.misbehaving_proof, Some(proof)); +// assert!(loaded_info.appointments.contains_key(&appointment.locator)); +// } +// +// #[tokio::test] +// async fn test_remove_tower() { +// let keypair = cryptography::get_random_keypair(); +// let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); +// let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); +// let mut wt_client = WTClient::new( +// tmp_path.path().to_path_buf(), +// keypair.0, +// 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".to_owned(), +// 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, &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!(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 +// 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 +// .storage +// .appointment_receipt_exists(locator, tower_id)); +// +// // Remove and check both the tower and the appointment +// wt_client.remove_tower(tower_id).unwrap(); +// assert!(wt_client.load_tower_info(tower_id).is_none()); +// assert!(!wt_client.towers.contains_key(&tower_id)); +// assert!(!wt_client +// .storage +// .appointment_receipt_exists(locator, tower_id)); +// } +// +// #[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 keypair = cryptography::get_random_keypair(); +// let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); +// let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); +// let mut wt_client = WTClient::new( +// tmp_path.path().to_path_buf(), +// keypair.0, +// 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); +// +// wt_client +// .add_update_tower(tower1_id, "talaia.watch", &receipt) +// .unwrap(); +// wt_client +// .add_update_tower(tower2_id, "talaia.watch", &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 +// .storage +// .appointment_receipt_exists(locator, tower1_id)); +// assert!(wt_client +// .storage +// .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!(wt_client.load_tower_info(tower1_id).is_none()); +// +// assert!(!wt_client +// .storage +// .appointment_receipt_exists(locator, tower1_id)); +// assert!(wt_client +// .storage +// .appointment_receipt_exists(locator, tower2_id)); +// } +// +// #[tokio::test] +// async fn test_remove_inexistent_tower() { +// let keypair = cryptography::get_random_keypair(); +// let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); +// let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); +// let mut wt_client = WTClient::new( +// tmp_path.path().to_path_buf(), +// keypair.0, +// unbounded_channel().0, +// ) +// .await; +// +// assert!(matches!( +// wt_client.remove_tower(get_random_user_id()), +// Err(DBError::NotFound) +// )); +// } +// } From 91e8e0ac457834441eb8bd2ff1c1d03808716cd4 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Sun, 9 Feb 2025 09:55:49 +0100 Subject: [PATCH 44/85] in memory kv store p1 Signed-off-by: dzdidi --- teos-ldk-client/src/storage/kv.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/teos-ldk-client/src/storage/kv.rs b/teos-ldk-client/src/storage/kv.rs index 28ca1101..f298a91f 100644 --- a/teos-ldk-client/src/storage/kv.rs +++ b/teos-ldk-client/src/storage/kv.rs @@ -35,15 +35,18 @@ const NS_INVALID_APPOINTMENTS: &str = "invalid_appointments"; const NS_MISBEHAVIOR_PROOFS: &str = "misbehavior_proofs"; pub struct Storage { - store: T, + store: Box, sk: Vec, } // Implement methods to convert TowerInfo to vec and vice versa -impl Storage { - pub(crate) fn new(store: T, sk: Vec) -> Result { - Ok(Storage { store, sk }) +impl Storage { + pub fn new(store: impl T, sk: Vec) -> Result { + Ok(Storage { + store: Box::new(store), + sk + }) } /// Creates a composite key from multiple components From 71f715ecb5d07ecfdf56950bbaa6b71abd96872b Mon Sep 17 00:00:00 2001 From: dzdidi Date: Tue, 11 Feb 2025 15:46:17 +0100 Subject: [PATCH 45/85] storage trait draft Signed-off-by: dzdidi --- Cargo.lock | 147 +++++++++++++++- teos-ldk-client/Cargo.toml | 2 +- teos-ldk-client/src/retrier.rs | 1 + teos-ldk-client/src/storage/dbm.rs | 226 ++++++++++++++----------- teos-ldk-client/src/storage/kv.rs | 56 +++--- teos-ldk-client/src/storage/mod.rs | 6 +- teos-ldk-client/src/storage/storage.rs | 159 +++++++++++++++++ teos-ldk-client/src/wt_client.rs | 9 +- watchtower-plugin/src/lib.rs | 4 +- 9 files changed, 462 insertions(+), 148 deletions(-) create mode 100644 teos-ldk-client/src/storage/storage.rs diff --git a/Cargo.lock b/Cargo.lock index cd68eca0..3f1fd387 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + [[package]] name = "ahash" version = "0.7.8" @@ -466,21 +476,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fee7ad89dc1128635074c268ee661f90c3f7e83d9fd12910608c36b47d6c3412" dependencies = [ "cfg-if 1.0.0", - "cipher", + "cipher 0.3.0", "cpufeatures 0.1.5", "zeroize", ] +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if 1.0.0", + "cipher 0.4.4", + "cpufeatures 0.2.17", +] + [[package]] name = "chacha20poly1305" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1580317203210c517b6d44794abfbe600698276db18127e37ad3e69bf5e848e5" dependencies = [ - "aead", - "chacha20", - "cipher", - "poly1305", + "aead 0.4.3", + "chacha20 0.7.1", + "cipher 0.3.0", + "poly1305 0.7.2", + "zeroize", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead 0.5.2", + "chacha20 0.9.1", + "cipher 0.4.4", + "poly1305 0.8.0", "zeroize", ] @@ -499,6 +533,17 @@ dependencies = [ "generic-array", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + [[package]] name = "clap" version = "2.34.0" @@ -590,6 +635,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] @@ -1441,6 +1487,15 @@ dependencies = [ "hashbrown 0.15.2", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "instant" version = "0.1.13" @@ -2147,7 +2202,18 @@ checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" dependencies = [ "cpufeatures 0.2.17", "opaque-debug", - "universal-hash", + "universal-hash 0.4.0", +] + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash 0.5.1", ] [[package]] @@ -2171,7 +2237,7 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -2316,6 +2382,17 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.0", + "zerocopy 0.8.17", +] + [[package]] name = "rand_chacha" version = "0.2.2" @@ -2336,6 +2413,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.0", +] + [[package]] name = "rand_core" version = "0.3.1" @@ -2369,6 +2456,16 @@ dependencies = [ "getrandom 0.2.15", ] +[[package]] +name = "rand_core" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" +dependencies = [ + "getrandom 0.3.1", + "zerocopy 0.8.17", +] + [[package]] name = "rand_hc" version = "0.2.0" @@ -3049,7 +3146,7 @@ name = "teos-common" version = "0.2.0" dependencies = [ "bitcoin", - "chacha20poly1305", + "chacha20poly1305 0.8.0", "hex", "lightning", "prost", @@ -3068,11 +3165,13 @@ dependencies = [ "backoff", "bincode", "bitcoin", + "chacha20poly1305 0.10.1", "hex", "home", "lightning", "log", "mockito", + "rand 0.9.0", "reqwest", "rusqlite", "serde", @@ -3552,6 +3651,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "untrusted" version = "0.9.0" @@ -4053,7 +4162,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa91407dacce3a68c56de03abe2760159582b846c6a4acd2f456618087f12713" +dependencies = [ + "zerocopy-derive 0.8.17", ] [[package]] @@ -4067,6 +4185,17 @@ dependencies = [ "syn 2.0.96", ] +[[package]] +name = "zerocopy-derive" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06718a168365cad3d5ff0bb133aad346959a2074bd4a85c121255a11304a8626" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "zerofrom" version = "0.1.5" diff --git a/teos-ldk-client/Cargo.toml b/teos-ldk-client/Cargo.toml index c3f95e9c..80643697 100644 --- a/teos-ldk-client/Cargo.toml +++ b/teos-ldk-client/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [features] -default = ["kv"] +default = ["sqlite"] sqlite = [] kv = [] diff --git a/teos-ldk-client/src/retrier.rs b/teos-ldk-client/src/retrier.rs index c097c299..ff4a4ad4 100644 --- a/teos-ldk-client/src/retrier.rs +++ b/teos-ldk-client/src/retrier.rs @@ -1,3 +1,4 @@ +use crate::storage::storage::Persister; use std::collections::{HashMap, HashSet}; use std::fmt::Display; use std::sync::{Arc, Mutex}; diff --git a/teos-ldk-client/src/storage/dbm.rs b/teos-ldk-client/src/storage/dbm.rs index 14a6a6cd..5570393b 100644 --- a/teos-ldk-client/src/storage/dbm.rs +++ b/teos-ldk-client/src/storage/dbm.rs @@ -6,12 +6,19 @@ use std::path::PathBuf; use rusqlite::{params, Connection, Error as SqliteError}; use teos_common::appointment::{Appointment, Locator}; -use teos_common::dbm::{DatabaseConnection, DatabaseManager, Error}; +use teos_common::dbm::{DatabaseConnection, DatabaseManager}; use teos_common::receipts::{AppointmentReceipt, RegistrationReceipt}; use teos_common::{TowerId, UserId}; +use crate::storage::storage::{Persister, StorageError}; + use crate::{AppointmentStatus, MisbehaviorProof, TowerInfo, TowerStatus, TowerSummary}; -pub use teos_common::dbm::Error as DBError; + +impl From for StorageError { + fn from(error: SqliteError) -> Self { + StorageError::Other(error.to_string()) + } +} const TABLES: [&str; 8] = [ "CREATE TABLE IF NOT EXISTS towers ( @@ -112,30 +119,99 @@ impl DBM { Ok(dbm) } + /// Checks whether a misbehaving proof exists for a given tower. + fn exists_misbehaving_proof(&self, tower_id: TowerId) -> bool { + let mut misbehaving_stmt = self + .connection + .prepare("SELECT tower_id FROM misbehaving_proofs WHERE tower_id = ?") + .unwrap(); + misbehaving_stmt.exists([tower_id.to_vec()]).unwrap() + } + + /// Loads the misbehaving proof for a given tower from the database (if found). + 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 = ?") + .unwrap(); + + misbehaving_stmt + .query_row([tower_id.to_vec()], |row| { + let locator = Locator::from_slice(&row.get::<_, Vec>(0).unwrap()).unwrap(); + let recovered_id = TowerId::from_slice(&row.get::<_, Vec>(1).unwrap()).unwrap(); + Ok((locator, recovered_id)) + }) + .map(|(locator, recovered_id)| { + let mut receipt_stmt = self + .connection + .prepare( + "SELECT start_block, user_signature, tower_signature + FROM appointment_receipts + WHERE locator = ?1 AND tower_id = ?2", + ) + .unwrap(); + let receipt = receipt_stmt + .query_row([locator.to_vec(), tower_id.to_vec()], |row| { + let start_block = row.get::<_, u32>(0).unwrap(); + let user_signature = row.get::<_, String>(1).unwrap(); + let tower_signature = row.get::<_, String>(2).unwrap(); + Ok(AppointmentReceipt::with_signature( + user_signature, + start_block, + tower_signature, + )) + }) + .unwrap(); + MisbehaviorProof::new(locator, receipt, recovered_id) + }) + .ok() + } + + /// Stores an appointment into the database. + /// + /// Appointments are only stored as a whole when they are pending or invalid. + /// Accepted appointments are simplified in the form of an appointment receipt. + fn store_appointment( + tx: &rusqlite::Transaction, + appointment: &Appointment, + ) -> Result { + tx.execute( + "INSERT INTO appointments (locator, encrypted_blob, to_self_delay) VALUES (?1, ?2, ?3)", + params![ + appointment.locator.to_vec(), + appointment.encrypted_blob, + appointment.to_self_delay + ], + ) + } + +} + +impl Persister for DBM { /// Stores a tower record into the database alongside the corresponding registration receipt. /// /// This function MUST be guarded against inserting duplicate (tower_id, subscription_expiry) pairs. /// This is currently done in WTClient::add_update_tower. - pub fn store_tower_record( + fn store_tower_record( &mut self, tower_id: TowerId, net_addr: &str, receipt: &RegistrationReceipt, - ) -> Result<(), Error> { + ) -> Result<(), StorageError> { let tx = self.get_mut_connection().transaction().unwrap(); tx.execute( "INSERT INTO towers (tower_id, net_addr, available_slots) VALUES (?1, ?2, ?3) ON CONFLICT (tower_id) DO UPDATE SET net_addr = ?2, available_slots = ?3", params![tower_id.to_vec(), net_addr, receipt.available_slots()], - ) - .map_err(Error::Unknown)?; + )?; tx.execute( "INSERT INTO registration_receipts (tower_id, available_slots, subscription_start, subscription_expiry, signature) VALUES (?1, ?2, ?3, ?4, ?5)", - params![tower_id.to_vec(), receipt.available_slots(), receipt.subscription_start(), receipt.subscription_expiry(), receipt.signature()]).map_err( Error::Unknown)?; + params![tower_id.to_vec(), receipt.available_slots(), receipt.subscription_start(), receipt.subscription_expiry(), receipt.signature()])?; - tx.commit().map_err(Error::Unknown) + tx.commit()?; + Ok(()) } /// Loads a tower record from the database. @@ -143,7 +219,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) -> Option { + 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 @@ -184,7 +260,7 @@ impl DBM { /// Loads the latest registration receipt for a given tower. /// /// Latests is determined by the one with the `subscription_expiry` further into the future. - pub fn load_registration_receipt( + fn load_registration_receipt( &self, tower_id: TowerId, user_id: UserId, @@ -217,13 +293,16 @@ impl DBM { /// /// 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> { + fn remove_tower_record(&self, tower_id: TowerId) -> Result<(), StorageError> { let query = "DELETE FROM towers WHERE tower_id=?"; - self.remove_data(query, params![tower_id.to_vec()]) + match self.connection.execute(query, params![tower_id.to_vec()]) { + Ok(_) => Ok(()), + Err(e) => Err(StorageError::Other(e.to_string())), + } } /// Loads all tower records from the database. - pub fn load_towers(&self) -> HashMap { + fn load_towers(&self) -> HashMap { let mut towers = HashMap::new(); let mut stmt = self .connection @@ -271,13 +350,13 @@ impl DBM { } /// Stores an appointments receipt into the database representing an appointment accepted by a given tower. - pub fn store_appointment_receipt( + fn store_appointment_receipt( &mut self, tower_id: TowerId, locator: Locator, available_slots: u32, receipt: &AppointmentReceipt, - ) -> Result<(), SqliteError> { + ) -> Result<(), StorageError> { let tx = self.get_mut_connection().transaction().unwrap(); tx.execute( "INSERT INTO appointment_receipts (locator, tower_id, start_block, user_signature, tower_signature) @@ -294,11 +373,12 @@ impl DBM { "UPDATE towers SET available_slots=?1 WHERE tower_id=?2", params![available_slots, tower_id.to_vec()], )?; - tx.commit() + tx.commit()?; + Ok(()) } /// Loads a given appointment receipt of a given tower from the database. - pub fn load_appointment_receipt( + fn load_appointment_receipt( &self, tower_id: TowerId, locator: Locator, @@ -326,7 +406,7 @@ impl DBM { /// /// 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. - pub fn load_appointment_receipts(&self, tower_id: TowerId) -> HashMap { + fn load_appointment_receipts(&self, tower_id: TowerId) -> HashMap { let mut receipts = HashMap::new(); let mut stmt = self .connection @@ -348,7 +428,7 @@ impl DBM { /// /// The loaded locators can be loaded either from appointment_receipts, pending_appointments or invalid_appointments /// depending on `status`. - pub fn load_appointment_locators( + fn load_appointment_locators( &self, tower_id: TowerId, status: AppointmentStatus, @@ -375,7 +455,7 @@ impl DBM { } /// Loads an appointment from the database. - pub fn load_appointment(&self, locator: Locator) -> Option { + fn load_appointment(&self, locator: Locator) -> Option { let mut stmt = self .connection .prepare("SELECT encrypted_blob, to_self_delay FROM appointments WHERE locator = ?") @@ -390,34 +470,16 @@ impl DBM { .ok() } - /// Stores an appointment into the database. - /// - /// Appointments are only stored as a whole when they are pending or invalid. - /// Accepted appointments are simplified in the form of an appointment receipt. - fn store_appointment( - tx: &rusqlite::Transaction, - appointment: &Appointment, - ) -> Result { - tx.execute( - "INSERT INTO appointments (locator, encrypted_blob, to_self_delay) VALUES (?1, ?2, ?3)", - params![ - appointment.locator.to_vec(), - appointment.encrypted_blob, - appointment.to_self_delay - ], - ) - } - /// Stores a pending appointment into the database. /// /// A pending appointment is an appointment that was sent to a tower when it was unreachable. /// This data is stored so it can be resent once the tower comes back online. /// Internally calls [Self::store_appointment]. - pub fn store_pending_appointment( + fn store_pending_appointment( &mut self, tower_id: TowerId, appointment: &Appointment, - ) -> Result<(), SqliteError> { + ) -> Result<(), StorageError> { let tx = self.get_mut_connection().transaction().unwrap(); // If the appointment already exists (because it was added by another tower as either pending or invalid) we simply @@ -428,17 +490,19 @@ impl DBM { params![appointment.locator.to_vec(), tower_id.to_vec(),], )?; - tx.commit() + tx.commit()?; + + Ok(()) } /// Removes a pending appointment from the database. /// /// If the pending appointment is the only instance of the appointment, the appointment will also be deleted form the appointments table. - pub fn delete_pending_appointment( + fn delete_pending_appointment( &mut self, tower_id: TowerId, locator: Locator, - ) -> Result<(), SqliteError> { + ) -> Result<(), StorageError> { // We will delete data from pending_appointments or from appointments depending on whether the later has a single reference // to it or not. If that's the case, deleting the entry from appointments will trigger a cascade deletion of the entry in pending. // If there are other references, this will be deleted when removing the last one. @@ -474,7 +538,9 @@ impl DBM { params![locator.to_vec(), tower_id.to_vec()], )?; }; - tx.commit() + tx.commit()?; + + Ok(()) } /// Stores an invalid appointment into the database. @@ -482,11 +548,11 @@ impl DBM { /// An invalid appointment is an appointment that was rejected by the tower. /// Storing this data may allow us to see what was the issue and send the data later on. /// Internally calls [Self::store_appointment]. - pub fn store_invalid_appointment( + fn store_invalid_appointment( &mut self, tower_id: TowerId, appointment: &Appointment, - ) -> Result<(), SqliteError> { + ) -> Result<(), StorageError> { let tx = self.get_mut_connection().transaction().unwrap(); // If the appointment already exists (because it was added by another tower as either pending or invalid) we simply @@ -497,14 +563,16 @@ impl DBM { params![appointment.locator.to_vec(), tower_id.to_vec(),], )?; - tx.commit() + tx.commit()?; + + Ok(()) } /// Loads non finalized appointments from the database for a given tower based on a status flag. /// /// This is meant to be used only for pending and invalid appointments, if the method is called for /// accepted appointment, an empty collection will be returned. - pub fn load_appointments( + fn load_appointments( &self, tower_id: TowerId, status: AppointmentStatus, @@ -537,11 +605,11 @@ impl DBM { /// /// A misbehaving proof is proof that the tower has signed an appointment using a key different /// than the one advertised to the user when they registered. - pub fn store_misbehaving_proof( + fn store_misbehaving_proof( &mut self, tower_id: TowerId, proof: &MisbehaviorProof, - ) -> Result<(), SqliteError> { + ) -> Result<(), StorageError> { let tx = self.get_mut_connection().transaction().unwrap(); tx.execute( "INSERT INTO appointment_receipts (tower_id, locator, start_block, user_signature, tower_signature) @@ -563,55 +631,9 @@ impl DBM { ], )?; - tx.commit() - } - - /// Loads the misbehaving proof for a given tower from the database (if found). - 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 = ?") - .unwrap(); + tx.commit()?; - misbehaving_stmt - .query_row([tower_id.to_vec()], |row| { - let locator = Locator::from_slice(&row.get::<_, Vec>(0).unwrap()).unwrap(); - let recovered_id = TowerId::from_slice(&row.get::<_, Vec>(1).unwrap()).unwrap(); - Ok((locator, recovered_id)) - }) - .map(|(locator, recovered_id)| { - let mut receipt_stmt = self - .connection - .prepare( - "SELECT start_block, user_signature, tower_signature - FROM appointment_receipts - WHERE locator = ?1 AND tower_id = ?2", - ) - .unwrap(); - let receipt = receipt_stmt - .query_row([locator.to_vec(), tower_id.to_vec()], |row| { - let start_block = row.get::<_, u32>(0).unwrap(); - let user_signature = row.get::<_, String>(1).unwrap(); - let tower_signature = row.get::<_, String>(2).unwrap(); - Ok(AppointmentReceipt::with_signature( - user_signature, - start_block, - tower_signature, - )) - }) - .unwrap(); - MisbehaviorProof::new(locator, receipt, recovered_id) - }) - .ok() - } - - /// Checks whether a misbehaving proof exists for a given tower. - fn exists_misbehaving_proof(&self, tower_id: TowerId) -> bool { - let mut misbehaving_stmt = self - .connection - .prepare("SELECT tower_id FROM misbehaving_proofs WHERE tower_id = ?") - .unwrap(); - misbehaving_stmt.exists([tower_id.to_vec()]).unwrap() + Ok(()) } } @@ -821,10 +843,10 @@ mod tests { 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) - )); + match dbm.remove_tower_record(get_random_user_id()) { + Ok(_) => panic!("Tower record was removed when it shouldn't have"), + Err(_) => {} + } } #[test] diff --git a/teos-ldk-client/src/storage/kv.rs b/teos-ldk-client/src/storage/kv.rs index f298a91f..9e138f01 100644 --- a/teos-ldk-client/src/storage/kv.rs +++ b/teos-ldk-client/src/storage/kv.rs @@ -39,6 +39,20 @@ pub struct Storage { sk: Vec, } +/// Creates a composite key from multiple components +fn make_key(components: &[&str]) -> String { + components.join(":") +} + +/// Gets the appropriate namespace based on appointment status +fn get_appointment_namespace(status: AppointmentStatus) -> &'static str { + match status { + AppointmentStatus::Accepted => NS_APPOINTMENT_RECEIPTS, + AppointmentStatus::Pending => NS_PENDING_APPOINTMENTS, + AppointmentStatus::Invalid => NS_INVALID_APPOINTMENTS, + } +} + // Implement methods to convert TowerInfo to vec and vice versa impl Storage { @@ -49,20 +63,6 @@ impl Storage { }) } - /// Creates a composite key from multiple components - fn make_key(components: &[&str]) -> String { - components.join(":") - } - - /// Gets the appropriate namespace based on appointment status - fn get_appointment_namespace(status: AppointmentStatus) -> &'static str { - match status { - AppointmentStatus::Accepted => NS_APPOINTMENT_RECEIPTS, - AppointmentStatus::Pending => NS_PENDING_APPOINTMENTS, - AppointmentStatus::Invalid => NS_INVALID_APPOINTMENTS, - } - } - /// Stores a tower record into the database alongside the corresponding registration receipt. /// /// This function MUST be guarded against inserting duplicate (tower_id, subscription_expiry) pairs. @@ -73,7 +73,7 @@ impl Storage { net_addr: &str, receipt: &RegistrationReceipt, ) -> Result<(), DBError> { - let key = Self::make_key(&[&tower_id.to_string()]); + let key = make_key(&[&tower_id.to_string()]); let value = TowerInfo::new( net_addr.to_string(), @@ -99,7 +99,7 @@ impl Storage { /// 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) -> Option { - let key = Self::make_key(&[&tower_id.to_string()]); + let key = make_key(&[&tower_id.to_string()]); let value = match self.store.read(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key) { Ok(v) => v, Err(_) => return None, @@ -122,7 +122,7 @@ impl Storage { /// 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<(), DBError> { - let key = Self::make_key(&[&tower_id.to_string()]); + let key = make_key(&[&tower_id.to_string()]); // primary namespace: "watchtower" // secondary namespance: "tower_record" // key: @@ -144,7 +144,7 @@ impl Storage { tower_id: TowerId, user_id: UserId, ) -> Option { - // let key = Self::make_key(&[&tower_id.to_string()]); + // let key = make_key(&[&tower_id.to_string()]); // // self.store // .read(PRIMARY_NAMESPACE, NS_REGISTRATION_RECEIPTS, &key); @@ -159,7 +159,7 @@ impl Storage { available_slots: u32, receipt: &AppointmentReceipt, ) -> Result<(), DBError> { - let key = Self::make_key(&[&tower_id.to_string(), &locator.to_string()]); + let key = make_key(&[&tower_id.to_string(), &locator.to_string()]); // primary namespace: "watchtower" // secondary namespance: "appointment_receipt" // key: + @@ -174,7 +174,7 @@ impl Storage { tower_id: TowerId, locator: Locator, ) -> Option { - // let key = Self::make_key(&[&tower_id.to_string(), &locator.to_string()]); + // let key = make_key(&[&tower_id.to_string(), &locator.to_string()]); // // primary namespace: "watchtower" // // secondary namespance: "appointment_receipt" // self.store @@ -220,7 +220,7 @@ impl Storage { /// Appointments are only stored as a whole when they are pending or invalid. /// Accepted appointments are simplified in the form of an appointment receipt. fn store_appointment(&self, appointment: &Appointment) -> Result { - let key = Self::make_key(&[&appointment.locator.to_string()]); + let key = make_key(&[&appointment.locator.to_string()]); // primary namespace: "watchtower" // secondary namespance: "appointment" // key: @@ -238,7 +238,7 @@ impl Storage { tower_id: TowerId, appointment: &Appointment, ) -> Result<(), DBError> { - let key = Self::make_key(&[&tower_id.to_string(), &appointment.locator.to_string()]); + let key = make_key(&[&tower_id.to_string(), &appointment.locator.to_string()]); // primary namespace: "watchtower" // secondary namespance: "pending_appointment" // key: @@ -254,7 +254,7 @@ impl Storage { tower_id: TowerId, locator: Locator, ) -> Result<(), DBError> { - let key = Self::make_key(&[&tower_id.to_string(), &locator.to_string()]); + let key = make_key(&[&tower_id.to_string(), &locator.to_string()]); // primary namespace: "watchtower" // secondary namespance: "pending_appointment" // key: ? @@ -272,7 +272,7 @@ impl Storage { tower_id: TowerId, appointment: &Appointment, ) -> Result<(), DBError> { - let key = Self::make_key(&[&tower_id.to_string(), &appointment.locator.to_string()]); + let key = make_key(&[&tower_id.to_string(), &appointment.locator.to_string()]); // primary namespace: "watchtower" // secondary namespance: "invalid_appointment" // key: ? @@ -289,7 +289,7 @@ impl Storage { tower_id: TowerId, status: AppointmentStatus, ) -> Vec { - let key = Self::make_key(&[&tower_id.to_string()]); + let key = make_key(&[&tower_id.to_string()]); // primary namespace: "watchtower" // secondary namespance: "{status}_appointment_receipt" // key: ? @@ -306,7 +306,7 @@ impl Storage { tower_id: TowerId, proof: &MisbehaviorProof, ) -> Result<(), DBError> { - let key = Self::make_key(&[&tower_id.to_string()]); + let key = make_key(&[&tower_id.to_string()]); // primary namespace: "watchtower" // secondary namespance: "misbehaving_proof" // key: ? @@ -316,7 +316,7 @@ impl Storage { /// Loads the misbehaving proof for a given tower from the database (if found). fn load_misbehaving_proof(&self, tower_id: TowerId) -> Option { - let key = Self::make_key(&[&tower_id.to_string()]); + let key = make_key(&[&tower_id.to_string()]); // primary namespace: "watchtower" // secondary namespance: "misbehaving_proof" // key: ? @@ -325,7 +325,7 @@ impl Storage { /// Checks whether a misbehaving proof exists for a given tower. fn exists_misbehaving_proof(&self, tower_id: TowerId) -> bool { - let key = Self::make_key(&[&tower_id.to_string()]); + let key = make_key(&[&tower_id.to_string()]); // primary namespace: "watchtower" // secondary namespance: "misbehaving_proof" // key: ? diff --git a/teos-ldk-client/src/storage/mod.rs b/teos-ldk-client/src/storage/mod.rs index 9ce6f816..87ac5901 100644 --- a/teos-ldk-client/src/storage/mod.rs +++ b/teos-ldk-client/src/storage/mod.rs @@ -1,3 +1,5 @@ +pub mod storage; + #[cfg(all(not(feature = "sqlite"), not(feature = "kv")))] compile_error!( "No storage backend enabled. Please enable one of the following features: sqlite, kv" @@ -8,8 +10,8 @@ compile_error!("Only one of 'sqlite' or 'kv' features can be enabled"); #[cfg(feature = "sqlite")] mod dbm; -#[cfg(feature = "sqlite")] -pub use dbm::DBError; +// #[cfg(feature = "sqlite")] +// pub use dbm::DBError; #[cfg(feature = "sqlite")] pub use dbm::DBM as Storage; diff --git a/teos-ldk-client/src/storage/storage.rs b/teos-ldk-client/src/storage/storage.rs new file mode 100644 index 00000000..c48268f8 --- /dev/null +++ b/teos-ldk-client/src/storage/storage.rs @@ -0,0 +1,159 @@ +use std::collections::{HashMap, HashSet}; +use std::fmt; + +use teos_common::appointment::{Appointment, Locator}; +use teos_common::receipts::{AppointmentReceipt, RegistrationReceipt}; +use teos_common::{TowerId, UserId}; + +use crate::{AppointmentStatus, MisbehaviorProof, TowerInfo, TowerSummary}; + +/// A general storage error type that can be used across different storage implementations +#[derive(Debug)] +pub enum StorageError { + /// Error when storing data + StoreError(String), + /// Error when retrieving data + RetrievalError(String), + /// Error when data is not found + NotFound(String), + /// Any other storage-related error + Other(String), +} + +impl fmt::Display for StorageError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + StorageError::StoreError(msg) => write!(f, "Storage store error: {}", msg), + StorageError::RetrievalError(msg) => write!(f, "Storage retrieval error: {}", msg), + StorageError::NotFound(msg) => write!(f, "Data not found: {}", msg), + StorageError::Other(msg) => write!(f, "Storage error: {}", msg), + } + } +} + +impl std::error::Error for StorageError {} + +/// Trait defining the interface for database operations +pub trait Persister { + /// Stores a tower record into the database alongside the corresponding registration receipt. + /// + /// This function MUST be guarded against inserting duplicate (tower_id, subscription_expiry) pairs. + /// This is currently done in WTClient::add_update_tower. + fn store_tower_record( + &mut self, + tower_id: TowerId, + net_addr: &str, + receipt: &RegistrationReceipt, + ) -> Result<(), StorageError>; + + /// Loads a tower record from the database. + /// + /// 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. + fn load_tower_record(&self, tower_id: TowerId) -> Option; + + /// Loads the latest registration receipt for a given tower. + /// + /// Latests is determined by the one with the `subscription_expiry` further into the future. + fn load_registration_receipt( + &self, + tower_id: TowerId, + user_id: UserId, + ) -> Option; + + /// 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. + fn remove_tower_record(&self, tower_id: TowerId) -> Result<(), StorageError>; + + /// Loads all tower records from the database. + fn load_towers(&self) -> HashMap; + + /// Stores an appointments receipt into the database representing an appointment accepted by a given tower. + fn store_appointment_receipt( + &mut self, + tower_id: TowerId, + locator: Locator, + available_slots: u32, + receipt: &AppointmentReceipt, + ) -> Result<(), StorageError>; + + /// Loads a given appointment receipt of a given tower from the database. + fn load_appointment_receipt( + &self, + tower_id: TowerId, + locator: Locator, + ) -> Option; + + /// 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. + fn load_appointment_receipts(&self, tower_id: TowerId) -> HashMap; + + /// Loads a collection of locators from the database entry associated to a given tower. + /// + /// The loaded locators can be loaded either from appointment_receipts, pending_appointments or invalid_appointments + /// depending on `status`. + fn load_appointment_locators( + &self, + tower_id: TowerId, + status: AppointmentStatus, + ) -> HashSet; + + /// Loads an appointment from the database. + fn load_appointment(&self, locator: Locator) -> Option; + + /// Stores a pending appointment into the database. + /// + /// A pending appointment is an appointment that was sent to a tower when it was unreachable. + /// This data is stored so it can be resent once the tower comes back online. + /// Internally calls [Self::store_appointment]. + fn store_pending_appointment( + &mut self, + tower_id: TowerId, + appointment: &Appointment, + ) -> Result<(), StorageError>; + + /// Removes a pending appointment from the database. + /// + /// If the pending appointment is the only instance of the appointment, the appointment will also be deleted form the appointments table. + fn delete_pending_appointment( + &mut self, + tower_id: TowerId, + locator: Locator, + ) -> Result<(), StorageError>; + + /// Stores an invalid appointment into the database. + /// + /// An invalid appointment is an appointment that was rejected by the tower. + /// Storing this data may allow us to see what was the issue and send the data later on. + /// Internally calls [Self::store_appointment]. + fn store_invalid_appointment( + &mut self, + tower_id: TowerId, + appointment: &Appointment, + ) -> Result<(), StorageError>; + + /// Loads non finalized appointments from the database for a given tower based on a status flag. + /// + /// This is meant to be used only for pending and invalid appointments, if the method is called for + /// accepted appointment, an empty collection will be returned. + fn load_appointments( + &self, + tower_id: TowerId, + status: AppointmentStatus, + ) -> Vec; + + /// Stores a misbehaving proof into the database. + /// + /// A misbehaving proof is proof that the tower has signed an appointment using a key different + /// than the one advertised to the user when they registered. + fn store_misbehaving_proof( + &mut self, + tower_id: TowerId, + proof: &MisbehaviorProof, + ) -> Result<(), StorageError>; +} diff --git a/teos-ldk-client/src/wt_client.rs b/teos-ldk-client/src/wt_client.rs index 59669306..fc1a3917 100644 --- a/teos-ldk-client/src/wt_client.rs +++ b/teos-ldk-client/src/wt_client.rs @@ -1,3 +1,4 @@ +use crate::storage::storage::{Persister, StorageError}; use std::collections::{HashMap, HashSet}; use std::iter::FromIterator; @@ -16,7 +17,7 @@ use teos_common::receipts::{AppointmentReceipt, RegistrationReceipt}; use teos_common::{TowerId, UserId}; use crate::retrier::RetrierStatus; -use crate::storage::{DBError, Storage}; +use crate::storage::Storage; #[cfg(feature = "kv")] use lightning::util::persist::KVStore; use crate::{MisbehaviorProof, SubscriptionError, TowerInfo, TowerStatus, TowerSummary}; @@ -317,12 +318,12 @@ impl WTClient { /// 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> { + pub fn remove_tower(&mut self, tower_id: TowerId) -> Result<(), StorageError> { if self.towers.contains_key(&tower_id) { self.towers.remove(&tower_id); self.storage.remove_tower_record(tower_id) } else { - Err(DBError::NotFound) + Err(StorageError::NotFound("foo".to_string())) } } } @@ -1009,7 +1010,7 @@ impl WTClient { // // assert!(matches!( // wt_client.remove_tower(get_random_user_id()), -// Err(DBError::NotFound) +// Err(StorageError::NotFound) // )); // } // } diff --git a/watchtower-plugin/src/lib.rs b/watchtower-plugin/src/lib.rs index af2358a2..78c2e6b5 100755 --- a/watchtower-plugin/src/lib.rs +++ b/watchtower-plugin/src/lib.rs @@ -20,7 +20,7 @@ pub mod wt_client; mod test_utils; /// The status the tower can be found at. -#[derive(Clone, Serialize, PartialEq, Eq, Copy, Debug)] +#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Copy, Debug)] #[serde(rename_all = "snake_case")] pub enum TowerStatus { Reachable, @@ -249,7 +249,7 @@ impl TowerInfo { } /// A misbehaving proof. Contains proof of a tower replying with a public key different from the advertised one. -#[derive(Clone, Serialize, Debug, PartialEq, Eq)] +#[derive(Clone, Deserialize, Serialize, Debug, PartialEq, Eq)] pub struct MisbehaviorProof { #[serde(with = "hex::serde")] pub locator: Locator, From 90fa5ed1e5ca2bcc6e8dc1c758d31818f90fccbb Mon Sep 17 00:00:00 2001 From: dzdidi Date: Tue, 11 Feb 2025 18:33:30 +0100 Subject: [PATCH 46/85] persister trait done Signed-off-by: dzdidi --- teos-ldk-client/src/retrier.rs | 61 +- teos-ldk-client/src/storage/dbm.rs | 44 +- teos-ldk-client/src/storage/storage.rs | 6 +- teos-ldk-client/src/wt_client.rs | 1464 ++++++++++++------------ 4 files changed, 801 insertions(+), 774 deletions(-) diff --git a/teos-ldk-client/src/retrier.rs b/teos-ldk-client/src/retrier.rs index ff4a4ad4..8a6ee64a 100644 --- a/teos-ldk-client/src/retrier.rs +++ b/teos-ldk-client/src/retrier.rs @@ -1,5 +1,6 @@ -use crate::storage::storage::Persister; use std::collections::{HashMap, HashSet}; +use crate::storage::Storage; +use crate::storage::storage::{Persister, StorageError}; use std::fmt::Display; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; @@ -630,10 +631,12 @@ mod tests { let keypair = cryptography::get_random_keypair(); let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let db_path = tmp_path.path().join("watchtower.db"); + let storage = Box::new(Storage::new(&db_path).unwrap()); let wt_client = Arc::new(Mutex::new( WTClient::new( - tmp_path.path().to_path_buf(), + storage, keypair.0, unbounded_channel().0, ) @@ -731,10 +734,12 @@ mod tests { let keypair = cryptography::get_random_keypair(); let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let db_path = tmp_path.path().join("watchtower.db"); + let storage = Box::new(Storage::new(&db_path).unwrap()); let wt_client = Arc::new(Mutex::new( WTClient::new( - tmp_path.path().to_path_buf(), + storage, keypair.0, unbounded_channel().0, ) @@ -865,10 +870,12 @@ mod tests { let keypair = cryptography::get_random_keypair(); let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let db_path = tmp_path.path().join("watchtower.db"); + let storage = Box::new(Storage::new(&db_path).unwrap()); let wt_client = Arc::new(Mutex::new( WTClient::new( - tmp_path.path().to_path_buf(), + storage, keypair.0, unbounded_channel().0, ) @@ -976,10 +983,12 @@ mod tests { let keypair = cryptography::get_random_keypair(); let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let db_path = tmp_path.path().join("watchtower.db"); + let storage = Box::new(Storage::new(&db_path).unwrap()); let wt_client = Arc::new(Mutex::new( WTClient::new( - tmp_path.path().to_path_buf(), + storage, keypair.0, unbounded_channel().0, ) @@ -1075,10 +1084,12 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let (tx, rx) = unbounded_channel(); + let db_path = tmp_path.path().join("watchtower.db"); + let storage = Box::new(Storage::new(&db_path).unwrap()); let wt_client = Arc::new(Mutex::new( WTClient::new( - tmp_path.path().to_path_buf(), + storage, keypair.0, unbounded_channel().0, ) @@ -1125,10 +1136,12 @@ mod tests { let keypair = cryptography::get_random_keypair(); let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let db_path = tmp_path.path().join("watchtower.db"); + let storage = Box::new(Storage::new(&db_path).unwrap()); let wt_client = Arc::new(Mutex::new( WTClient::new( - tmp_path.path().to_path_buf(), + storage, keypair.0, unbounded_channel().0, ) @@ -1255,7 +1268,8 @@ mod tests { let (tower_sk, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); - let mut storage = Storage::new(&tmp_path.path().join("watchtower.db")).unwrap(); + let db_path = tmp_path.path().join("watchtower.db"); + let mut storage = Box::new(Storage::new(&db_path).unwrap()); let receipt = get_random_registration_receipt(); storage .store_tower_record(tower_id, "http://unreachable.tower", &receipt) @@ -1265,11 +1279,10 @@ mod tests { storage .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(), + Box::new(Storage::new(&db_path).unwrap()), keypair.0, unbounded_channel().0, ) @@ -1405,10 +1418,12 @@ mod tests { let keypair = cryptography::get_random_keypair(); let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let db_path = tmp_path.path().join("watchtower.db"); + let storage = Box::new(Storage::new(&db_path).unwrap()); let wt_client = Arc::new(Mutex::new( WTClient::new( - tmp_path.path().to_path_buf(), + storage, keypair.0, unbounded_channel().0, ) @@ -1462,10 +1477,12 @@ mod tests { let keypair = cryptography::get_random_keypair(); let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let db_path = tmp_path.path().join("watchtower.db"); + let storage = Box::new(Storage::new(&db_path).unwrap()); let wt_client = Arc::new(Mutex::new( WTClient::new( - tmp_path.path().to_path_buf(), + storage, keypair.0, unbounded_channel().0, ) @@ -1493,10 +1510,12 @@ mod tests { let keypair = cryptography::get_random_keypair(); let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let db_path = tmp_path.path().join("watchtower.db"); + let storage = Box::new(Storage::new(&db_path).unwrap()); let wt_client = Arc::new(Mutex::new( WTClient::new( - tmp_path.path().to_path_buf(), + storage, keypair.0, unbounded_channel().0, ) @@ -1552,10 +1571,12 @@ mod tests { let keypair = cryptography::get_random_keypair(); let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let db_path = tmp_path.path().join("watchtower.db"); + let storage = Box::new(Storage::new(&db_path).unwrap()); let wt_client = Arc::new(Mutex::new( WTClient::new( - tmp_path.path().to_path_buf(), + storage, keypair.0, unbounded_channel().0, ) @@ -1591,10 +1612,12 @@ mod tests { let keypair = cryptography::get_random_keypair(); let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let db_path = tmp_path.path().join("watchtower.db"); + let storage = Box::new(Storage::new(&db_path).unwrap()); let wt_client = Arc::new(Mutex::new( WTClient::new( - tmp_path.path().to_path_buf(), + storage, keypair.0, unbounded_channel().0, ) @@ -1653,10 +1676,12 @@ mod tests { let keypair = cryptography::get_random_keypair(); let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let db_path = tmp_path.path().join("watchtower.db"); + let storage = Box::new(Storage::new(&db_path).unwrap()); let wt_client = Arc::new(Mutex::new( WTClient::new( - tmp_path.path().to_path_buf(), + storage, keypair.0, unbounded_channel().0, ) @@ -1721,10 +1746,12 @@ mod tests { let keypair = cryptography::get_random_keypair(); let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let db_path = tmp_path.path().join("watchtower.db"); + let storage = Box::new(Storage::new(&db_path).unwrap()); let wt_client = Arc::new(Mutex::new( WTClient::new( - tmp_path.path().to_path_buf(), + storage, keypair.0, unbounded_channel().0, ) diff --git a/teos-ldk-client/src/storage/dbm.rs b/teos-ldk-client/src/storage/dbm.rs index 5570393b..57da162a 100644 --- a/teos-ldk-client/src/storage/dbm.rs +++ b/teos-ldk-client/src/storage/dbm.rs @@ -635,6 +635,27 @@ impl Persister for DBM { Ok(()) } + + fn appointment_exists(&self, locator: Locator) -> bool { + let mut stmt = self + .connection + .prepare("SELECT * FROM appointments WHERE locator=? ") + .unwrap(); + stmt.exists(params![locator.to_vec()]).unwrap() + } + + 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() + } } #[cfg(test)] @@ -655,29 +676,8 @@ mod tests { Ok(dbm) } - - pub(crate) fn appointment_exists(&self, locator: Locator) -> bool { - let mut stmt = self - .connection - .prepare("SELECT * FROM appointments WHERE locator=? ") - .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] fn test_create_tables() { let connection = Connection::open_in_memory().unwrap(); diff --git a/teos-ldk-client/src/storage/storage.rs b/teos-ldk-client/src/storage/storage.rs index c48268f8..6ebcc7fc 100644 --- a/teos-ldk-client/src/storage/storage.rs +++ b/teos-ldk-client/src/storage/storage.rs @@ -34,7 +34,7 @@ impl fmt::Display for StorageError { impl std::error::Error for StorageError {} /// Trait defining the interface for database operations -pub trait Persister { +pub trait Persister: Send { /// Stores a tower record into the database alongside the corresponding registration receipt. /// /// This function MUST be guarded against inserting duplicate (tower_id, subscription_expiry) pairs. @@ -156,4 +156,8 @@ pub trait Persister { tower_id: TowerId, proof: &MisbehaviorProof, ) -> Result<(), StorageError>; + + fn appointment_exists(&self, locator: Locator) -> bool; + + fn appointment_receipt_exists(&self, locator: Locator, tower_id: TowerId) -> bool; } diff --git a/teos-ldk-client/src/wt_client.rs b/teos-ldk-client/src/wt_client.rs index fc1a3917..e46a790d 100644 --- a/teos-ldk-client/src/wt_client.rs +++ b/teos-ldk-client/src/wt_client.rs @@ -62,14 +62,9 @@ impl std::fmt::Debug for RevocationData { } } -/// Represents the watchtower client that is being used as the CoreLN plugin state. pub struct WTClient { - /// A [Storage] instance. - #[cfg(feature = "sqlite")] - pub storage: Storage, // should support both slqite or KVStorage? - /// A [Storage] instance. - #[cfg(feature = "kv")] - pub storage: Storage, // should support both slqite or KVStorage? + /// A database manager instance implementing the DatabaseManager trait + pub storage: Box, /// A collection of towers the client is registered to. pub towers: HashMap, /// Queue of unreachable towers. @@ -85,17 +80,10 @@ pub struct WTClient { impl WTClient { #[cfg(feature = "sqlite")] pub async fn new( - data_dir: PathBuf, + storage: Box, user_sk: SecretKey, 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| { - log::error!("Cannot create data dir: {e:?}"); - std::process::exit(1); - }); - - let storage = Storage::new(&data_dir.join("watchtower.db")).unwrap(); let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &user_sk)); let towers = storage.load_towers(); @@ -122,38 +110,6 @@ impl WTClient { } } - #[cfg(feature = "kv")] - pub async fn new( - store: T, - user_sk: SecretKey, - unreachable_towers: UnboundedSender<(TowerId, RevocationData)>, - ) -> Self { - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &user_sk)); - let storage = Storage::new(store, user_sk.secret_bytes().to_vec()).unwrap(); - - let towers = storage.load_towers(); - for (tower_id, tower) in towers.iter() { - if tower.status.is_temporary_unreachable() { - unreachable_towers - .send(( - *tower_id, - RevocationData::Stale(tower.pending_appointments.iter().cloned().collect()), - )) - .unwrap(); - } - } - - log::info!("Plugin watchtower client initialized. User id = {user_id}"); - - WTClient { - towers, - unreachable_towers, - retriers: HashMap::new(), - storage, - user_sk, - user_id, - } - } /// Adds or updates a tower entry. pub fn add_update_tower( @@ -327,690 +283,730 @@ impl WTClient { } } } -// -// #[cfg(test)] -// mod tests { -// use super::*; -// -// use tempdir::TempDir; -// use tokio::sync::mpsc::unbounded_channel; -// -// use teos_common::test_utils::{ -// generate_random_appointment, get_random_appointment_receipt, -// get_random_registration_receipt, get_random_user_id, -// get_registration_receipt_from_previous, -// }; -// -// #[tokio::test] -// async fn test_add_update_load_tower() { -// let keypair = cryptography::get_random_keypair(); -// let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); -// let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); -// let mut wt_client = WTClient::new( -// tmp_path.path().to_path_buf(), -// keypair.0, -// unbounded_channel().0, -// ) -// .await; -// -// // 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_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(), -// receipt.subscription_start(), -// receipt.subscription_expiry(), -// ); -// -// wt_client -// .add_update_tower(tower_id, &tower_info.net_addr, &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); -// -// // Calling the method again with updated information should also updated the records in memory and the database -// receipt = get_registration_receipt_from_previous(&receipt); -// -// let updated_tower_info = TowerInfo::empty( -// "talaia.watch".to_owned(), -// receipt.available_slots(), -// receipt.subscription_start(), -// receipt.subscription_expiry(), -// ); -// wt_client -// .add_update_tower(tower_id, &updated_tower_info.net_addr, &receipt) -// .unwrap(); -// -// assert_eq!( -// wt_client.towers.get(&tower_id), -// Some(&TowerSummary::from(updated_tower_info.clone())) -// ); -// assert_eq!( -// wt_client.load_tower_info(tower_id).unwrap(), -// updated_tower_info -// ); -// -// // If we try to update without increasing both the end_time and the slots, this will fail -// let mut receipt_same_slots = RegistrationReceipt::new( -// receipt.user_id(), -// receipt.available_slots(), -// receipt.subscription_start(), -// receipt.subscription_expiry() + 1, -// ); -// 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), -// 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, -// &receipt_same_expiry -// ), -// 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] -// async fn test_get_tower_status() { -// let keypair = cryptography::get_random_keypair(); -// let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); -// let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); -// let mut wt_client = WTClient::new( -// tmp_path.path().to_path_buf(), -// keypair.0, -// 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 keypair = cryptography::get_random_keypair(); -// let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); -// let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); -// let mut wt_client = WTClient::new( -// tmp_path.path().to_path_buf(), -// keypair.0, -// unbounded_channel().0, -// ) -// .await; -// -// // If the tower is unknown nothing will happen -// let unknown_tower = get_random_user_id(); -// wt_client.set_tower_status(unknown_tower, TowerStatus::Reachable); -// assert!(!wt_client.towers.contains_key(&unknown_tower)); -// -// // If the tower is known, the status will be updated. -// let receipt = get_random_registration_receipt(); -// let tower_id = get_random_user_id(); -// wt_client -// .add_update_tower(tower_id, "talaia.watch", &receipt) -// .unwrap(); -// -// for status in [ -// TowerStatus::Reachable, -// TowerStatus::TemporaryUnreachable, -// TowerStatus::Unreachable, -// TowerStatus::SubscriptionError, -// TowerStatus::Misbehaving, -// ] { -// wt_client.set_tower_status(tower_id, status); -// assert_eq!(status, wt_client.get_tower_status(&tower_id).unwrap()); -// } -// } -// -// #[tokio::test] -// async fn test_add_appointment_receipt() { -// let keypair = cryptography::get_random_keypair(); -// let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); -// let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); -// let mut wt_client = WTClient::new( -// tmp_path.path().to_path_buf(), -// keypair.0, -// unbounded_channel().0, -// ) -// .await; -// -// let (tower_sk, tower_pk) = cryptography::get_random_keypair(); -// let tower_id = TowerId(tower_pk); -// -// 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.towers.contains_key(&tower_id)); -// -// // Add the tower to the state and try again -// let tower_info = TowerInfo::new( -// "talaia.watch".to_owned(), -// registration_receipt.available_slots(), -// registration_receipt.subscription_start(), -// registration_receipt.subscription_expiry(), -// HashMap::from([(locator, appointment_receipt.signature().unwrap())]), -// Vec::new(), -// Vec::new(), -// ); -// wt_client -// .add_update_tower(tower_id, &tower_info.net_addr, ®istration_receipt) -// .unwrap(); -// wt_client.add_appointment_receipt( -// tower_id, -// locator, -// registration_receipt.available_slots(), -// &appointment_receipt, -// ); -// -// assert!(wt_client.towers.contains_key(&tower_id)); -// assert_eq!( -// wt_client.towers.get(&tower_id).unwrap(), -// &TowerSummary::from(tower_info.clone()) -// ); -// assert_eq!(wt_client.load_tower_info(tower_id).unwrap(), tower_info); -// } -// -// #[tokio::test] -// async fn test_add_pending_appointment() { -// let keypair = cryptography::get_random_keypair(); -// let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); -// let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); -// let mut wt_client = WTClient::new( -// tmp_path.path().to_path_buf(), -// keypair.0, -// unbounded_channel().0, -// ) -// .await; -// -// let tower_id = get_random_user_id(); -// -// let registration_receipt = get_random_registration_receipt(); -// let appointment = generate_random_appointment(None); -// -// // If we call this on an unknown tower it will simply do nothing -// wt_client.add_pending_appointment(tower_id, &appointment); -// assert!(!wt_client.towers.contains_key(&tower_id)); -// -// // Add the tower to the state and try again -// let tower_info = TowerInfo::new( -// "talaia.watch".to_owned(), -// registration_receipt.available_slots(), -// registration_receipt.subscription_start(), -// registration_receipt.subscription_expiry(), -// HashMap::new(), -// vec![appointment.clone()], -// Vec::new(), -// ); -// -// wt_client -// .add_update_tower(tower_id, &tower_info.net_addr, ®istration_receipt) -// .unwrap(); -// wt_client.add_pending_appointment(tower_id, &appointment); -// -// assert!(wt_client.towers.contains_key(&tower_id)); -// assert_eq!( -// wt_client.towers.get(&tower_id).unwrap(), -// &TowerSummary::from(tower_info.clone()) -// ); -// // When towers data is loaded from the database, it is assumed to be reachable. -// assert_eq!( -// wt_client.load_tower_info(tower_id).unwrap(), -// tower_info.with_status(TowerStatus::TemporaryUnreachable) -// ); -// } -// -// #[tokio::test] -// async fn test_remove_pending_appointment() { -// let keypair = cryptography::get_random_keypair(); -// let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); -// let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); -// let mut wt_client = WTClient::new( -// tmp_path.path().to_path_buf(), -// keypair.0, -// unbounded_channel().0, -// ) -// .await; -// -// let tower_id = get_random_user_id(); -// -// let registration_receipt = get_random_registration_receipt(); -// let appointment = generate_random_appointment(None); -// -// // If we call this on an unknown tower it will simply do nothing -// wt_client.remove_pending_appointment(tower_id, appointment.locator); -// -// // Add the tower to the state and try again -// wt_client -// .add_update_tower(tower_id, "talaia.watch", ®istration_receipt) -// .unwrap(); -// wt_client.add_pending_appointment(tower_id, &appointment); -// -// wt_client.remove_pending_appointment(tower_id, appointment.locator); -// assert!(!wt_client -// .towers -// .get(&tower_id) -// .unwrap() -// .pending_appointments -// .contains(&appointment.locator)); -// // This bit is tested exhaustively in the Storage. -// assert!(!wt_client.storage.appointment_exists(appointment.locator)); -// } -// -// #[tokio::test] -// async fn test_add_invalid_appointment() { -// let keypair = cryptography::get_random_keypair(); -// let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); -// let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); -// let mut wt_client = WTClient::new( -// tmp_path.path().to_path_buf(), -// keypair.0, -// unbounded_channel().0, -// ) -// .await; -// -// let tower_id = get_random_user_id(); -// -// let registration_receipt = get_random_registration_receipt(); -// let appointment = generate_random_appointment(None); -// -// // If we call this on an unknown tower it will simply do nothing -// wt_client.add_invalid_appointment(tower_id, &appointment); -// assert!(!wt_client.towers.contains_key(&tower_id)); -// -// // Add the tower to the state and try again -// let tower_info = TowerInfo::new( -// "talaia.watch".to_owned(), -// registration_receipt.available_slots(), -// registration_receipt.subscription_start(), -// registration_receipt.subscription_expiry(), -// HashMap::new(), -// Vec::new(), -// vec![appointment.clone()], -// ); -// -// wt_client -// .add_update_tower(tower_id, &tower_info.net_addr, ®istration_receipt) -// .unwrap(); -// wt_client.add_invalid_appointment(tower_id, &appointment); -// -// assert!(wt_client.towers.contains_key(&tower_id)); -// assert_eq!( -// wt_client.towers.get(&tower_id).unwrap(), -// &TowerSummary::from(tower_info.clone()) -// ); -// assert_eq!(wt_client.load_tower_info(tower_id).unwrap(), tower_info); -// } -// -// #[tokio::test] -// async fn test_move_pending_appointment_to_invalid() { -// let keypair = cryptography::get_random_keypair(); -// let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); -// let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); -// let mut wt_client = WTClient::new( -// tmp_path.path().to_path_buf(), -// keypair.0, -// unbounded_channel().0, -// ) -// .await; -// -// let tower_id = get_random_user_id(); -// -// let registration_receipt = get_random_registration_receipt(); -// let appointment = generate_random_appointment(None); -// -// wt_client -// .add_update_tower(tower_id, "talaia.watch", ®istration_receipt) -// .unwrap(); -// wt_client.add_pending_appointment(tower_id, &appointment); -// -// // Check that the appointment can be moved from pending to invalid -// wt_client.add_invalid_appointment(tower_id, &appointment); -// wt_client.remove_pending_appointment(tower_id, appointment.locator); -// -// assert!(!wt_client -// .towers -// .get(&tower_id) -// .unwrap() -// .pending_appointments -// .contains(&appointment.locator)); -// assert!(wt_client -// .towers -// .get(&tower_id) -// .unwrap() -// .invalid_appointments -// .contains(&appointment.locator)); -// assert!(!wt_client -// .storage -// .load_appointment_locators(tower_id, crate::AppointmentStatus::Pending) -// .contains(&appointment.locator)); -// assert!(wt_client -// .storage -// .load_appointment_locators(tower_id, crate::AppointmentStatus::Invalid) -// .contains(&appointment.locator)); -// assert!(wt_client.storage.appointment_exists(appointment.locator)); -// } -// -// #[tokio::test] -// async fn test_move_pending_appointment_to_invalid_multiple_towers() { -// // Check that moving an appointment from pending to invalid can be done even if multiple towers have a reference to it -// let keypair = cryptography::get_random_keypair(); -// let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); -// let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); -// let mut wt_client = WTClient::new( -// tmp_path.path().to_path_buf(), -// keypair.0, -// unbounded_channel().0, -// ) -// .await; -// -// let tower_id = get_random_user_id(); -// let another_tower_id = get_random_user_id(); -// 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) -// .unwrap(); -// wt_client -// .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); -// -// // Check that the appointment can be moved from pending to invalid -// wt_client.add_invalid_appointment(tower_id, &appointment); -// wt_client.remove_pending_appointment(tower_id, appointment.locator); -// -// // TOWER_ID CHECKS -// assert!(!wt_client -// .towers -// .get(&tower_id) -// .unwrap() -// .pending_appointments -// .contains(&appointment.locator)); -// assert!(wt_client -// .towers -// .get(&tower_id) -// .unwrap() -// .invalid_appointments -// .contains(&appointment.locator)); -// assert!(!wt_client -// .storage -// .load_appointment_locators(tower_id, crate::AppointmentStatus::Pending) -// .contains(&appointment.locator)); -// assert!(wt_client -// .storage -// .load_appointment_locators(tower_id, crate::AppointmentStatus::Invalid) -// .contains(&appointment.locator)); -// -// // ANOTHER_TOWER_ID CHECKS -// assert!(wt_client -// .towers -// .get(&another_tower_id) -// .unwrap() -// .pending_appointments -// .contains(&appointment.locator)); -// assert!(!wt_client -// .towers -// .get(&another_tower_id) -// .unwrap() -// .invalid_appointments -// .contains(&appointment.locator)); -// assert!(wt_client -// .storage -// .load_appointment_locators(another_tower_id, crate::AppointmentStatus::Pending) -// .contains(&appointment.locator)); -// assert!(!wt_client -// .storage -// .load_appointment_locators(another_tower_id, crate::AppointmentStatus::Invalid) -// .contains(&appointment.locator)); -// -// // GENERAL -// assert!(wt_client.storage.appointment_exists(appointment.locator)); -// } -// -// #[tokio::test] -// async fn test_flag_misbehaving_tower() { -// let keypair = cryptography::get_random_keypair(); -// let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); -// let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); -// let mut wt_client = WTClient::new( -// tmp_path.path().to_path_buf(), -// keypair.0, -// unbounded_channel().0, -// ) -// .await; -// -// let (tower_sk, tower_pk) = cryptography::get_random_keypair(); -// let tower_id = TowerId(tower_pk); -// -// // If we call this on an unknown tower it will simply do nothing -// let appointment = generate_random_appointment(None); -// let receipt = get_random_appointment_receipt(tower_sk); -// let proof = MisbehaviorProof::new(appointment.locator, receipt, get_random_user_id()); -// wt_client.flag_misbehaving_tower(tower_id, proof.clone()); -// assert!(!wt_client.towers.contains_key(&tower_id)); -// -// // // Add the tower to the state and try again -// let registration_receipt = get_random_registration_receipt(); -// wt_client -// .add_update_tower(tower_id, "talaia.watch", ®istration_receipt) -// .unwrap(); -// wt_client.flag_misbehaving_tower(tower_id, proof.clone()); -// -// // Check data in memory -// let tower_summary = wt_client.towers.get(&tower_id); -// assert!(tower_summary.is_some()); -// assert!(tower_summary.unwrap().status.is_misbehaving()); -// -// // Check data in DB -// let loaded_info = wt_client.load_tower_info(tower_id).unwrap(); -// assert!(loaded_info.status.is_misbehaving()); -// assert_eq!(loaded_info.misbehaving_proof, Some(proof)); -// assert!(loaded_info.appointments.contains_key(&appointment.locator)); -// } -// -// #[tokio::test] -// async fn test_remove_tower() { -// let keypair = cryptography::get_random_keypair(); -// let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); -// let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); -// let mut wt_client = WTClient::new( -// tmp_path.path().to_path_buf(), -// keypair.0, -// 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".to_owned(), -// 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, &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!(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 -// 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 -// .storage -// .appointment_receipt_exists(locator, tower_id)); -// -// // Remove and check both the tower and the appointment -// wt_client.remove_tower(tower_id).unwrap(); -// assert!(wt_client.load_tower_info(tower_id).is_none()); -// assert!(!wt_client.towers.contains_key(&tower_id)); -// assert!(!wt_client -// .storage -// .appointment_receipt_exists(locator, tower_id)); -// } -// -// #[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 keypair = cryptography::get_random_keypair(); -// let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); -// let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); -// let mut wt_client = WTClient::new( -// tmp_path.path().to_path_buf(), -// keypair.0, -// 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); -// -// wt_client -// .add_update_tower(tower1_id, "talaia.watch", &receipt) -// .unwrap(); -// wt_client -// .add_update_tower(tower2_id, "talaia.watch", &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 -// .storage -// .appointment_receipt_exists(locator, tower1_id)); -// assert!(wt_client -// .storage -// .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!(wt_client.load_tower_info(tower1_id).is_none()); -// -// assert!(!wt_client -// .storage -// .appointment_receipt_exists(locator, tower1_id)); -// assert!(wt_client -// .storage -// .appointment_receipt_exists(locator, tower2_id)); -// } -// -// #[tokio::test] -// async fn test_remove_inexistent_tower() { -// let keypair = cryptography::get_random_keypair(); -// let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); -// let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); -// let mut wt_client = WTClient::new( -// tmp_path.path().to_path_buf(), -// keypair.0, -// unbounded_channel().0, -// ) -// .await; -// -// assert!(matches!( -// wt_client.remove_tower(get_random_user_id()), -// Err(StorageError::NotFound) -// )); -// } -// } + +#[cfg(test)] +mod tests { + use super::*; + + use tempdir::TempDir; + use tokio::sync::mpsc::unbounded_channel; + + use teos_common::test_utils::{ + generate_random_appointment, get_random_appointment_receipt, + get_random_registration_receipt, get_random_user_id, + get_registration_receipt_from_previous, + }; + + #[tokio::test] + async fn test_add_update_load_tower() { + let keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let db_path = tmp_path.path().join("watchtower.db"); + let storage = Box::new(Storage::new(&db_path).unwrap()); + + let mut wt_client = WTClient::new( + storage, + keypair.0, + unbounded_channel().0, + ) + .await; + + // 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_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(), + receipt.subscription_start(), + receipt.subscription_expiry(), + ); + + wt_client + .add_update_tower(tower_id, &tower_info.net_addr, &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); + + // Calling the method again with updated information should also updated the records in memory and the database + receipt = get_registration_receipt_from_previous(&receipt); + + let updated_tower_info = TowerInfo::empty( + "talaia.watch".to_owned(), + receipt.available_slots(), + receipt.subscription_start(), + receipt.subscription_expiry(), + ); + wt_client + .add_update_tower(tower_id, &updated_tower_info.net_addr, &receipt) + .unwrap(); + + assert_eq!( + wt_client.towers.get(&tower_id), + Some(&TowerSummary::from(updated_tower_info.clone())) + ); + assert_eq!( + wt_client.load_tower_info(tower_id).unwrap(), + updated_tower_info + ); + + // If we try to update without increasing both the end_time and the slots, this will fail + let mut receipt_same_slots = RegistrationReceipt::new( + receipt.user_id(), + receipt.available_slots(), + receipt.subscription_start(), + receipt.subscription_expiry() + 1, + ); + 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), + 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, + &receipt_same_expiry + ), + 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] + async fn test_get_tower_status() { + let keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let db_path = tmp_path.path().join("watchtower.db"); + let storage = Box::new(Storage::new(&db_path).unwrap()); + + let mut wt_client = WTClient::new( + storage, + keypair.0, + 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 keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let db_path = tmp_path.path().join("watchtower.db"); + let storage = Box::new(Storage::new(&db_path).unwrap()); + + let mut wt_client = WTClient::new( + storage, + keypair.0, + unbounded_channel().0, + ) + .await; + + // If the tower is unknown nothing will happen + let unknown_tower = get_random_user_id(); + wt_client.set_tower_status(unknown_tower, TowerStatus::Reachable); + assert!(!wt_client.towers.contains_key(&unknown_tower)); + + // If the tower is known, the status will be updated. + let receipt = get_random_registration_receipt(); + let tower_id = get_random_user_id(); + wt_client + .add_update_tower(tower_id, "talaia.watch", &receipt) + .unwrap(); + + for status in [ + TowerStatus::Reachable, + TowerStatus::TemporaryUnreachable, + TowerStatus::Unreachable, + TowerStatus::SubscriptionError, + TowerStatus::Misbehaving, + ] { + wt_client.set_tower_status(tower_id, status); + assert_eq!(status, wt_client.get_tower_status(&tower_id).unwrap()); + } + } + + #[tokio::test] + async fn test_add_appointment_receipt() { + let keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let db_path = tmp_path.path().join("watchtower.db"); + let storage = Box::new(Storage::new(&db_path).unwrap()); + + let mut wt_client = WTClient::new( + storage, + keypair.0, + unbounded_channel().0, + ) + .await; + + let (tower_sk, tower_pk) = cryptography::get_random_keypair(); + let tower_id = TowerId(tower_pk); + + 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.towers.contains_key(&tower_id)); + + // Add the tower to the state and try again + let tower_info = TowerInfo::new( + "talaia.watch".to_owned(), + registration_receipt.available_slots(), + registration_receipt.subscription_start(), + registration_receipt.subscription_expiry(), + HashMap::from([(locator, appointment_receipt.signature().unwrap())]), + Vec::new(), + Vec::new(), + ); + wt_client + .add_update_tower(tower_id, &tower_info.net_addr, ®istration_receipt) + .unwrap(); + wt_client.add_appointment_receipt( + tower_id, + locator, + registration_receipt.available_slots(), + &appointment_receipt, + ); + + assert!(wt_client.towers.contains_key(&tower_id)); + assert_eq!( + wt_client.towers.get(&tower_id).unwrap(), + &TowerSummary::from(tower_info.clone()) + ); + assert_eq!(wt_client.load_tower_info(tower_id).unwrap(), tower_info); + } + + #[tokio::test] + async fn test_add_pending_appointment() { + let keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let db_path = tmp_path.path().join("watchtower.db"); + let storage = Box::new(Storage::new(&db_path).unwrap()); + + let mut wt_client = WTClient::new( + storage, + keypair.0, + unbounded_channel().0, + ) + .await; + + let tower_id = get_random_user_id(); + + let registration_receipt = get_random_registration_receipt(); + let appointment = generate_random_appointment(None); + + // If we call this on an unknown tower it will simply do nothing + wt_client.add_pending_appointment(tower_id, &appointment); + assert!(!wt_client.towers.contains_key(&tower_id)); + + // Add the tower to the state and try again + let tower_info = TowerInfo::new( + "talaia.watch".to_owned(), + registration_receipt.available_slots(), + registration_receipt.subscription_start(), + registration_receipt.subscription_expiry(), + HashMap::new(), + vec![appointment.clone()], + Vec::new(), + ); + + wt_client + .add_update_tower(tower_id, &tower_info.net_addr, ®istration_receipt) + .unwrap(); + wt_client.add_pending_appointment(tower_id, &appointment); + + assert!(wt_client.towers.contains_key(&tower_id)); + assert_eq!( + wt_client.towers.get(&tower_id).unwrap(), + &TowerSummary::from(tower_info.clone()) + ); + // When towers data is loaded from the database, it is assumed to be reachable. + assert_eq!( + wt_client.load_tower_info(tower_id).unwrap(), + tower_info.with_status(TowerStatus::TemporaryUnreachable) + ); + } + + #[tokio::test] + async fn test_remove_pending_appointment() { + let keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let db_path = tmp_path.path().join("watchtower.db"); + let storage = Box::new(Storage::new(&db_path).unwrap()); + + let mut wt_client = WTClient::new( + storage, + keypair.0, + unbounded_channel().0, + ) + .await; + + let tower_id = get_random_user_id(); + + let registration_receipt = get_random_registration_receipt(); + let appointment = generate_random_appointment(None); + + // If we call this on an unknown tower it will simply do nothing + wt_client.remove_pending_appointment(tower_id, appointment.locator); + + // Add the tower to the state and try again + wt_client + .add_update_tower(tower_id, "talaia.watch", ®istration_receipt) + .unwrap(); + wt_client.add_pending_appointment(tower_id, &appointment); + + wt_client.remove_pending_appointment(tower_id, appointment.locator); + assert!(!wt_client + .towers + .get(&tower_id) + .unwrap() + .pending_appointments + .contains(&appointment.locator)); + // This bit is tested exhaustively in the Storage. + assert!(!wt_client.storage.appointment_exists(appointment.locator)); + } + + #[tokio::test] + async fn test_add_invalid_appointment() { + let keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let db_path = tmp_path.path().join("watchtower.db"); + let storage = Box::new(Storage::new(&db_path).unwrap()); + + let mut wt_client = WTClient::new( + storage, + keypair.0, + unbounded_channel().0, + ) + .await; + + let tower_id = get_random_user_id(); + + let registration_receipt = get_random_registration_receipt(); + let appointment = generate_random_appointment(None); + + // If we call this on an unknown tower it will simply do nothing + wt_client.add_invalid_appointment(tower_id, &appointment); + assert!(!wt_client.towers.contains_key(&tower_id)); + + // Add the tower to the state and try again + let tower_info = TowerInfo::new( + "talaia.watch".to_owned(), + registration_receipt.available_slots(), + registration_receipt.subscription_start(), + registration_receipt.subscription_expiry(), + HashMap::new(), + Vec::new(), + vec![appointment.clone()], + ); + + wt_client + .add_update_tower(tower_id, &tower_info.net_addr, ®istration_receipt) + .unwrap(); + wt_client.add_invalid_appointment(tower_id, &appointment); + + assert!(wt_client.towers.contains_key(&tower_id)); + assert_eq!( + wt_client.towers.get(&tower_id).unwrap(), + &TowerSummary::from(tower_info.clone()) + ); + assert_eq!(wt_client.load_tower_info(tower_id).unwrap(), tower_info); + } + + #[tokio::test] + async fn test_move_pending_appointment_to_invalid() { + let keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let db_path = tmp_path.path().join("watchtower.db"); + let storage = Box::new(Storage::new(&db_path).unwrap()); + + let mut wt_client = WTClient::new( + storage, + keypair.0, + unbounded_channel().0, + ) + .await; + + let tower_id = get_random_user_id(); + + let registration_receipt = get_random_registration_receipt(); + let appointment = generate_random_appointment(None); + + wt_client + .add_update_tower(tower_id, "talaia.watch", ®istration_receipt) + .unwrap(); + wt_client.add_pending_appointment(tower_id, &appointment); + + // Check that the appointment can be moved from pending to invalid + wt_client.add_invalid_appointment(tower_id, &appointment); + wt_client.remove_pending_appointment(tower_id, appointment.locator); + + assert!(!wt_client + .towers + .get(&tower_id) + .unwrap() + .pending_appointments + .contains(&appointment.locator)); + assert!(wt_client + .towers + .get(&tower_id) + .unwrap() + .invalid_appointments + .contains(&appointment.locator)); + assert!(!wt_client + .storage + .load_appointment_locators(tower_id, crate::AppointmentStatus::Pending) + .contains(&appointment.locator)); + assert!(wt_client + .storage + .load_appointment_locators(tower_id, crate::AppointmentStatus::Invalid) + .contains(&appointment.locator)); + assert!(wt_client.storage.appointment_exists(appointment.locator)); + } + + #[tokio::test] + async fn test_move_pending_appointment_to_invalid_multiple_towers() { + // Check that moving an appointment from pending to invalid can be done even if multiple towers have a reference to it + let keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let db_path = tmp_path.path().join("watchtower.db"); + let storage = Box::new(Storage::new(&db_path).unwrap()); + + let mut wt_client = WTClient::new( + storage, + keypair.0, + unbounded_channel().0, + ) + .await; + + let tower_id = get_random_user_id(); + let another_tower_id = get_random_user_id(); + 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) + .unwrap(); + wt_client + .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); + + // Check that the appointment can be moved from pending to invalid + wt_client.add_invalid_appointment(tower_id, &appointment); + wt_client.remove_pending_appointment(tower_id, appointment.locator); + + // TOWER_ID CHECKS + assert!(!wt_client + .towers + .get(&tower_id) + .unwrap() + .pending_appointments + .contains(&appointment.locator)); + assert!(wt_client + .towers + .get(&tower_id) + .unwrap() + .invalid_appointments + .contains(&appointment.locator)); + assert!(!wt_client + .storage + .load_appointment_locators(tower_id, crate::AppointmentStatus::Pending) + .contains(&appointment.locator)); + assert!(wt_client + .storage + .load_appointment_locators(tower_id, crate::AppointmentStatus::Invalid) + .contains(&appointment.locator)); + + // ANOTHER_TOWER_ID CHECKS + assert!(wt_client + .towers + .get(&another_tower_id) + .unwrap() + .pending_appointments + .contains(&appointment.locator)); + assert!(!wt_client + .towers + .get(&another_tower_id) + .unwrap() + .invalid_appointments + .contains(&appointment.locator)); + assert!(wt_client + .storage + .load_appointment_locators(another_tower_id, crate::AppointmentStatus::Pending) + .contains(&appointment.locator)); + assert!(!wt_client + .storage + .load_appointment_locators(another_tower_id, crate::AppointmentStatus::Invalid) + .contains(&appointment.locator)); + + // GENERAL + assert!(wt_client.storage.appointment_exists(appointment.locator)); + } + + #[tokio::test] + async fn test_flag_misbehaving_tower() { + let keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let db_path = tmp_path.path().join("watchtower.db"); + let storage = Box::new(Storage::new(&db_path).unwrap()); + + let mut wt_client = WTClient::new( + storage, + keypair.0, + unbounded_channel().0, + ) + .await; + + let (tower_sk, tower_pk) = cryptography::get_random_keypair(); + let tower_id = TowerId(tower_pk); + + // If we call this on an unknown tower it will simply do nothing + let appointment = generate_random_appointment(None); + let receipt = get_random_appointment_receipt(tower_sk); + let proof = MisbehaviorProof::new(appointment.locator, receipt, get_random_user_id()); + wt_client.flag_misbehaving_tower(tower_id, proof.clone()); + assert!(!wt_client.towers.contains_key(&tower_id)); + + // // Add the tower to the state and try again + let registration_receipt = get_random_registration_receipt(); + wt_client + .add_update_tower(tower_id, "talaia.watch", ®istration_receipt) + .unwrap(); + wt_client.flag_misbehaving_tower(tower_id, proof.clone()); + + // Check data in memory + let tower_summary = wt_client.towers.get(&tower_id); + assert!(tower_summary.is_some()); + assert!(tower_summary.unwrap().status.is_misbehaving()); + + // Check data in DB + let loaded_info = wt_client.load_tower_info(tower_id).unwrap(); + assert!(loaded_info.status.is_misbehaving()); + assert_eq!(loaded_info.misbehaving_proof, Some(proof)); + assert!(loaded_info.appointments.contains_key(&appointment.locator)); + } + + #[tokio::test] + async fn test_remove_tower() { + let keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let db_path = tmp_path.path().join("watchtower.db"); + let storage = Box::new(Storage::new(&db_path).unwrap()); + + let mut wt_client = WTClient::new( + storage, + keypair.0, + 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".to_owned(), + 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, &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!(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 + 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, + ); + // FIXME + assert!(wt_client + .storage + .appointment_receipt_exists(locator, tower_id)); + + // Remove and check both the tower and the appointment + wt_client.remove_tower(tower_id).unwrap(); + assert!(wt_client.load_tower_info(tower_id).is_none()); + assert!(!wt_client.towers.contains_key(&tower_id)); + // FIXME + assert!(!wt_client + .storage + .appointment_receipt_exists(locator, tower_id)); + } + + #[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 keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let db_path = tmp_path.path().join("watchtower.db"); + let storage = Box::new(Storage::new(&db_path).unwrap()); + + let mut wt_client = WTClient::new( + storage, + keypair.0, + 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); + + wt_client + .add_update_tower(tower1_id, "talaia.watch", &receipt) + .unwrap(); + wt_client + .add_update_tower(tower2_id, "talaia.watch", &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 + .storage + .appointment_receipt_exists(locator, tower1_id)); + assert!(wt_client + .storage + .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!(wt_client.load_tower_info(tower1_id).is_none()); + + assert!(!wt_client + .storage + .appointment_receipt_exists(locator, tower1_id)); + assert!(wt_client + .storage + .appointment_receipt_exists(locator, tower2_id)); + } + + #[tokio::test] + async fn test_remove_inexistent_tower() { + let keypair = cryptography::get_random_keypair(); + let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); + let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); + let db_path = tmp_path.path().join("watchtower.db"); + let storage = Box::new(Storage::new(&db_path).unwrap()); + + let mut wt_client = WTClient::new( + storage, + keypair.0, + unbounded_channel().0, + ) + .await; + + match wt_client.remove_tower(get_random_user_id()) { + Ok(_) => panic!("Tower record was removed when it shouldn't have"), + Err(_) => {} + } + } +} From 20ad64faa70b9f846575c1d16e86abc736a5f017 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Wed, 12 Feb 2025 09:01:23 +0100 Subject: [PATCH 47/85] fmt Signed-off-by: dzdidi --- teos-common/src/receipts.rs | 2 +- teos-ldk-client/src/lib.rs | 2 - teos-ldk-client/src/retrier.rs | 96 +++-------------- teos-ldk-client/src/storage/dbm.rs | 20 +--- teos-ldk-client/src/storage/kv.rs | 16 +-- teos-ldk-client/src/storage/memory_store.rs | 106 ++++++++++++++----- teos-ldk-client/src/storage/mod.rs | 4 +- teos-ldk-client/src/storage/storage.rs | 8 +- teos-ldk-client/src/wt_client.rs | 109 ++++---------------- watchtower-plugin/src/lib.rs | 2 +- 10 files changed, 137 insertions(+), 228 deletions(-) diff --git a/teos-common/src/receipts.rs b/teos-common/src/receipts.rs index 136dcd20..303ec9e4 100644 --- a/teos-common/src/receipts.rs +++ b/teos-common/src/receipts.rs @@ -1,6 +1,6 @@ //! Receipts issued by towers and handed to users as commitment proof. -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; use bitcoin::secp256k1::SecretKey; diff --git a/teos-ldk-client/src/lib.rs b/teos-ldk-client/src/lib.rs index b787bffa..1eb07423 100644 --- a/teos-ldk-client/src/lib.rs +++ b/teos-ldk-client/src/lib.rs @@ -4,8 +4,6 @@ use std::io; use serde::{Deserialize, Serialize}; -use bincode; - use teos_common::appointment::{Appointment, Locator}; use teos_common::net::NetAddr; use teos_common::receipts::AppointmentReceipt; diff --git a/teos-ldk-client/src/retrier.rs b/teos-ldk-client/src/retrier.rs index 8a6ee64a..dd889e1a 100644 --- a/teos-ldk-client/src/retrier.rs +++ b/teos-ldk-client/src/retrier.rs @@ -1,6 +1,4 @@ use std::collections::{HashMap, HashSet}; -use crate::storage::Storage; -use crate::storage::storage::{Persister, StorageError}; use std::fmt::Display; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; @@ -576,6 +574,9 @@ impl Retrier { mod tests { use super::*; + use crate::storage::storage::Persister; + use crate::storage::Storage; + use bitcoin::secp256k1::{PublicKey, Secp256k1}; use serde_json::json; use tempdir::TempDir; @@ -635,12 +636,7 @@ mod tests { let storage = Box::new(Storage::new(&db_path).unwrap()); let wt_client = Arc::new(Mutex::new( - WTClient::new( - storage, - keypair.0, - unbounded_channel().0, - ) - .await, + WTClient::new(storage, keypair.0, unbounded_channel().0).await, )); let mut server = mockito::Server::new_async().await; @@ -738,12 +734,7 @@ mod tests { let storage = Box::new(Storage::new(&db_path).unwrap()); let wt_client = Arc::new(Mutex::new( - WTClient::new( - storage, - keypair.0, - unbounded_channel().0, - ) - .await, + WTClient::new(storage, keypair.0, unbounded_channel().0).await, )); // Add a tower with pending appointments @@ -874,12 +865,7 @@ mod tests { let storage = Box::new(Storage::new(&db_path).unwrap()); let wt_client = Arc::new(Mutex::new( - WTClient::new( - storage, - keypair.0, - unbounded_channel().0, - ) - .await, + WTClient::new(storage, keypair.0, unbounded_channel().0).await, )); let mut server = mockito::Server::new_async().await; @@ -987,12 +973,7 @@ mod tests { let storage = Box::new(Storage::new(&db_path).unwrap()); let wt_client = Arc::new(Mutex::new( - WTClient::new( - storage, - keypair.0, - unbounded_channel().0, - ) - .await, + WTClient::new(storage, keypair.0, unbounded_channel().0).await, )); let mut server = mockito::Server::new_async().await; @@ -1088,12 +1069,7 @@ mod tests { let storage = Box::new(Storage::new(&db_path).unwrap()); let wt_client = Arc::new(Mutex::new( - WTClient::new( - storage, - keypair.0, - unbounded_channel().0, - ) - .await, + WTClient::new(storage, keypair.0, unbounded_channel().0).await, )); let server = mockito::Server::new_async().await; @@ -1140,12 +1116,7 @@ mod tests { let storage = Box::new(Storage::new(&db_path).unwrap()); let wt_client = Arc::new(Mutex::new( - WTClient::new( - storage, - keypair.0, - unbounded_channel().0, - ) - .await, + WTClient::new(storage, keypair.0, unbounded_channel().0).await, )); let mut server = mockito::Server::new_async().await; @@ -1422,12 +1393,7 @@ mod tests { let storage = Box::new(Storage::new(&db_path).unwrap()); let wt_client = Arc::new(Mutex::new( - WTClient::new( - storage, - keypair.0, - unbounded_channel().0, - ) - .await, + WTClient::new(storage, keypair.0, unbounded_channel().0).await, )); let mut server = mockito::Server::new_async().await; @@ -1481,12 +1447,7 @@ mod tests { let storage = Box::new(Storage::new(&db_path).unwrap()); let wt_client = Arc::new(Mutex::new( - WTClient::new( - storage, - keypair.0, - unbounded_channel().0, - ) - .await, + WTClient::new(storage, keypair.0, unbounded_channel().0).await, )); let server = mockito::Server::new_async().await; @@ -1514,12 +1475,7 @@ mod tests { let storage = Box::new(Storage::new(&db_path).unwrap()); let wt_client = Arc::new(Mutex::new( - WTClient::new( - storage, - keypair.0, - unbounded_channel().0, - ) - .await, + WTClient::new(storage, keypair.0, unbounded_channel().0).await, )); let mut server = mockito::Server::new_async().await; @@ -1575,12 +1531,7 @@ mod tests { let storage = Box::new(Storage::new(&db_path).unwrap()); let wt_client = Arc::new(Mutex::new( - WTClient::new( - storage, - keypair.0, - unbounded_channel().0, - ) - .await, + WTClient::new(storage, keypair.0, unbounded_channel().0).await, )); // The tower we'd like to retry sending appointments to has to exist within the plugin @@ -1616,12 +1567,7 @@ mod tests { let storage = Box::new(Storage::new(&db_path).unwrap()); let wt_client = Arc::new(Mutex::new( - WTClient::new( - storage, - keypair.0, - unbounded_channel().0, - ) - .await, + WTClient::new(storage, keypair.0, unbounded_channel().0).await, )); let mut server = mockito::Server::new_async().await; @@ -1680,12 +1626,7 @@ mod tests { let storage = Box::new(Storage::new(&db_path).unwrap()); let wt_client = Arc::new(Mutex::new( - WTClient::new( - storage, - keypair.0, - unbounded_channel().0, - ) - .await, + WTClient::new(storage, keypair.0, unbounded_channel().0).await, )); let mut server = mockito::Server::new_async().await; @@ -1750,12 +1691,7 @@ mod tests { let storage = Box::new(Storage::new(&db_path).unwrap()); let wt_client = Arc::new(Mutex::new( - WTClient::new( - storage, - keypair.0, - unbounded_channel().0, - ) - .await, + WTClient::new(storage, keypair.0, unbounded_channel().0).await, )); // The tower we'd like to retry sending appointments to has to exist within the plugin diff --git a/teos-ldk-client/src/storage/dbm.rs b/teos-ldk-client/src/storage/dbm.rs index 57da162a..3371c08f 100644 --- a/teos-ldk-client/src/storage/dbm.rs +++ b/teos-ldk-client/src/storage/dbm.rs @@ -184,7 +184,6 @@ impl DBM { ], ) } - } impl Persister for DBM { @@ -572,11 +571,7 @@ impl Persister for DBM { /// /// This is meant to be used only for pending and invalid appointments, if the method is called for /// accepted appointment, an empty collection will be returned. - fn load_appointments( - &self, - tower_id: TowerId, - status: AppointmentStatus, - ) -> Vec { + fn load_appointments(&self, tower_id: TowerId, status: AppointmentStatus) -> Vec { let table = match status { AppointmentStatus::Accepted => return Vec::new(), AppointmentStatus::Pending => "pending_appointments", @@ -644,11 +639,7 @@ impl Persister for DBM { stmt.exists(params![locator.to_vec()]).unwrap() } - fn appointment_receipt_exists( - &self, - locator: Locator, - tower_id: TowerId, - ) -> bool { + 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 ") @@ -677,7 +668,7 @@ mod tests { Ok(dbm) } } - + #[test] fn test_create_tables() { let connection = Connection::open_in_memory().unwrap(); @@ -843,9 +834,8 @@ mod tests { fn test_remove_tower_record_inexistent() { let dbm = DBM::in_memory().unwrap(); - match dbm.remove_tower_record(get_random_user_id()) { - Ok(_) => panic!("Tower record was removed when it shouldn't have"), - Err(_) => {} + if dbm.remove_tower_record(get_random_user_id()).is_ok() { + panic!("Tower record was removed when it shouldn't have") } } diff --git a/teos-ldk-client/src/storage/kv.rs b/teos-ldk-client/src/storage/kv.rs index 9e138f01..2e74605b 100644 --- a/teos-ldk-client/src/storage/kv.rs +++ b/teos-ldk-client/src/storage/kv.rs @@ -19,7 +19,6 @@ use bitcoin::hashes::{sha256, Hash}; // use bitcoin::{Transaction, Txid}; // use lightning::util::message_signing; - use crate::{AppointmentStatus, MisbehaviorProof, TowerInfo, TowerStatus, TowerSummary}; // Primary namespace for all watchtower-related data @@ -59,7 +58,7 @@ impl Storage { pub fn new(store: impl T, sk: Vec) -> Result { Ok(Storage { store: Box::new(store), - sk + sk, }) } @@ -340,10 +339,7 @@ impl Storage { /// - `[0; 12]` as IV. /// /// The message to be encrypted is expected to be the penalty transaction. -fn encrypt( - message: &Vec, - secret: &Vec, -) -> Result, chacha20poly1305::aead::Error> { +fn encrypt(message: &Vec, secret: &Vec) -> Result, chacha20poly1305::aead::Error> { // Defaults is [0; 12] let nonce = Nonce::default(); let k = sha256::Hash::hash(secret); @@ -361,7 +357,10 @@ fn encrypt( /// - `[0; 12]` as IV. /// /// The result is expected to be a penalty transaction. -fn decrypt(encrypted_blob: &[u8], secret: &Vec) -> Result, chacha20poly1305::aead::Error> { +fn decrypt( + encrypted_blob: &[u8], + secret: &Vec, +) -> Result, chacha20poly1305::aead::Error> { // Defaults is [0; 12] let nonce = Nonce::default(); let k = sha256::Hash::hash(secret); @@ -408,7 +407,8 @@ mod tests { ); // Check the loaded data matches the in memory data - storage.store_tower_record(tower_id, net_addr, &receipt) + storage + .store_tower_record(tower_id, net_addr, &receipt) .unwrap(); assert_eq!(storage.load_tower_record(tower_id).unwrap(), tower_info); } diff --git a/teos-ldk-client/src/storage/memory_store.rs b/teos-ldk-client/src/storage/memory_store.rs index c6383b4c..fdf15294 100644 --- a/teos-ldk-client/src/storage/memory_store.rs +++ b/teos-ldk-client/src/storage/memory_store.rs @@ -1,8 +1,7 @@ - -use std::collections::HashMap; -use std::sync::{Arc, Mutex}; use lightning::io::Error as DBError; use lightning::util::persist::KVStore; +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; /// In-memory key-value store implementation for testing #[derive(Clone, Debug)] @@ -31,8 +30,16 @@ impl Default for MemoryStore { } impl KVStore for MemoryStore { - fn read(&self, primary_namespace: &str, secondary_namespace: &str, key: &str) -> Result, DBError> { - let data = self.data.lock().map_err(|_| DBError::new(bitcoin::io::ErrorKind::AddrInUse, "Lock poisoned"))?; + fn read( + &self, + primary_namespace: &str, + secondary_namespace: &str, + key: &str, + ) -> Result, DBError> { + let data = self + .data + .lock() + .map_err(|_| DBError::new(bitcoin::io::ErrorKind::AddrInUse, "Lock poisoned"))?; let namespace = Self::make_key(primary_namespace, secondary_namespace); data.get(&namespace) @@ -41,8 +48,17 @@ impl KVStore for MemoryStore { .ok_or_else(|| DBError::new(bitcoin::io::ErrorKind::NotFound, "Key not found")) } - fn write(&self, primary_namespace: &str, secondary_namespace: &str, key: &str, value: &[u8]) -> Result<(), DBError> { - let mut data = self.data.lock().map_err(|_| DBError::new(bitcoin::io::ErrorKind::AddrInUse, "Lock poisoned"))?; + fn write( + &self, + primary_namespace: &str, + secondary_namespace: &str, + key: &str, + value: &[u8], + ) -> Result<(), DBError> { + let mut data = self + .data + .lock() + .map_err(|_| DBError::new(bitcoin::io::ErrorKind::AddrInUse, "Lock poisoned"))?; let namespace = Self::make_key(primary_namespace, secondary_namespace); let ns_map = data.entry(namespace).or_default(); @@ -51,23 +67,43 @@ impl KVStore for MemoryStore { Ok(()) } - fn remove(&self, primary_namespace: &str, secondary_namespace: &str, key: &str, _lazy: bool) -> Result<(), DBError> { - let mut data = self.data.lock().map_err(|_| DBError::new(bitcoin::io::ErrorKind::AddrInUse, "Lock poisoned"))?; + fn remove( + &self, + primary_namespace: &str, + secondary_namespace: &str, + key: &str, + _lazy: bool, + ) -> Result<(), DBError> { + let mut data = self + .data + .lock() + .map_err(|_| DBError::new(bitcoin::io::ErrorKind::AddrInUse, "Lock poisoned"))?; let namespace = Self::make_key(primary_namespace, secondary_namespace); if let Some(ns_map) = data.get_mut(&namespace) { ns_map.remove(key); Ok(()) } else { - Err(DBError::new(bitcoin::io::ErrorKind::NotFound, "Key not found")) + Err(DBError::new( + bitcoin::io::ErrorKind::NotFound, + "Key not found", + )) } } - fn list(&self, primary_namespace: &str, secondary_namespace: &str) -> Result, DBError> { - let data = self.data.lock().map_err(|_| DBError::new(bitcoin::io::ErrorKind::AddrInUse, "Lock poisoned"))?; + fn list( + &self, + primary_namespace: &str, + secondary_namespace: &str, + ) -> Result, DBError> { + let data = self + .data + .lock() + .map_err(|_| DBError::new(bitcoin::io::ErrorKind::AddrInUse, "Lock poisoned"))?; let namespace = Self::make_key(primary_namespace, secondary_namespace); - let res = data.get(&namespace) + let res = data + .get(&namespace) .map(|ns_map| ns_map.keys().cloned().collect()) .unwrap(); @@ -84,16 +120,25 @@ mod tests { let store = MemoryStore::new(); // Test write and read - store.write("primary", "secondary", "key1", b"value1").unwrap(); - assert_eq!(store.read("primary", "secondary", "key1").unwrap(), b"value1"); + store + .write("primary", "secondary", "key1", b"value1") + .unwrap(); + assert_eq!( + store.read("primary", "secondary", "key1").unwrap(), + b"value1" + ); // Test remove store.remove("primary", "secondary", "key1", false).unwrap(); assert!(store.read("primary", "secondary", "key1").is_err()); // Test list - store.write("primary", "secondary", "key1", b"value1").unwrap(); - store.write("primary", "secondary", "key2", b"value2").unwrap(); + store + .write("primary", "secondary", "key1", b"value1") + .unwrap(); + store + .write("primary", "secondary", "key2", b"value2") + .unwrap(); let keys = store.list("primary", "secondary").unwrap(); assert_eq!(keys.len(), 2); assert!(keys.contains(&"key1".to_string())); @@ -105,13 +150,28 @@ mod tests { let store = MemoryStore::new(); // Write same key to different namespaces - store.write("primary1", "secondary1", "key", b"value1").unwrap(); - store.write("primary1", "secondary2", "key", b"value2").unwrap(); - store.write("primary2", "secondary1", "key", b"value3").unwrap(); + store + .write("primary1", "secondary1", "key", b"value1") + .unwrap(); + store + .write("primary1", "secondary2", "key", b"value2") + .unwrap(); + store + .write("primary2", "secondary1", "key", b"value3") + .unwrap(); // Verify they don't interfere - assert_eq!(store.read("primary1", "secondary1", "key").unwrap(), b"value1"); - assert_eq!(store.read("primary1", "secondary2", "key").unwrap(), b"value2"); - assert_eq!(store.read("primary2", "secondary1", "key").unwrap(), b"value3"); + assert_eq!( + store.read("primary1", "secondary1", "key").unwrap(), + b"value1" + ); + assert_eq!( + store.read("primary1", "secondary2", "key").unwrap(), + b"value2" + ); + assert_eq!( + store.read("primary2", "secondary1", "key").unwrap(), + b"value3" + ); } } diff --git a/teos-ldk-client/src/storage/mod.rs b/teos-ldk-client/src/storage/mod.rs index 87ac5901..86337833 100644 --- a/teos-ldk-client/src/storage/mod.rs +++ b/teos-ldk-client/src/storage/mod.rs @@ -18,9 +18,9 @@ pub use dbm::DBM as Storage; #[cfg(feature = "kv")] mod kv; #[cfg(feature = "kv")] -pub(crate) use kv::Storage; -#[cfg(feature = "kv")] pub use kv::DBError; +#[cfg(feature = "kv")] +pub(crate) use kv::Storage; #[cfg(test)] #[cfg(feature = "kv")] diff --git a/teos-ldk-client/src/storage/storage.rs b/teos-ldk-client/src/storage/storage.rs index 6ebcc7fc..4731ba7f 100644 --- a/teos-ldk-client/src/storage/storage.rs +++ b/teos-ldk-client/src/storage/storage.rs @@ -141,11 +141,7 @@ pub trait Persister: Send { /// /// This is meant to be used only for pending and invalid appointments, if the method is called for /// accepted appointment, an empty collection will be returned. - fn load_appointments( - &self, - tower_id: TowerId, - status: AppointmentStatus, - ) -> Vec; + fn load_appointments(&self, tower_id: TowerId, status: AppointmentStatus) -> Vec; /// Stores a misbehaving proof into the database. /// @@ -157,7 +153,7 @@ pub trait Persister: Send { proof: &MisbehaviorProof, ) -> Result<(), StorageError>; - fn appointment_exists(&self, locator: Locator) -> bool; + fn appointment_exists(&self, locator: Locator) -> bool; fn appointment_receipt_exists(&self, locator: Locator, tower_id: TowerId) -> bool; } diff --git a/teos-ldk-client/src/wt_client.rs b/teos-ldk-client/src/wt_client.rs index e46a790d..150952dd 100644 --- a/teos-ldk-client/src/wt_client.rs +++ b/teos-ldk-client/src/wt_client.rs @@ -2,25 +2,18 @@ use crate::storage::storage::{Persister, StorageError}; use std::collections::{HashMap, HashSet}; use std::iter::FromIterator; -#[cfg(feature = "sqlite")] -use std::path::PathBuf; -#[cfg(feature = "sqlite")] -use tokio::fs; use tokio::sync::mpsc::UnboundedSender; use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; use teos_common::appointment::{Appointment, Locator}; -#[cfg(feature = "sqlite")] -use teos_common::cryptography; use teos_common::receipts::{AppointmentReceipt, RegistrationReceipt}; use teos_common::{TowerId, UserId}; use crate::retrier::RetrierStatus; -use crate::storage::Storage; +use crate::{MisbehaviorProof, SubscriptionError, TowerInfo, TowerStatus, TowerSummary}; #[cfg(feature = "kv")] use lightning::util::persist::KVStore; -use crate::{MisbehaviorProof, SubscriptionError, TowerInfo, TowerStatus, TowerSummary}; #[derive(Eq, PartialEq)] pub enum RevocationData { @@ -110,7 +103,6 @@ impl WTClient { } } - /// Adds or updates a tower entry. pub fn add_update_tower( &mut self, @@ -288,7 +280,9 @@ impl WTClient { mod tests { use super::*; + use crate::storage::Storage; use tempdir::TempDir; + use teos_common::cryptography; use tokio::sync::mpsc::unbounded_channel; use teos_common::test_utils::{ @@ -304,13 +298,8 @@ mod tests { let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); let storage = Box::new(Storage::new(&db_path).unwrap()); - - let mut wt_client = WTClient::new( - storage, - keypair.0, - unbounded_channel().0, - ) - .await; + + let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; // Adding a new tower will add a summary to towers and the full data to the let mut receipt = get_random_registration_receipt(); @@ -408,11 +397,7 @@ mod tests { let db_path = tmp_path.path().join("watchtower.db"); let storage = Box::new(Storage::new(&db_path).unwrap()); - let mut wt_client = WTClient::new( - storage, - keypair.0, - unbounded_channel().0, - ).await; + let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; // If the tower is unknown, get_tower_status returns None let tower_id = get_random_user_id(); @@ -439,12 +424,7 @@ mod tests { let db_path = tmp_path.path().join("watchtower.db"); let storage = Box::new(Storage::new(&db_path).unwrap()); - let mut wt_client = WTClient::new( - storage, - keypair.0, - unbounded_channel().0, - ) - .await; + let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; // If the tower is unknown nothing will happen let unknown_tower = get_random_user_id(); @@ -478,12 +458,7 @@ mod tests { let db_path = tmp_path.path().join("watchtower.db"); let storage = Box::new(Storage::new(&db_path).unwrap()); - let mut wt_client = WTClient::new( - storage, - keypair.0, - unbounded_channel().0, - ) - .await; + let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; let (tower_sk, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); @@ -537,12 +512,7 @@ mod tests { let db_path = tmp_path.path().join("watchtower.db"); let storage = Box::new(Storage::new(&db_path).unwrap()); - let mut wt_client = WTClient::new( - storage, - keypair.0, - unbounded_channel().0, - ) - .await; + let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; let tower_id = get_random_user_id(); @@ -589,12 +559,7 @@ mod tests { let db_path = tmp_path.path().join("watchtower.db"); let storage = Box::new(Storage::new(&db_path).unwrap()); - let mut wt_client = WTClient::new( - storage, - keypair.0, - unbounded_channel().0, - ) - .await; + let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; let tower_id = get_random_user_id(); @@ -629,12 +594,7 @@ mod tests { let db_path = tmp_path.path().join("watchtower.db"); let storage = Box::new(Storage::new(&db_path).unwrap()); - let mut wt_client = WTClient::new( - storage, - keypair.0, - unbounded_channel().0, - ) - .await; + let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; let tower_id = get_random_user_id(); @@ -677,12 +637,7 @@ mod tests { let db_path = tmp_path.path().join("watchtower.db"); let storage = Box::new(Storage::new(&db_path).unwrap()); - let mut wt_client = WTClient::new( - storage, - keypair.0, - unbounded_channel().0, - ) - .await; + let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; let tower_id = get_random_user_id(); @@ -730,12 +685,7 @@ mod tests { let db_path = tmp_path.path().join("watchtower.db"); let storage = Box::new(Storage::new(&db_path).unwrap()); - let mut wt_client = WTClient::new( - storage, - keypair.0, - unbounded_channel().0, - ) - .await; + let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; let tower_id = get_random_user_id(); let another_tower_id = get_random_user_id(); @@ -813,12 +763,7 @@ mod tests { let db_path = tmp_path.path().join("watchtower.db"); let storage = Box::new(Storage::new(&db_path).unwrap()); - let mut wt_client = WTClient::new( - storage, - keypair.0, - unbounded_channel().0, - ) - .await; + let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; let (tower_sk, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); @@ -857,12 +802,7 @@ mod tests { let db_path = tmp_path.path().join("watchtower.db"); let storage = Box::new(Storage::new(&db_path).unwrap()); - let mut wt_client = WTClient::new( - storage, - keypair.0, - unbounded_channel().0, - ) - .await; + let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; let receipt = get_random_registration_receipt(); let (tower_sk, tower_pk) = cryptography::get_random_keypair(); @@ -931,12 +871,7 @@ mod tests { let db_path = tmp_path.path().join("watchtower.db"); let storage = Box::new(Storage::new(&db_path).unwrap()); - let mut wt_client = WTClient::new( - storage, - keypair.0, - unbounded_channel().0, - ) - .await; + let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; let receipt = get_random_registration_receipt(); let (tower1_sk, tower1_pk) = cryptography::get_random_keypair(); @@ -997,16 +932,10 @@ mod tests { let db_path = tmp_path.path().join("watchtower.db"); let storage = Box::new(Storage::new(&db_path).unwrap()); - let mut wt_client = WTClient::new( - storage, - keypair.0, - unbounded_channel().0, - ) - .await; + let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; - match wt_client.remove_tower(get_random_user_id()) { - Ok(_) => panic!("Tower record was removed when it shouldn't have"), - Err(_) => {} + if wt_client.remove_tower(get_random_user_id()).is_ok() { + panic!("Tower record was removed when it shouldn't have") } } } diff --git a/watchtower-plugin/src/lib.rs b/watchtower-plugin/src/lib.rs index 78c2e6b5..145f2a13 100755 --- a/watchtower-plugin/src/lib.rs +++ b/watchtower-plugin/src/lib.rs @@ -1,7 +1,7 @@ use std::collections::{HashMap, HashSet}; use std::fmt; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; use teos_common::appointment::{Appointment, Locator}; use teos_common::net::NetAddr; From 4d91b923fa7e50afedf50707a5465e9ecb72dadb Mon Sep 17 00:00:00 2001 From: dzdidi Date: Wed, 12 Feb 2025 09:36:53 +0100 Subject: [PATCH 48/85] rename storage to persister Signed-off-by: dzdidi --- teos-ldk-client/src/retrier.rs | 2 +- teos-ldk-client/src/storage/dbm.rs | 22 +++++++-------- teos-ldk-client/src/storage/mod.rs | 2 +- .../src/storage/{storage.rs => persister.rs} | 28 +++++++++---------- teos-ldk-client/src/wt_client.rs | 6 ++-- 5 files changed, 30 insertions(+), 30 deletions(-) rename teos-ldk-client/src/storage/{storage.rs => persister.rs} (89%) diff --git a/teos-ldk-client/src/retrier.rs b/teos-ldk-client/src/retrier.rs index dd889e1a..d6029b1d 100644 --- a/teos-ldk-client/src/retrier.rs +++ b/teos-ldk-client/src/retrier.rs @@ -574,7 +574,7 @@ impl Retrier { mod tests { use super::*; - use crate::storage::storage::Persister; + use crate::storage::persister::Persister; use crate::storage::Storage; use bitcoin::secp256k1::{PublicKey, Secp256k1}; diff --git a/teos-ldk-client/src/storage/dbm.rs b/teos-ldk-client/src/storage/dbm.rs index 3371c08f..940dfc7c 100644 --- a/teos-ldk-client/src/storage/dbm.rs +++ b/teos-ldk-client/src/storage/dbm.rs @@ -10,13 +10,13 @@ use teos_common::dbm::{DatabaseConnection, DatabaseManager}; use teos_common::receipts::{AppointmentReceipt, RegistrationReceipt}; use teos_common::{TowerId, UserId}; -use crate::storage::storage::{Persister, StorageError}; +use crate::storage::persister::{Persister, PersisterError}; use crate::{AppointmentStatus, MisbehaviorProof, TowerInfo, TowerStatus, TowerSummary}; -impl From for StorageError { +impl From for PersisterError { fn from(error: SqliteError) -> Self { - StorageError::Other(error.to_string()) + PersisterError::Other(error.to_string()) } } @@ -196,7 +196,7 @@ impl Persister for DBM { tower_id: TowerId, net_addr: &str, receipt: &RegistrationReceipt, - ) -> Result<(), StorageError> { + ) -> Result<(), PersisterError> { let tx = self.get_mut_connection().transaction().unwrap(); tx.execute( "INSERT INTO towers (tower_id, net_addr, available_slots) @@ -292,11 +292,11 @@ impl Persister for DBM { /// /// 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. - fn remove_tower_record(&self, tower_id: TowerId) -> Result<(), StorageError> { + fn remove_tower_record(&self, tower_id: TowerId) -> Result<(), PersisterError> { let query = "DELETE FROM towers WHERE tower_id=?"; match self.connection.execute(query, params![tower_id.to_vec()]) { Ok(_) => Ok(()), - Err(e) => Err(StorageError::Other(e.to_string())), + Err(e) => Err(PersisterError::Other(e.to_string())), } } @@ -355,7 +355,7 @@ impl Persister for DBM { locator: Locator, available_slots: u32, receipt: &AppointmentReceipt, - ) -> Result<(), StorageError> { + ) -> Result<(), PersisterError> { let tx = self.get_mut_connection().transaction().unwrap(); tx.execute( "INSERT INTO appointment_receipts (locator, tower_id, start_block, user_signature, tower_signature) @@ -478,7 +478,7 @@ impl Persister for DBM { &mut self, tower_id: TowerId, appointment: &Appointment, - ) -> Result<(), StorageError> { + ) -> Result<(), PersisterError> { let tx = self.get_mut_connection().transaction().unwrap(); // If the appointment already exists (because it was added by another tower as either pending or invalid) we simply @@ -501,7 +501,7 @@ impl Persister for DBM { &mut self, tower_id: TowerId, locator: Locator, - ) -> Result<(), StorageError> { + ) -> Result<(), PersisterError> { // We will delete data from pending_appointments or from appointments depending on whether the later has a single reference // to it or not. If that's the case, deleting the entry from appointments will trigger a cascade deletion of the entry in pending. // If there are other references, this will be deleted when removing the last one. @@ -551,7 +551,7 @@ impl Persister for DBM { &mut self, tower_id: TowerId, appointment: &Appointment, - ) -> Result<(), StorageError> { + ) -> Result<(), PersisterError> { let tx = self.get_mut_connection().transaction().unwrap(); // If the appointment already exists (because it was added by another tower as either pending or invalid) we simply @@ -604,7 +604,7 @@ impl Persister for DBM { &mut self, tower_id: TowerId, proof: &MisbehaviorProof, - ) -> Result<(), StorageError> { + ) -> Result<(), PersisterError> { let tx = self.get_mut_connection().transaction().unwrap(); tx.execute( "INSERT INTO appointment_receipts (tower_id, locator, start_block, user_signature, tower_signature) diff --git a/teos-ldk-client/src/storage/mod.rs b/teos-ldk-client/src/storage/mod.rs index 86337833..bd9cc69d 100644 --- a/teos-ldk-client/src/storage/mod.rs +++ b/teos-ldk-client/src/storage/mod.rs @@ -1,4 +1,4 @@ -pub mod storage; +pub mod persister; #[cfg(all(not(feature = "sqlite"), not(feature = "kv")))] compile_error!( diff --git a/teos-ldk-client/src/storage/storage.rs b/teos-ldk-client/src/storage/persister.rs similarity index 89% rename from teos-ldk-client/src/storage/storage.rs rename to teos-ldk-client/src/storage/persister.rs index 4731ba7f..c1bff899 100644 --- a/teos-ldk-client/src/storage/storage.rs +++ b/teos-ldk-client/src/storage/persister.rs @@ -9,7 +9,7 @@ use crate::{AppointmentStatus, MisbehaviorProof, TowerInfo, TowerSummary}; /// A general storage error type that can be used across different storage implementations #[derive(Debug)] -pub enum StorageError { +pub enum PersisterError { /// Error when storing data StoreError(String), /// Error when retrieving data @@ -20,18 +20,18 @@ pub enum StorageError { Other(String), } -impl fmt::Display for StorageError { +impl fmt::Display for PersisterError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - StorageError::StoreError(msg) => write!(f, "Storage store error: {}", msg), - StorageError::RetrievalError(msg) => write!(f, "Storage retrieval error: {}", msg), - StorageError::NotFound(msg) => write!(f, "Data not found: {}", msg), - StorageError::Other(msg) => write!(f, "Storage error: {}", msg), + PersisterError::StoreError(msg) => write!(f, "Storage store error: {}", msg), + PersisterError::RetrievalError(msg) => write!(f, "Storage retrieval error: {}", msg), + PersisterError::NotFound(msg) => write!(f, "Data not found: {}", msg), + PersisterError::Other(msg) => write!(f, "Storage error: {}", msg), } } } -impl std::error::Error for StorageError {} +impl std::error::Error for PersisterError {} /// Trait defining the interface for database operations pub trait Persister: Send { @@ -44,7 +44,7 @@ pub trait Persister: Send { tower_id: TowerId, net_addr: &str, receipt: &RegistrationReceipt, - ) -> Result<(), StorageError>; + ) -> Result<(), PersisterError>; /// Loads a tower record from the database. /// @@ -66,7 +66,7 @@ pub trait Persister: Send { /// /// 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. - fn remove_tower_record(&self, tower_id: TowerId) -> Result<(), StorageError>; + fn remove_tower_record(&self, tower_id: TowerId) -> Result<(), PersisterError>; /// Loads all tower records from the database. fn load_towers(&self) -> HashMap; @@ -78,7 +78,7 @@ pub trait Persister: Send { locator: Locator, available_slots: u32, receipt: &AppointmentReceipt, - ) -> Result<(), StorageError>; + ) -> Result<(), PersisterError>; /// Loads a given appointment receipt of a given tower from the database. fn load_appointment_receipt( @@ -115,7 +115,7 @@ pub trait Persister: Send { &mut self, tower_id: TowerId, appointment: &Appointment, - ) -> Result<(), StorageError>; + ) -> Result<(), PersisterError>; /// Removes a pending appointment from the database. /// @@ -124,7 +124,7 @@ pub trait Persister: Send { &mut self, tower_id: TowerId, locator: Locator, - ) -> Result<(), StorageError>; + ) -> Result<(), PersisterError>; /// Stores an invalid appointment into the database. /// @@ -135,7 +135,7 @@ pub trait Persister: Send { &mut self, tower_id: TowerId, appointment: &Appointment, - ) -> Result<(), StorageError>; + ) -> Result<(), PersisterError>; /// Loads non finalized appointments from the database for a given tower based on a status flag. /// @@ -151,7 +151,7 @@ pub trait Persister: Send { &mut self, tower_id: TowerId, proof: &MisbehaviorProof, - ) -> Result<(), StorageError>; + ) -> Result<(), PersisterError>; fn appointment_exists(&self, locator: Locator) -> bool; diff --git a/teos-ldk-client/src/wt_client.rs b/teos-ldk-client/src/wt_client.rs index 150952dd..81741dfc 100644 --- a/teos-ldk-client/src/wt_client.rs +++ b/teos-ldk-client/src/wt_client.rs @@ -1,4 +1,4 @@ -use crate::storage::storage::{Persister, StorageError}; +use crate::storage::persister::{Persister, PersisterError}; use std::collections::{HashMap, HashSet}; use std::iter::FromIterator; @@ -266,12 +266,12 @@ impl WTClient { /// 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<(), StorageError> { + pub fn remove_tower(&mut self, tower_id: TowerId) -> Result<(), PersisterError> { if self.towers.contains_key(&tower_id) { self.towers.remove(&tower_id); self.storage.remove_tower_record(tower_id) } else { - Err(StorageError::NotFound("foo".to_string())) + Err(PersisterError::NotFound("foo".to_string())) } } } From 90f119d3039851d89ddefcded658190e409cbd94 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Wed, 12 Feb 2025 10:37:09 +0100 Subject: [PATCH 49/85] fix tests Signed-off-by: dzdidi --- teos-ldk-client/src/retrier.rs | 13 ++++++++++--- teos-ldk-client/src/storage/dbm.rs | 18 ++++++++++++------ teos-ldk-client/src/storage/persister.rs | 2 +- teos-ldk-client/src/wt_client.rs | 11 +++++++---- 4 files changed, 30 insertions(+), 14 deletions(-) diff --git a/teos-ldk-client/src/retrier.rs b/teos-ldk-client/src/retrier.rs index d6029b1d..1ee311c7 100644 --- a/teos-ldk-client/src/retrier.rs +++ b/teos-ldk-client/src/retrier.rs @@ -1255,13 +1255,14 @@ mod tests { WTClient::new( Box::new(Storage::new(&db_path).unwrap()), keypair.0, - unbounded_channel().0, + 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, @@ -1280,9 +1281,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); diff --git a/teos-ldk-client/src/storage/dbm.rs b/teos-ldk-client/src/storage/dbm.rs index 940dfc7c..2caeb439 100644 --- a/teos-ldk-client/src/storage/dbm.rs +++ b/teos-ldk-client/src/storage/dbm.rs @@ -294,9 +294,12 @@ impl Persister for DBM { /// reference to them. fn remove_tower_record(&self, tower_id: TowerId) -> Result<(), PersisterError> { let query = "DELETE FROM towers WHERE tower_id=?"; - match self.connection.execute(query, params![tower_id.to_vec()]) { - Ok(_) => Ok(()), - Err(e) => Err(PersisterError::Other(e.to_string())), + match self.load_tower_record(tower_id) { + None => Err(PersisterError::NotFound(format!("tower_id: {tower_id}"))), + Some(_tower) => match self.connection.execute(query, params![tower_id.to_vec()]) { + Ok(_) => Ok(()), + Err(e) => Err(PersisterError::Other(e.to_string())), + }, } } @@ -834,9 +837,12 @@ mod tests { fn test_remove_tower_record_inexistent() { let dbm = DBM::in_memory().unwrap(); - if dbm.remove_tower_record(get_random_user_id()).is_ok() { - panic!("Tower record was removed when it shouldn't have") - } + let tower_id = get_random_user_id(); + let err = dbm.remove_tower_record(tower_id).unwrap_err(); + assert_eq!( + err, + PersisterError::NotFound(format!("tower_id: {tower_id}")) + ); } #[test] diff --git a/teos-ldk-client/src/storage/persister.rs b/teos-ldk-client/src/storage/persister.rs index c1bff899..0d1122a4 100644 --- a/teos-ldk-client/src/storage/persister.rs +++ b/teos-ldk-client/src/storage/persister.rs @@ -8,7 +8,7 @@ use teos_common::{TowerId, UserId}; use crate::{AppointmentStatus, MisbehaviorProof, TowerInfo, TowerSummary}; /// A general storage error type that can be used across different storage implementations -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub enum PersisterError { /// Error when storing data StoreError(String), diff --git a/teos-ldk-client/src/wt_client.rs b/teos-ldk-client/src/wt_client.rs index 81741dfc..3107590c 100644 --- a/teos-ldk-client/src/wt_client.rs +++ b/teos-ldk-client/src/wt_client.rs @@ -271,7 +271,7 @@ impl WTClient { self.towers.remove(&tower_id); self.storage.remove_tower_record(tower_id) } else { - Err(PersisterError::NotFound("foo".to_string())) + Err(PersisterError::NotFound(format!("tower_id: {tower_id}"))) } } } @@ -934,8 +934,11 @@ mod tests { let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; - if wt_client.remove_tower(get_random_user_id()).is_ok() { - panic!("Tower record was removed when it shouldn't have") - } + let tower_id = get_random_user_id(); + let err = wt_client.remove_tower(tower_id).unwrap_err(); + assert_eq!( + err, + PersisterError::NotFound(format!("tower_id: {tower_id}")) + ); } } From 9c0d75f217068a1d7627dc802b2029a7deb2d142 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Wed, 12 Feb 2025 14:34:52 +0100 Subject: [PATCH 50/85] rename dbm to sql_storage Signed-off-by: dzdidi --- teos-ldk-client/src/storage/mod.rs | 16 ++-------------- .../src/storage/{dbm.rs => sql_storage.rs} | 1 - 2 files changed, 2 insertions(+), 15 deletions(-) rename teos-ldk-client/src/storage/{dbm.rs => sql_storage.rs} (99%) diff --git a/teos-ldk-client/src/storage/mod.rs b/teos-ldk-client/src/storage/mod.rs index bd9cc69d..a160e6c9 100644 --- a/teos-ldk-client/src/storage/mod.rs +++ b/teos-ldk-client/src/storage/mod.rs @@ -1,19 +1,7 @@ pub mod persister; -#[cfg(all(not(feature = "sqlite"), not(feature = "kv")))] -compile_error!( - "No storage backend enabled. Please enable one of the following features: sqlite, kv" -); - -#[cfg(all(feature = "sqlite", feature = "kv"))] -compile_error!("Only one of 'sqlite' or 'kv' features can be enabled"); - -#[cfg(feature = "sqlite")] -mod dbm; -// #[cfg(feature = "sqlite")] -// pub use dbm::DBError; -#[cfg(feature = "sqlite")] -pub use dbm::DBM as Storage; +mod sql_storage; +pub use sql_storage::DBM as Storage; #[cfg(feature = "kv")] mod kv; diff --git a/teos-ldk-client/src/storage/dbm.rs b/teos-ldk-client/src/storage/sql_storage.rs similarity index 99% rename from teos-ldk-client/src/storage/dbm.rs rename to teos-ldk-client/src/storage/sql_storage.rs index 2caeb439..93d2eace 100644 --- a/teos-ldk-client/src/storage/dbm.rs +++ b/teos-ldk-client/src/storage/sql_storage.rs @@ -1,4 +1,3 @@ -#[cfg(feature = "sqlite")] use std::collections::{HashMap, HashSet}; use std::iter::FromIterator; use std::path::PathBuf; From 962b4f94dca3cdf54aae2ebbe367c1853e7f4a73 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Wed, 12 Feb 2025 15:40:49 +0100 Subject: [PATCH 51/85] storage creator Signed-off-by: dzdidi --- teos-ldk-client/src/retrier.rs | 96 +++++++++++++++++++++++------- teos-ldk-client/src/storage/mod.rs | 33 +++++++++- teos-ldk-client/src/wt_client.rs | 82 ++++++++++++++++++++----- 3 files changed, 171 insertions(+), 40 deletions(-) diff --git a/teos-ldk-client/src/retrier.rs b/teos-ldk-client/src/retrier.rs index 1ee311c7..d77f5bfe 100644 --- a/teos-ldk-client/src/retrier.rs +++ b/teos-ldk-client/src/retrier.rs @@ -574,8 +574,7 @@ impl Retrier { mod tests { use super::*; - use crate::storage::persister::Persister; - use crate::storage::Storage; + use crate::storage::{create_storage, StorageConfig, StorageType}; use bitcoin::secp256k1::{PublicKey, Secp256k1}; use serde_json::json; @@ -633,7 +632,11 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = Box::new(Storage::new(&db_path).unwrap()); + let storage = create_storage(StorageConfig { + storage_type: StorageType::SQL, + db_path: Some(db_path), + }) + .unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -731,7 +734,11 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = Box::new(Storage::new(&db_path).unwrap()); + let storage = create_storage(StorageConfig { + storage_type: StorageType::SQL, + db_path: Some(db_path), + }) + .unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -862,7 +869,11 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = Box::new(Storage::new(&db_path).unwrap()); + let storage = create_storage(StorageConfig { + storage_type: StorageType::SQL, + db_path: Some(db_path), + }) + .unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -970,7 +981,11 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = Box::new(Storage::new(&db_path).unwrap()); + let storage = create_storage(StorageConfig { + storage_type: StorageType::SQL, + db_path: Some(db_path), + }) + .unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -1066,7 +1081,11 @@ mod tests { let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let (tx, rx) = unbounded_channel(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = Box::new(Storage::new(&db_path).unwrap()); + let storage = create_storage(StorageConfig { + storage_type: StorageType::SQL, + db_path: Some(db_path), + }) + .unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -1113,7 +1132,11 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = Box::new(Storage::new(&db_path).unwrap()); + let storage = create_storage(StorageConfig { + storage_type: StorageType::SQL, + db_path: Some(db_path), + }) + .unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -1223,7 +1246,6 @@ mod tests { #[tokio::test] async fn test_manage_retry_while_idle() { - use crate::storage::Storage; // 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 @@ -1240,7 +1262,12 @@ mod tests { let tower_id = TowerId(tower_pk); let db_path = tmp_path.path().join("watchtower.db"); - let mut storage = Box::new(Storage::new(&db_path).unwrap()); + + let mut storage = create_storage(StorageConfig { + storage_type: StorageType::SQL, + db_path: Some(db_path), + }) + .unwrap(); let receipt = get_random_registration_receipt(); storage .store_tower_record(tower_id, "http://unreachable.tower", &receipt) @@ -1252,12 +1279,7 @@ mod tests { .unwrap(); // Now we can create the WTClient and check that the data is pending let wt_client = Arc::new(Mutex::new( - WTClient::new( - Box::new(Storage::new(&db_path).unwrap()), - keypair.0, - tx.clone(), - ) - .await, + WTClient::new(storage, keypair.0, tx.clone()).await, )); // Also create the retrier thread so retries can be managed @@ -1397,7 +1419,11 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = Box::new(Storage::new(&db_path).unwrap()); + let storage = create_storage(StorageConfig { + storage_type: StorageType::SQL, + db_path: Some(db_path), + }) + .unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -1451,7 +1477,11 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = Box::new(Storage::new(&db_path).unwrap()); + let storage = create_storage(StorageConfig { + storage_type: StorageType::SQL, + db_path: Some(db_path), + }) + .unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -1479,7 +1509,11 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = Box::new(Storage::new(&db_path).unwrap()); + let storage = create_storage(StorageConfig { + storage_type: StorageType::SQL, + db_path: Some(db_path), + }) + .unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -1535,7 +1569,11 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = Box::new(Storage::new(&db_path).unwrap()); + let storage = create_storage(StorageConfig { + storage_type: StorageType::SQL, + db_path: Some(db_path), + }) + .unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -1571,7 +1609,11 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = Box::new(Storage::new(&db_path).unwrap()); + let storage = create_storage(StorageConfig { + storage_type: StorageType::SQL, + db_path: Some(db_path), + }) + .unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -1630,7 +1672,11 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = Box::new(Storage::new(&db_path).unwrap()); + let storage = create_storage(StorageConfig { + storage_type: StorageType::SQL, + db_path: Some(db_path), + }) + .unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -1695,7 +1741,11 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = Box::new(Storage::new(&db_path).unwrap()); + let storage = create_storage(StorageConfig { + storage_type: StorageType::SQL, + db_path: Some(db_path), + }) + .unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, diff --git a/teos-ldk-client/src/storage/mod.rs b/teos-ldk-client/src/storage/mod.rs index a160e6c9..3ac3ae8e 100644 --- a/teos-ldk-client/src/storage/mod.rs +++ b/teos-ldk-client/src/storage/mod.rs @@ -1,7 +1,11 @@ pub mod persister; mod sql_storage; -pub use sql_storage::DBM as Storage; +use std::path::PathBuf; + +pub use crate::storage::persister::{Persister, PersisterError}; + +use sql_storage::DBM; #[cfg(feature = "kv")] mod kv; @@ -16,3 +20,30 @@ mod memory_store; #[cfg(test)] #[cfg(feature = "kv")] pub use memory_store::MemoryStore; + +pub fn create_storage( + config: StorageConfig, +) -> Result, PersisterError> { + match config.storage_type { + StorageType::SQL => match DBM::new(&config.db_path.expect("db_path must be specificed")) { + Ok(storage) => Ok(Box::new(storage)), + Err(e) => Err(PersisterError::Other(format!( + "Error creating storage: {}", + e + ))), + }, + _ => { + panic!("Unsupported persistence type"); + } + } +} + +pub struct StorageConfig { + pub storage_type: StorageType, + pub db_path: Option, +} + +pub enum StorageType { + SQL, + KV, +} diff --git a/teos-ldk-client/src/wt_client.rs b/teos-ldk-client/src/wt_client.rs index 3107590c..001cca1c 100644 --- a/teos-ldk-client/src/wt_client.rs +++ b/teos-ldk-client/src/wt_client.rs @@ -12,8 +12,6 @@ use teos_common::{TowerId, UserId}; use crate::retrier::RetrierStatus; use crate::{MisbehaviorProof, SubscriptionError, TowerInfo, TowerStatus, TowerSummary}; -#[cfg(feature = "kv")] -use lightning::util::persist::KVStore; #[derive(Eq, PartialEq)] pub enum RevocationData { @@ -280,7 +278,7 @@ impl WTClient { mod tests { use super::*; - use crate::storage::Storage; + use crate::storage::{create_storage, StorageConfig, StorageType}; use tempdir::TempDir; use teos_common::cryptography; use tokio::sync::mpsc::unbounded_channel; @@ -297,7 +295,11 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = Box::new(Storage::new(&db_path).unwrap()); + let storage = create_storage(StorageConfig { + storage_type: StorageType::SQL, + db_path: Some(db_path), + }) + .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -395,7 +397,11 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = Box::new(Storage::new(&db_path).unwrap()); + let storage = create_storage(StorageConfig { + storage_type: StorageType::SQL, + db_path: Some(db_path), + }) + .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -422,7 +428,11 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = Box::new(Storage::new(&db_path).unwrap()); + let storage = create_storage(StorageConfig { + storage_type: StorageType::SQL, + db_path: Some(db_path), + }) + .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -456,7 +466,11 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = Box::new(Storage::new(&db_path).unwrap()); + let storage = create_storage(StorageConfig { + storage_type: StorageType::SQL, + db_path: Some(db_path), + }) + .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -510,7 +524,11 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = Box::new(Storage::new(&db_path).unwrap()); + let storage = create_storage(StorageConfig { + storage_type: StorageType::SQL, + db_path: Some(db_path), + }) + .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -557,7 +575,11 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = Box::new(Storage::new(&db_path).unwrap()); + let storage = create_storage(StorageConfig { + storage_type: StorageType::SQL, + db_path: Some(db_path), + }) + .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -592,7 +614,11 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = Box::new(Storage::new(&db_path).unwrap()); + let storage = create_storage(StorageConfig { + storage_type: StorageType::SQL, + db_path: Some(db_path), + }) + .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -635,7 +661,11 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = Box::new(Storage::new(&db_path).unwrap()); + let storage = create_storage(StorageConfig { + storage_type: StorageType::SQL, + db_path: Some(db_path), + }) + .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -683,7 +713,11 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = Box::new(Storage::new(&db_path).unwrap()); + let storage = create_storage(StorageConfig { + storage_type: StorageType::SQL, + db_path: Some(db_path), + }) + .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -761,7 +795,11 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = Box::new(Storage::new(&db_path).unwrap()); + let storage = create_storage(StorageConfig { + storage_type: StorageType::SQL, + db_path: Some(db_path), + }) + .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -800,7 +838,11 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = Box::new(Storage::new(&db_path).unwrap()); + let storage = create_storage(StorageConfig { + storage_type: StorageType::SQL, + db_path: Some(db_path), + }) + .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -869,7 +911,11 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = Box::new(Storage::new(&db_path).unwrap()); + let storage = create_storage(StorageConfig { + storage_type: StorageType::SQL, + db_path: Some(db_path), + }) + .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -930,7 +976,11 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = Box::new(Storage::new(&db_path).unwrap()); + let storage = create_storage(StorageConfig { + storage_type: StorageType::SQL, + db_path: Some(db_path), + }) + .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; From b0606e2370840afdafacde96d62eed666c839c65 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Wed, 12 Feb 2025 17:45:15 +0100 Subject: [PATCH 52/85] kv draft Signed-off-by: dzdidi --- teos-ldk-client/src/retrier.rs | 87 +- teos-ldk-client/src/storage/kv.rs | 1571 ++++++++++--------- teos-ldk-client/src/storage/memory_store.rs | 2 + teos-ldk-client/src/storage/mod.rs | 40 +- teos-ldk-client/src/storage/persister.rs | 3 + teos-ldk-client/src/wt_client.rs | 80 +- 6 files changed, 847 insertions(+), 936 deletions(-) diff --git a/teos-ldk-client/src/retrier.rs b/teos-ldk-client/src/retrier.rs index d77f5bfe..c4791deb 100644 --- a/teos-ldk-client/src/retrier.rs +++ b/teos-ldk-client/src/retrier.rs @@ -574,7 +574,7 @@ impl Retrier { mod tests { use super::*; - use crate::storage::{create_storage, StorageConfig, StorageType}; + use crate::storage::{create_storage, StorageConfig}; use bitcoin::secp256k1::{PublicKey, Secp256k1}; use serde_json::json; @@ -632,11 +632,7 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig { - storage_type: StorageType::SQL, - db_path: Some(db_path), - }) - .unwrap(); + let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -734,11 +730,7 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig { - storage_type: StorageType::SQL, - db_path: Some(db_path), - }) - .unwrap(); + let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -869,11 +861,7 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig { - storage_type: StorageType::SQL, - db_path: Some(db_path), - }) - .unwrap(); + let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -981,11 +969,7 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig { - storage_type: StorageType::SQL, - db_path: Some(db_path), - }) - .unwrap(); + let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -1081,11 +1065,7 @@ mod tests { let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let (tx, rx) = unbounded_channel(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig { - storage_type: StorageType::SQL, - db_path: Some(db_path), - }) - .unwrap(); + let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -1132,11 +1112,7 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig { - storage_type: StorageType::SQL, - db_path: Some(db_path), - }) - .unwrap(); + let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -1262,12 +1238,7 @@ mod tests { let tower_id = TowerId(tower_pk); let db_path = tmp_path.path().join("watchtower.db"); - - let mut storage = create_storage(StorageConfig { - storage_type: StorageType::SQL, - db_path: Some(db_path), - }) - .unwrap(); + let mut storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); let receipt = get_random_registration_receipt(); storage .store_tower_record(tower_id, "http://unreachable.tower", &receipt) @@ -1419,11 +1390,7 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig { - storage_type: StorageType::SQL, - db_path: Some(db_path), - }) - .unwrap(); + let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -1477,11 +1444,7 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig { - storage_type: StorageType::SQL, - db_path: Some(db_path), - }) - .unwrap(); + let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -1509,11 +1472,7 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig { - storage_type: StorageType::SQL, - db_path: Some(db_path), - }) - .unwrap(); + let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -1569,11 +1528,7 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig { - storage_type: StorageType::SQL, - db_path: Some(db_path), - }) - .unwrap(); + let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -1609,11 +1564,7 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig { - storage_type: StorageType::SQL, - db_path: Some(db_path), - }) - .unwrap(); + let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -1672,11 +1623,7 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig { - storage_type: StorageType::SQL, - db_path: Some(db_path), - }) - .unwrap(); + let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -1741,11 +1688,7 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig { - storage_type: StorageType::SQL, - db_path: Some(db_path), - }) - .unwrap(); + let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, diff --git a/teos-ldk-client/src/storage/kv.rs b/teos-ldk-client/src/storage/kv.rs index 2e74605b..ea13b1f2 100644 --- a/teos-ldk-client/src/storage/kv.rs +++ b/teos-ldk-client/src/storage/kv.rs @@ -1,5 +1,7 @@ use std::collections::{HashMap, HashSet}; +use std::sync::Arc; +use crate::storage::persister::{Persister, PersisterError}; // use bitcoin::secp256k1::SecretKey; pub use lightning::io::Error as DBError; use lightning::util::persist::KVStore; @@ -9,17 +11,21 @@ use teos_common::{TowerId, UserId}; // use teos_common::cryptography; // use chacha20poly1305::aead::{Aead, NewAead}; -use chacha20poly1305::{ChaCha20Poly1305, Key, KeyInit, Nonce}; // use rand::distributions::Uniform; // use rand::Rng; // use bitcoin::consensus; -use bitcoin::hashes::{sha256, Hash}; // use bitcoin::secp256k1::{Error, PublicKey, Secp256k1, SecretKey}; // use bitcoin::{Transaction, Txid}; // use lightning::util::message_signing; -use crate::{AppointmentStatus, MisbehaviorProof, TowerInfo, TowerStatus, TowerSummary}; +impl From for PersisterError { + fn from(error: DBError) -> Self { + PersisterError::Other(error.to_string()) + } +} + +use crate::{AppointmentStatus, MisbehaviorProof, TowerInfo, TowerSummary}; // Primary namespace for all watchtower-related data const PRIMARY_NAMESPACE: &str = "watchtower"; @@ -33,8 +39,10 @@ const NS_PENDING_APPOINTMENTS: &str = "pending_appointments"; const NS_INVALID_APPOINTMENTS: &str = "invalid_appointments"; const NS_MISBEHAVIOR_PROOFS: &str = "misbehavior_proofs"; -pub struct Storage { - store: Box, +pub type DynStore = dyn KVStore + Sync + Send; + +pub struct KVStorage { + store: Arc, sk: Vec, } @@ -54,42 +62,42 @@ fn get_appointment_namespace(status: AppointmentStatus) -> &'static str { // Implement methods to convert TowerInfo to vec and vice versa -impl Storage { - pub fn new(store: impl T, sk: Vec) -> Result { - Ok(Storage { - store: Box::new(store), - sk, - }) +impl KVStorage { + pub fn new(store: Arc, sk: Vec) -> Result { + Ok(KVStorage { store, sk }) } +} +impl Persister for KVStorage { /// Stores a tower record into the database alongside the corresponding registration receipt. /// /// This function MUST be guarded against inserting duplicate (tower_id, subscription_expiry) pairs. /// This is currently done in WTClient::add_update_tower. - pub fn store_tower_record( + fn store_tower_record( &mut self, tower_id: TowerId, net_addr: &str, receipt: &RegistrationReceipt, - ) -> Result<(), DBError> { - let key = make_key(&[&tower_id.to_string()]); - - let value = TowerInfo::new( - net_addr.to_string(), - receipt.available_slots(), - receipt.subscription_start(), - receipt.subscription_expiry(), - HashMap::new(), - Vec::new(), - Vec::new(), - ) - .to_vec() - .unwrap(); - - let encrypted = encrypt(&value, &self.sk).unwrap(); - - self.store - .write(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key, &encrypted) + ) -> Result<(), PersisterError> { + todo!(); + // let key = make_key(&[&tower_id.to_string()]); + // + // let value = TowerInfo::new( + // net_addr.to_string(), + // receipt.available_slots(), + // receipt.subscription_start(), + // receipt.subscription_expiry(), + // HashMap::new(), + // Vec::new(), + // Vec::new(), + // ) + // .to_vec() + // .unwrap(); + // + // let encrypted = encrypt(&value, &self.sk).unwrap(); + // + // self.store + // .write(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key, &encrypted) } /// Loads a tower record from the database. @@ -97,48 +105,50 @@ impl Storage { /// 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) -> Option { - let key = make_key(&[&tower_id.to_string()]); - let value = match self.store.read(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key) { - Ok(v) => v, - Err(_) => return None, - }; - - let decrypted = decrypt(&value, &self.sk).unwrap(); - - let mut tower_info = TowerInfo::from_slice(&decrypted).unwrap(); - // tower_info.appointments = self.load_appointment_receipts(tower_id); - // tower_info.pending_appointments = - // self.load_appointments(tower_id, AppointmentStatus::Pending); - // tower_info.invalid_appointments = - // self.load_appointments(tower_id, AppointmentStatus::Invalid); - - Some(tower_info) + fn load_tower_record(&self, tower_id: TowerId) -> Option { + todo!(); + // let key = make_key(&[&tower_id.to_string()]); + // let value = match self.store.read(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key) { + // Ok(v) => v, + // Err(_) => return None, + // }; + // + // let decrypted = decrypt(&value, &self.sk).unwrap(); + // + // let mut tower_info = TowerInfo::from_slice(&decrypted).unwrap(); + // // tower_info.appointments = self.load_appointment_receipts(tower_id); + // // tower_info.pending_appointments = + // // self.load_appointments(tower_id, AppointmentStatus::Pending); + // // tower_info.invalid_appointments = + // // self.load_appointments(tower_id, AppointmentStatus::Invalid); + // + // Some(tower_info) } /// 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<(), DBError> { - let key = make_key(&[&tower_id.to_string()]); - // primary namespace: "watchtower" - // secondary namespance: "tower_record" - // key: - // value: ? - self.store - .remove(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key, true) + fn remove_tower_record(&self, tower_id: TowerId) -> Result<(), PersisterError> { + todo!(); + // let key = make_key(&[&tower_id.to_string()]); + // // primary namespace: "watchtower" + // // secondary namespance: "tower_record" + // // key: + // // value: ? + // self.store + // .remove(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key, true) } /// Loads all tower records from the database. - pub fn load_towers(&self) -> HashMap { + fn load_towers(&self) -> HashMap { todo!() } /// Loads the latest registration receipt for a given tower. /// /// Latests is determined by the one with the `subscription_expiry` further into the future. - pub fn load_registration_receipt( + fn load_registration_receipt( &self, tower_id: TowerId, user_id: UserId, @@ -151,13 +161,13 @@ impl Storage { } /// Stores an appointments receipt into the database representing an appointment accepted by a given tower. - pub fn store_appointment_receipt( + fn store_appointment_receipt( &mut self, tower_id: TowerId, locator: Locator, available_slots: u32, receipt: &AppointmentReceipt, - ) -> Result<(), DBError> { + ) -> Result<(), PersisterError> { let key = make_key(&[&tower_id.to_string(), &locator.to_string()]); // primary namespace: "watchtower" // secondary namespance: "appointment_receipt" @@ -168,7 +178,7 @@ impl Storage { } /// Loads a given appointment receipt of a given tower from the database. - pub fn load_appointment_receipt( + fn load_appointment_receipt( &self, tower_id: TowerId, locator: Locator, @@ -185,7 +195,7 @@ impl Storage { /// /// 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. - pub fn load_appointment_receipts(&self, tower_id: TowerId) -> HashMap { + fn load_appointment_receipts(&self, tower_id: TowerId) -> HashMap { // primary namespace: "watchtower" // secondary namespance: "appointment_receipt" // filter by tower_id @@ -196,7 +206,7 @@ impl Storage { /// /// The loaded locators can be loaded either from appointment_receipts, pending_appointments or invalid_appointments /// depending on `status`. - pub fn load_appointment_locators( + fn load_appointment_locators( &self, tower_id: TowerId, status: AppointmentStatus, @@ -207,37 +217,37 @@ impl Storage { } /// Loads an appointment from the database. - pub fn load_appointment(&self, locator: Locator) -> Option { + fn load_appointment(&self, locator: Locator) -> Option { // primary namespace: "watchtower" // secondary namespance: "appointment_receipt" // key: todo!(); } - /// Stores an appointment into the database. - /// - /// Appointments are only stored as a whole when they are pending or invalid. - /// Accepted appointments are simplified in the form of an appointment receipt. - fn store_appointment(&self, appointment: &Appointment) -> Result { - let key = make_key(&[&appointment.locator.to_string()]); - // primary namespace: "watchtower" - // secondary namespance: "appointment" - // key: - // value: appointment.encrypted_blob() - todo!(); - } + // /// Stores an appointment into the database. + // /// + // /// Appointments are only stored as a whole when they are pending or invalid. + // /// Accepted appointments are simplified in the form of an appointment receipt. + // fn store_appointment(&self, appointment: &Appointment) -> Result { + // let key = make_key(&[&appointment.locator.to_string()]); + // // primary namespace: "watchtower" + // // secondary namespance: "appointment" + // // key: + // // value: appointment.encrypted_blob() + // todo!(); + // } - /// Stores a pending appointment into the database. - /// - /// A pending appointment is an appointment that was sent to a tower when it was unreachable. - /// This data is stored so it can be resent once the tower comes back online. - /// Internally calls [Self::store_appointment]. - pub fn store_pending_appointment( - &self, + // /// Stores a pending appointment into the database. + // /// + // /// A pending appointment is an appointment that was sent to a tower when it was unreachable. + // /// This data is stored so it can be resent once the tower comes back online. + // /// Internally calls [Self::store_appointment]. + fn store_pending_appointment( + &mut self, tower_id: TowerId, appointment: &Appointment, - ) -> Result<(), DBError> { - let key = make_key(&[&tower_id.to_string(), &appointment.locator.to_string()]); + ) -> Result<(), PersisterError> { + // let key = make_key(&[&tower_id.to_string(), &appointment.locator.to_string()]); // primary namespace: "watchtower" // secondary namespance: "pending_appointment" // key: @@ -248,12 +258,12 @@ impl Storage { /// Removes a pending appointment from the database. /// /// If the pending appointment is the only instance of the appointment, the appointment will also be deleted form the appointments table. - pub fn delete_pending_appointment( - &self, + fn delete_pending_appointment( + &mut self, tower_id: TowerId, locator: Locator, - ) -> Result<(), DBError> { - let key = make_key(&[&tower_id.to_string(), &locator.to_string()]); + ) -> Result<(), PersisterError> { + // let key = make_key(&[&tower_id.to_string(), &locator.to_string()]); // primary namespace: "watchtower" // secondary namespance: "pending_appointment" // key: ? @@ -266,12 +276,12 @@ impl Storage { /// An invalid appointment is an appointment that was rejected by the tower. /// Storing this data may allow us to see what was the issue and send the data later on. /// Internally calls [Self::store_appointment]. - pub fn store_invalid_appointment( + fn store_invalid_appointment( &mut self, tower_id: TowerId, appointment: &Appointment, - ) -> Result<(), DBError> { - let key = make_key(&[&tower_id.to_string(), &appointment.locator.to_string()]); + ) -> Result<(), PersisterError> { + // let key = make_key(&[&tower_id.to_string(), &appointment.locator.to_string()]); // primary namespace: "watchtower" // secondary namespance: "invalid_appointment" // key: ? @@ -283,12 +293,8 @@ impl Storage { /// /// This is meant to be used only for pending and invalid appointments, if the method is called for /// accepted appointment, an empty collection will be returned. - pub fn load_appointments( - &self, - tower_id: TowerId, - status: AppointmentStatus, - ) -> Vec { - let key = make_key(&[&tower_id.to_string()]); + fn load_appointments(&self, tower_id: TowerId, status: AppointmentStatus) -> Vec { + // let key = make_key(&[&tower_id.to_string()]); // primary namespace: "watchtower" // secondary namespance: "{status}_appointment_receipt" // key: ? @@ -300,12 +306,12 @@ impl Storage { /// /// A misbehaving proof is proof that the tower has signed an appointment using a key different /// than the one advertised to the user when they registered. - pub fn store_misbehaving_proof( - &self, + fn store_misbehaving_proof( + &mut self, tower_id: TowerId, proof: &MisbehaviorProof, - ) -> Result<(), DBError> { - let key = make_key(&[&tower_id.to_string()]); + ) -> Result<(), PersisterError> { + // let key = make_key(&[&tower_id.to_string()]); // primary namespace: "watchtower" // secondary namespance: "misbehaving_proof" // key: ? @@ -313,21 +319,28 @@ impl Storage { todo!(); } - /// Loads the misbehaving proof for a given tower from the database (if found). - fn load_misbehaving_proof(&self, tower_id: TowerId) -> Option { - let key = make_key(&[&tower_id.to_string()]); - // primary namespace: "watchtower" - // secondary namespance: "misbehaving_proof" - // key: ? + // /// Loads the misbehaving proof for a given tower from the database (if found). + // fn load_misbehaving_proof(&self, tower_id: TowerId) -> Option { + // let key = make_key(&[&tower_id.to_string()]); + // // primary namespace: "watchtower" + // // secondary namespance: "misbehaving_proof" + // // key: ? + // todo!(); + // } + + // /// Checks whether a misbehaving proof exists for a given tower. + // fn exists_misbehaving_proof(&self, tower_id: TowerId) -> bool { + // let key = make_key(&[&tower_id.to_string()]); + // // primary namespace: "watchtower" + // // secondary namespance: "misbehaving_proof" + // // key: ? + // todo!(); + // } + fn appointment_exists(&self, locator: Locator) -> bool { todo!(); } - /// Checks whether a misbehaving proof exists for a given tower. - fn exists_misbehaving_proof(&self, tower_id: TowerId) -> bool { - let key = make_key(&[&tower_id.to_string()]); - // primary namespace: "watchtower" - // secondary namespance: "misbehaving_proof" - // key: ? + fn appointment_receipt_exists(&self, locator: Locator, tower_id: TowerId) -> bool { todo!(); } } @@ -340,14 +353,15 @@ impl Storage { /// /// The message to be encrypted is expected to be the penalty transaction. fn encrypt(message: &Vec, secret: &Vec) -> Result, chacha20poly1305::aead::Error> { - // Defaults is [0; 12] - let nonce = Nonce::default(); - let k = sha256::Hash::hash(secret); - let key = Key::from_slice(k.as_byte_array()); - - let cypher = ChaCha20Poly1305::new(key); - - cypher.encrypt(&nonce, message.to_vec().as_ref()) + todo!(); + // // Defaults is [0; 12] + // let nonce = Nonce::default(); + // let k = sha256::Hash::hash(secret); + // let key = Key::from_slice(k.as_byte_array()); + // + // let cypher = ChaCha20Poly1305::new(key); + // + // cypher.encrypt(&nonce, message.to_vec().as_ref()) } /// Decrypts an encrypted blob of data using `chacha20poly1305` and a given secret. @@ -361,662 +375,663 @@ fn decrypt( encrypted_blob: &[u8], secret: &Vec, ) -> Result, chacha20poly1305::aead::Error> { - // Defaults is [0; 12] - let nonce = Nonce::default(); - let k = sha256::Hash::hash(secret); - let key = Key::from_slice(k.as_byte_array()); - - let cypher = ChaCha20Poly1305::new(key); - - cypher.decrypt(&nonce, encrypted_blob.as_ref()) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::storage::MemoryStore; - - use teos_common::test_utils::{ - generate_random_appointment, get_random_registration_receipt, get_random_user_id, - get_registration_receipt_from_previous, - }; - - fn create_test_storage() -> Storage { - let store = MemoryStore::new(); - let sk = vec![0u8; 32]; // Test secret key - Storage::new(store, sk).unwrap() - } - - #[test] - fn test_store_load_tower_record() { - let mut storage = create_test_storage(); - - // In order to add a tower record we need to associated registration receipt. - let tower_id = get_random_user_id(); - let net_addr = "talaia.watch"; - - let receipt = get_random_registration_receipt(); - let tower_info = TowerInfo::new( - net_addr.to_owned(), - receipt.available_slots(), - receipt.subscription_start(), - receipt.subscription_expiry(), - HashMap::new(), - Vec::new(), - Vec::new(), - ); - - // Check the loaded data matches the in memory data - storage - .store_tower_record(tower_id, net_addr, &receipt) - .unwrap(); - assert_eq!(storage.load_tower_record(tower_id).unwrap(), tower_info); - } - - // #[test] - // fn test_load_registration_receipt() { - // let mut dbm = DBM::in_memory().unwrap(); - // - // // Registration receipts are stored alongside tower records when the register command is called - // let tower_id = get_random_user_id(); - // let net_addr = "talaia.watch"; - // let receipt = get_random_registration_receipt(); - // - // // Check the receipt was stored - // dbm.store_tower_record(tower_id, net_addr, &receipt) - // .unwrap(); - // assert_eq!( - // dbm.load_registration_receipt(tower_id, receipt.user_id()) - // .unwrap(), - // receipt - // ); - // - // // Add another receipt for the same tower with a higher expiry and check this last one is loaded - // let middle_receipt = get_registration_receipt_from_previous(&receipt); - // let latest_receipt = get_registration_receipt_from_previous(&middle_receipt); - // - // dbm.store_tower_record(tower_id, net_addr, &latest_receipt) - // .unwrap(); - // assert_eq!( - // dbm.load_registration_receipt(tower_id, latest_receipt.user_id()) - // .unwrap(), - // latest_receipt - // ); - // - // // Add a final one with a lower expiry and check the last is still loaded - // dbm.store_tower_record(tower_id, net_addr, &middle_receipt) - // .unwrap(); - // assert_eq!( - // dbm.load_registration_receipt(tower_id, latest_receipt.user_id()) - // .unwrap(), - // latest_receipt - // ); - // } - // - // #[test] - // fn test_load_same_registration_receipt() { - // let mut dbm = DBM::in_memory().unwrap(); - // - // // Registration receipts are stored alongside tower records when the register command is called - // let tower_id = get_random_user_id(); - // let net_addr = "talaia.watch"; - // let receipt = get_random_registration_receipt(); - // - // // Store it once - // dbm.store_tower_record(tower_id, net_addr, &receipt) - // .unwrap(); - // assert_eq!( - // dbm.load_registration_receipt(tower_id, receipt.user_id()) - // .unwrap(), - // receipt - // ); - // - // // Store the same again, this should fail due to UNIQUE PK constrains. - // // Notice store_tower_record is guarded against this by WTClient::add_update_tower though. - // assert!(matches!( - // dbm.store_tower_record(tower_id, net_addr, &receipt), - // Err { .. } - // )); - // } - // - // #[test] - // fn test_load_nonexistent_tower_record() { - // let dbm = DBM::in_memory().unwrap(); - // - // // If the tower does not exists, `load_tower` will fail. - // let tower_id = get_random_user_id(); - // assert!(dbm.load_tower_record(tower_id).is_none()); - // } - // - // #[test] - // fn test_store_load_towers() { - // let mut dbm = DBM::in_memory().unwrap(); - // let mut towers = HashMap::new(); - // - // // In order to add a tower record we need to associated registration receipt. - // for _ in 0..10 { - // let tower_id = get_random_user_id(); - // let net_addr = "talaia.watch"; - // let mut receipt = get_random_registration_receipt(); - // dbm.store_tower_record(tower_id, net_addr, &receipt) - // .unwrap(); - // - // // Add not only one registration receipt to test if the tower retrieves the one with furthest expiry date. - // for _ in 0..10 { - // receipt = get_registration_receipt_from_previous(&receipt); - // dbm.store_tower_record(tower_id, net_addr, &receipt) - // .unwrap(); - // } - // - // towers.insert( - // tower_id, - // TowerSummary::new( - // net_addr.to_owned(), - // receipt.available_slots(), - // receipt.subscription_start(), - // receipt.subscription_expiry(), - // ), - // ); - // } - // - // assert_eq!(dbm.load_towers(), towers); - // } - // - // #[test] - // fn test_load_towers_empty() { - // // If there are no towers in the database, `load_towers` should return an empty map. - // let dbm = DBM::in_memory().unwrap(); - // assert_eq!(dbm.load_towers(), HashMap::new()); - // } + todo!(); + // // Defaults is [0; 12] + // let nonce = Nonce::default(); + // let k = sha256::Hash::hash(secret); + // let key = Key::from_slice(k.as_byte_array()); // - // #[test] - // fn test_remove_tower_record() { - // let mut dbm = DBM::in_memory().unwrap(); + // let cypher = ChaCha20Poly1305::new(key); // - // 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(); - // - // // In order to add a tower record we need to associated registration receipt. - // let tower_id = get_random_user_id(); - // let net_addr = "talaia.watch"; - // - // let receipt = get_random_registration_receipt(); - // let mut tower_summary = TowerSummary::new( - // net_addr.to_owned(), - // receipt.available_slots(), - // receipt.subscription_start(), - // receipt.subscription_expiry(), - // ); - // dbm.store_tower_record(tower_id, net_addr, &receipt) - // .unwrap(); - // - // // Add some appointment receipts and check they match - // let mut receipts = HashMap::new(); - // for _ in 0..5 { - // let appointment = generate_random_appointment(None); - // let user_signature = "user_signature"; - // let appointment_receipt = AppointmentReceipt::with_signature( - // user_signature.to_owned(), - // 42, - // "tower_signature".to_owned(), - // ); - // - // tower_summary.available_slots -= 1; - // - // dbm.store_appointment_receipt( - // tower_id, - // appointment.locator, - // tower_summary.available_slots, - // &appointment_receipt, - // ) - // .unwrap(); - // receipts.insert( - // appointment.locator, - // appointment_receipt.signature().unwrap(), - // ); - // } - // - // 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!(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"; - // let receipt = get_random_registration_receipt(); - // dbm.store_tower_record(tower_id, net_addr, &receipt) - // .unwrap(); - // - // assert!(dbm - // .load_appointment_receipt(tower_id, appointment.locator) - // .is_none()); - // - // // Add both - // let tower_summary = TowerSummary::new( - // net_addr.to_owned(), - // receipt.available_slots(), - // receipt.subscription_start(), - // receipt.subscription_expiry(), - // ); - // let appointment_receipt = AppointmentReceipt::with_signature( - // "user_signature".to_owned(), - // 42, - // "tower_signature".to_owned(), - // ); - // 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` - // let mut dbm = DBM::in_memory().unwrap(); - // - // // We first need to add a tower record to the database so we can add some associated data. - // let tower_id = get_random_user_id(); - // let net_addr = "talaia.watch"; - // - // let receipt = get_random_registration_receipt(); - // let tower_summary = TowerSummary::new( - // net_addr.to_owned(), - // receipt.available_slots(), - // receipt.subscription_start(), - // receipt.subscription_expiry(), - // ); - // dbm.store_tower_record(tower_id, net_addr, &receipt) - // .unwrap(); - // - // // Create all types of appointments and store them in the db. - // let user_signature = "user_signature"; - // let mut receipts = HashSet::new(); - // let mut pending_appointments = HashSet::new(); - // let mut invalid_appointments = HashSet::new(); - // for _ in 0..5 { - // let appointment = generate_random_appointment(None); - // let appointment_receipt = AppointmentReceipt::with_signature( - // user_signature.to_owned(), - // 42, - // "tower_signature".to_owned(), - // ); - // let pending_appointment = generate_random_appointment(None); - // let invalid_appointment = generate_random_appointment(None); - // - // dbm.store_appointment_receipt( - // tower_id, - // appointment.locator, - // tower_summary.available_slots, - // &appointment_receipt, - // ) - // .unwrap(); - // dbm.store_pending_appointment(tower_id, &pending_appointment) - // .unwrap(); - // dbm.store_invalid_appointment(tower_id, &invalid_appointment) - // .unwrap(); - // - // receipts.insert(appointment.locator); - // pending_appointments.insert(pending_appointment.locator); - // invalid_appointments.insert(invalid_appointment.locator); - // } - // - // // Pull data from the db and check it matches the expected data - // assert_eq!( - // dbm.load_appointment_locators(tower_id, AppointmentStatus::Accepted), - // receipts - // ); - // assert_eq!( - // dbm.load_appointment_locators(tower_id, AppointmentStatus::Pending), - // pending_appointments - // ); - // assert_eq!( - // dbm.load_appointment_locators(tower_id, AppointmentStatus::Invalid), - // invalid_appointments - // ); - // } - // - // #[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!(loaded_appointment.is_none()); - // } - // - // #[test] - // fn test_store_pending_appointment() { - // let mut dbm = DBM::in_memory().unwrap(); - // - // // In order to add a tower record we need to associated registration receipt. - // let tower_id = get_random_user_id(); - // let net_addr = "talaia.watch"; - // - // let receipt = get_random_registration_receipt(); - // let mut tower_summary = TowerSummary::new( - // net_addr.to_owned(), - // receipt.available_slots(), - // receipt.subscription_start(), - // receipt.subscription_expiry(), - // ) - // .with_status(TowerStatus::TemporaryUnreachable); - // dbm.store_tower_record(tower_id, net_addr, &receipt) - // .unwrap(); - // - // // Add some pending appointments and check they match - // for _ in 0..5 { - // let appointment = generate_random_appointment(None); - // - // tower_summary - // .pending_appointments - // .insert(appointment.locator); - // - // dbm.store_pending_appointment(tower_id, &appointment) - // .unwrap(); - // assert_eq!( - // TowerSummary::from(dbm.load_tower_record(tower_id).unwrap()), - // tower_summary - // ); - // } - // } - // - // #[test] - // fn test_store_pending_appointment_twice() { - // let mut dbm = DBM::in_memory().unwrap(); - // - // // In order to add a tower record we need to associated registration receipt. - // let tower_id_1 = get_random_user_id(); - // let tower_id_2 = get_random_user_id(); - // let net_addr = "talaia.watch"; - // - // let receipt = get_random_registration_receipt(); - // dbm.store_tower_record(tower_id_1, net_addr, &receipt) - // .unwrap(); - // dbm.store_tower_record(tower_id_2, net_addr, &receipt) - // .unwrap(); - // - // // If the same appointment is stored twice (by different towers) it should go through - // // Since the appointment data will be stored only once and this will create two references - // let appointment = generate_random_appointment(None); - // dbm.store_pending_appointment(tower_id_1, &appointment) - // .unwrap(); - // dbm.store_pending_appointment(tower_id_2, &appointment) - // .unwrap(); - // - // // If this is called twice with for the same tower it will fail, since two identical references - // // can not exist. This is intended behavior and should not happen - // assert!(dbm - // .store_pending_appointment(tower_id_2, &appointment) - // .is_err()); - // } - // - // #[test] - // fn test_delete_pending_appointment() { - // let mut dbm = DBM::in_memory().unwrap(); - // - // // In order to add a tower record we need to associated registration receipt. - // 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(); - // - // // Add a single one, remove it later - // let appointment = generate_random_appointment(None); - // dbm.store_pending_appointment(tower_id, &appointment) - // .unwrap(); - // assert!(dbm - // .delete_pending_appointment(tower_id, appointment.locator) - // .is_ok()); - // - // // The appointment should be completely gone - // assert!(!dbm - // .load_appointment_locators(tower_id, AppointmentStatus::Pending) - // .contains(&appointment.locator)); - // // assert!(!dbm.appointment_exists(appointment.locator)); - // - // // Try again with more than one reference - // let another_tower_id = get_random_user_id(); - // dbm.store_tower_record(another_tower_id, net_addr, &receipt) - // .unwrap(); - // - // // Add two - // dbm.store_pending_appointment(tower_id, &appointment) - // .unwrap(); - // dbm.store_pending_appointment(another_tower_id, &appointment) - // .unwrap(); - // // Delete one - // assert!(dbm - // .delete_pending_appointment(tower_id, appointment.locator) - // .is_ok()); - // // Check - // assert!(!dbm - // .load_appointment_locators(tower_id, AppointmentStatus::Pending) - // .contains(&appointment.locator)); - // assert!(dbm - // .load_appointment_locators(another_tower_id, AppointmentStatus::Pending) - // .contains(&appointment.locator)); - // // assert!(dbm.appointment_exists(appointment.locator)); - // - // // Add an invalid reference and check again - // dbm.store_invalid_appointment(tower_id, &appointment) - // .unwrap(); - // assert!(dbm - // .delete_pending_appointment(another_tower_id, appointment.locator) - // .is_ok()); - // assert!(!dbm - // .load_appointment_locators(another_tower_id, AppointmentStatus::Pending) - // .contains(&appointment.locator)); - // assert!(dbm - // .load_appointment_locators(tower_id, AppointmentStatus::Invalid) - // .contains(&appointment.locator)); - // // assert!(dbm.appointment_exists(appointment.locator)); - // } - // - // #[test] - // fn test_store_invalid_appointment() { - // let mut dbm = DBM::in_memory().unwrap(); - // - // // In order to add a tower record we need to associated registration receipt. - // let tower_id = get_random_user_id(); - // let net_addr = "talaia.watch"; - // - // let receipt = get_random_registration_receipt(); - // let mut tower_summary = TowerSummary::new( - // net_addr.to_owned(), - // receipt.available_slots(), - // receipt.subscription_start(), - // receipt.subscription_expiry(), - // ); - // dbm.store_tower_record(tower_id, net_addr, &receipt) - // .unwrap(); - // - // // Add some invalid appointments and check they match - // for _ in 0..5 { - // let appointment = generate_random_appointment(None); - // - // tower_summary - // .invalid_appointments - // .insert(appointment.locator); - // - // dbm.store_invalid_appointment(tower_id, &appointment) - // .unwrap(); - // assert_eq!( - // TowerSummary::from(dbm.load_tower_record(tower_id).unwrap()), - // tower_summary - // ); - // } - // } - // - // #[test] - // fn test_store_invalid_appointment_twice() { - // let mut dbm = DBM::in_memory().unwrap(); - // - // // In order to add a tower record we need to associated registration receipt. - // let tower_id_1 = get_random_user_id(); - // let tower_id_2 = get_random_user_id(); - // let net_addr = "talaia.watch"; - // - // let receipt = get_random_registration_receipt(); - // dbm.store_tower_record(tower_id_1, net_addr, &receipt) - // .unwrap(); - // dbm.store_tower_record(tower_id_2, net_addr, &receipt) - // .unwrap(); - // - // // Same as with pending appointments. Two references from different towers is allowed - // let appointment = generate_random_appointment(None); - // dbm.store_invalid_appointment(tower_id_1, &appointment) - // .unwrap(); - // dbm.store_invalid_appointment(tower_id_2, &appointment) - // .unwrap(); - // - // // Two references from the same tower is not. - // assert!(dbm - // .store_invalid_appointment(tower_id_2, &appointment) - // .is_err()); - // } - // - // #[test] - // fn test_store_load_misbehaving_proof() { - // let mut dbm = DBM::in_memory().unwrap(); - // - // // In order to add a tower record we need to associated registration receipt. - // let tower_id = get_random_user_id(); - // let net_addr = "talaia.watch"; - // - // let receipt = get_random_registration_receipt(); - // let tower_summary = TowerSummary::new( - // net_addr.to_owned(), - // receipt.available_slots(), - // receipt.subscription_start(), - // receipt.subscription_expiry(), - // ); - // dbm.store_tower_record(tower_id, net_addr, &receipt) - // .unwrap(); - // assert_eq!( - // TowerSummary::from(dbm.load_tower_record(tower_id).unwrap()), - // tower_summary - // ); - // - // // Store a misbehaving proof and load it back - // let appointment = generate_random_appointment(None); - // let appointment_receipt = AppointmentReceipt::with_signature( - // "user_signature".to_owned(), - // 42, - // "tower_signature".to_owned(), - // ); - // - // let proof = MisbehaviorProof::new( - // appointment.locator, - // appointment_receipt, - // get_random_user_id(), - // ); - // - // dbm.store_misbehaving_proof(tower_id, &proof).unwrap(); - // assert_eq!(dbm.load_misbehaving_proof(tower_id).unwrap(), proof); - // } - // - // #[test] - // fn test_store_load_non_existing_misbehaving_proof() { - // let dbm = DBM::in_memory().unwrap(); - // assert!(dbm.load_misbehaving_proof(get_random_user_id()).is_none()); - // } - // - // #[test] - // fn test_store_exists_misbehaving_proof() { - // let mut dbm = DBM::in_memory().unwrap(); - // - // // In order to add a tower record we need to associated registration receipt. - // let tower_id = get_random_user_id(); - // let net_addr = "talaia.watch"; - // - // let receipt = get_random_registration_receipt(); - // let tower_summary = TowerSummary::new( - // net_addr.to_owned(), - // receipt.available_slots(), - // receipt.subscription_start(), - // receipt.subscription_expiry(), - // ); - // dbm.store_tower_record(tower_id, net_addr, &receipt) - // .unwrap(); - // assert_eq!( - // TowerSummary::from(dbm.load_tower_record(tower_id).unwrap()), - // tower_summary - // ); - // - // // // Store a misbehaving proof check - // let appointment = generate_random_appointment(None); - // let appointment_receipt = AppointmentReceipt::with_signature( - // "user_signature".to_owned(), - // 42, - // "tower_signature".to_owned(), - // ); - // - // let proof = MisbehaviorProof::new( - // appointment.locator, - // appointment_receipt, - // get_random_user_id(), - // ); - // - // dbm.store_misbehaving_proof(tower_id, &proof).unwrap(); - // assert!(dbm.exists_misbehaving_proof(tower_id)); - // } - // - // #[test] - // fn test_exists_misbehaving_proof_false() { - // let dbm = DBM::in_memory().unwrap(); - // assert!(!dbm.exists_misbehaving_proof(get_random_user_id())); - // } + // cypher.decrypt(&nonce, encrypted_blob.as_ref()) } + +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::storage::MemoryStore; +// +// use teos_common::test_utils::{ +// generate_random_appointment, get_random_registration_receipt, get_random_user_id, +// get_registration_receipt_from_previous, +// }; +// +// fn create_test_storage() -> Storage { +// let store = MemoryStore::new(); +// let sk = vec![0u8; 32]; // Test secret key +// Storage::new(store, sk).unwrap() +// } +// +// #[test] +// fn test_store_load_tower_record() { +// let mut storage = create_test_storage(); +// +// // In order to add a tower record we need to associated registration receipt. +// let tower_id = get_random_user_id(); +// let net_addr = "talaia.watch"; +// +// let receipt = get_random_registration_receipt(); +// let tower_info = TowerInfo::new( +// net_addr.to_owned(), +// receipt.available_slots(), +// receipt.subscription_start(), +// receipt.subscription_expiry(), +// HashMap::new(), +// Vec::new(), +// Vec::new(), +// ); +// +// // Check the loaded data matches the in memory data +// storage +// .store_tower_record(tower_id, net_addr, &receipt) +// .unwrap(); +// assert_eq!(storage.load_tower_record(tower_id).unwrap(), tower_info); +// } +// +// // #[test] +// // fn test_load_registration_receipt() { +// // let mut dbm = DBM::in_memory().unwrap(); +// // +// // // Registration receipts are stored alongside tower records when the register command is called +// // let tower_id = get_random_user_id(); +// // let net_addr = "talaia.watch"; +// // let receipt = get_random_registration_receipt(); +// // +// // // Check the receipt was stored +// // dbm.store_tower_record(tower_id, net_addr, &receipt) +// // .unwrap(); +// // assert_eq!( +// // dbm.load_registration_receipt(tower_id, receipt.user_id()) +// // .unwrap(), +// // receipt +// // ); +// // +// // // Add another receipt for the same tower with a higher expiry and check this last one is loaded +// // let middle_receipt = get_registration_receipt_from_previous(&receipt); +// // let latest_receipt = get_registration_receipt_from_previous(&middle_receipt); +// // +// // dbm.store_tower_record(tower_id, net_addr, &latest_receipt) +// // .unwrap(); +// // assert_eq!( +// // dbm.load_registration_receipt(tower_id, latest_receipt.user_id()) +// // .unwrap(), +// // latest_receipt +// // ); +// // +// // // Add a final one with a lower expiry and check the last is still loaded +// // dbm.store_tower_record(tower_id, net_addr, &middle_receipt) +// // .unwrap(); +// // assert_eq!( +// // dbm.load_registration_receipt(tower_id, latest_receipt.user_id()) +// // .unwrap(), +// // latest_receipt +// // ); +// // } +// // +// // #[test] +// // fn test_load_same_registration_receipt() { +// // let mut dbm = DBM::in_memory().unwrap(); +// // +// // // Registration receipts are stored alongside tower records when the register command is called +// // let tower_id = get_random_user_id(); +// // let net_addr = "talaia.watch"; +// // let receipt = get_random_registration_receipt(); +// // +// // // Store it once +// // dbm.store_tower_record(tower_id, net_addr, &receipt) +// // .unwrap(); +// // assert_eq!( +// // dbm.load_registration_receipt(tower_id, receipt.user_id()) +// // .unwrap(), +// // receipt +// // ); +// // +// // // Store the same again, this should fail due to UNIQUE PK constrains. +// // // Notice store_tower_record is guarded against this by WTClient::add_update_tower though. +// // assert!(matches!( +// // dbm.store_tower_record(tower_id, net_addr, &receipt), +// // Err { .. } +// // )); +// // } +// // +// // #[test] +// // fn test_load_nonexistent_tower_record() { +// // let dbm = DBM::in_memory().unwrap(); +// // +// // // If the tower does not exists, `load_tower` will fail. +// // let tower_id = get_random_user_id(); +// // assert!(dbm.load_tower_record(tower_id).is_none()); +// // } +// // +// // #[test] +// // fn test_store_load_towers() { +// // let mut dbm = DBM::in_memory().unwrap(); +// // let mut towers = HashMap::new(); +// // +// // // In order to add a tower record we need to associated registration receipt. +// // for _ in 0..10 { +// // let tower_id = get_random_user_id(); +// // let net_addr = "talaia.watch"; +// // let mut receipt = get_random_registration_receipt(); +// // dbm.store_tower_record(tower_id, net_addr, &receipt) +// // .unwrap(); +// // +// // // Add not only one registration receipt to test if the tower retrieves the one with furthest expiry date. +// // for _ in 0..10 { +// // receipt = get_registration_receipt_from_previous(&receipt); +// // dbm.store_tower_record(tower_id, net_addr, &receipt) +// // .unwrap(); +// // } +// // +// // towers.insert( +// // tower_id, +// // TowerSummary::new( +// // net_addr.to_owned(), +// // receipt.available_slots(), +// // receipt.subscription_start(), +// // receipt.subscription_expiry(), +// // ), +// // ); +// // } +// // +// // assert_eq!(dbm.load_towers(), towers); +// // } +// // +// // #[test] +// // fn test_load_towers_empty() { +// // // If there are no towers in the database, `load_towers` should return an empty map. +// // let dbm = DBM::in_memory().unwrap(); +// // 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(); +// // +// // // In order to add a tower record we need to associated registration receipt. +// // let tower_id = get_random_user_id(); +// // let net_addr = "talaia.watch"; +// // +// // let receipt = get_random_registration_receipt(); +// // let mut tower_summary = TowerSummary::new( +// // net_addr.to_owned(), +// // receipt.available_slots(), +// // receipt.subscription_start(), +// // receipt.subscription_expiry(), +// // ); +// // dbm.store_tower_record(tower_id, net_addr, &receipt) +// // .unwrap(); +// // +// // // Add some appointment receipts and check they match +// // let mut receipts = HashMap::new(); +// // for _ in 0..5 { +// // let appointment = generate_random_appointment(None); +// // let user_signature = "user_signature"; +// // let appointment_receipt = AppointmentReceipt::with_signature( +// // user_signature.to_owned(), +// // 42, +// // "tower_signature".to_owned(), +// // ); +// // +// // tower_summary.available_slots -= 1; +// // +// // dbm.store_appointment_receipt( +// // tower_id, +// // appointment.locator, +// // tower_summary.available_slots, +// // &appointment_receipt, +// // ) +// // .unwrap(); +// // receipts.insert( +// // appointment.locator, +// // appointment_receipt.signature().unwrap(), +// // ); +// // } +// // +// // 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!(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"; +// // let receipt = get_random_registration_receipt(); +// // dbm.store_tower_record(tower_id, net_addr, &receipt) +// // .unwrap(); +// // +// // assert!(dbm +// // .load_appointment_receipt(tower_id, appointment.locator) +// // .is_none()); +// // +// // // Add both +// // let tower_summary = TowerSummary::new( +// // net_addr.to_owned(), +// // receipt.available_slots(), +// // receipt.subscription_start(), +// // receipt.subscription_expiry(), +// // ); +// // let appointment_receipt = AppointmentReceipt::with_signature( +// // "user_signature".to_owned(), +// // 42, +// // "tower_signature".to_owned(), +// // ); +// // 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` +// // let mut dbm = DBM::in_memory().unwrap(); +// // +// // // We first need to add a tower record to the database so we can add some associated data. +// // let tower_id = get_random_user_id(); +// // let net_addr = "talaia.watch"; +// // +// // let receipt = get_random_registration_receipt(); +// // let tower_summary = TowerSummary::new( +// // net_addr.to_owned(), +// // receipt.available_slots(), +// // receipt.subscription_start(), +// // receipt.subscription_expiry(), +// // ); +// // dbm.store_tower_record(tower_id, net_addr, &receipt) +// // .unwrap(); +// // +// // // Create all types of appointments and store them in the db. +// // let user_signature = "user_signature"; +// // let mut receipts = HashSet::new(); +// // let mut pending_appointments = HashSet::new(); +// // let mut invalid_appointments = HashSet::new(); +// // for _ in 0..5 { +// // let appointment = generate_random_appointment(None); +// // let appointment_receipt = AppointmentReceipt::with_signature( +// // user_signature.to_owned(), +// // 42, +// // "tower_signature".to_owned(), +// // ); +// // let pending_appointment = generate_random_appointment(None); +// // let invalid_appointment = generate_random_appointment(None); +// // +// // dbm.store_appointment_receipt( +// // tower_id, +// // appointment.locator, +// // tower_summary.available_slots, +// // &appointment_receipt, +// // ) +// // .unwrap(); +// // dbm.store_pending_appointment(tower_id, &pending_appointment) +// // .unwrap(); +// // dbm.store_invalid_appointment(tower_id, &invalid_appointment) +// // .unwrap(); +// // +// // receipts.insert(appointment.locator); +// // pending_appointments.insert(pending_appointment.locator); +// // invalid_appointments.insert(invalid_appointment.locator); +// // } +// // +// // // Pull data from the db and check it matches the expected data +// // assert_eq!( +// // dbm.load_appointment_locators(tower_id, AppointmentStatus::Accepted), +// // receipts +// // ); +// // assert_eq!( +// // dbm.load_appointment_locators(tower_id, AppointmentStatus::Pending), +// // pending_appointments +// // ); +// // assert_eq!( +// // dbm.load_appointment_locators(tower_id, AppointmentStatus::Invalid), +// // invalid_appointments +// // ); +// // } +// // +// // #[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!(loaded_appointment.is_none()); +// // } +// // +// // #[test] +// // fn test_store_pending_appointment() { +// // let mut dbm = DBM::in_memory().unwrap(); +// // +// // // In order to add a tower record we need to associated registration receipt. +// // let tower_id = get_random_user_id(); +// // let net_addr = "talaia.watch"; +// // +// // let receipt = get_random_registration_receipt(); +// // let mut tower_summary = TowerSummary::new( +// // net_addr.to_owned(), +// // receipt.available_slots(), +// // receipt.subscription_start(), +// // receipt.subscription_expiry(), +// // ) +// // .with_status(TowerStatus::TemporaryUnreachable); +// // dbm.store_tower_record(tower_id, net_addr, &receipt) +// // .unwrap(); +// // +// // // Add some pending appointments and check they match +// // for _ in 0..5 { +// // let appointment = generate_random_appointment(None); +// // +// // tower_summary +// // .pending_appointments +// // .insert(appointment.locator); +// // +// // dbm.store_pending_appointment(tower_id, &appointment) +// // .unwrap(); +// // assert_eq!( +// // TowerSummary::from(dbm.load_tower_record(tower_id).unwrap()), +// // tower_summary +// // ); +// // } +// // } +// // +// // #[test] +// // fn test_store_pending_appointment_twice() { +// // let mut dbm = DBM::in_memory().unwrap(); +// // +// // // In order to add a tower record we need to associated registration receipt. +// // let tower_id_1 = get_random_user_id(); +// // let tower_id_2 = get_random_user_id(); +// // let net_addr = "talaia.watch"; +// // +// // let receipt = get_random_registration_receipt(); +// // dbm.store_tower_record(tower_id_1, net_addr, &receipt) +// // .unwrap(); +// // dbm.store_tower_record(tower_id_2, net_addr, &receipt) +// // .unwrap(); +// // +// // // If the same appointment is stored twice (by different towers) it should go through +// // // Since the appointment data will be stored only once and this will create two references +// // let appointment = generate_random_appointment(None); +// // dbm.store_pending_appointment(tower_id_1, &appointment) +// // .unwrap(); +// // dbm.store_pending_appointment(tower_id_2, &appointment) +// // .unwrap(); +// // +// // // If this is called twice with for the same tower it will fail, since two identical references +// // // can not exist. This is intended behavior and should not happen +// // assert!(dbm +// // .store_pending_appointment(tower_id_2, &appointment) +// // .is_err()); +// // } +// // +// // #[test] +// // fn test_delete_pending_appointment() { +// // let mut dbm = DBM::in_memory().unwrap(); +// // +// // // In order to add a tower record we need to associated registration receipt. +// // 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(); +// // +// // // Add a single one, remove it later +// // let appointment = generate_random_appointment(None); +// // dbm.store_pending_appointment(tower_id, &appointment) +// // .unwrap(); +// // assert!(dbm +// // .delete_pending_appointment(tower_id, appointment.locator) +// // .is_ok()); +// // +// // // The appointment should be completely gone +// // assert!(!dbm +// // .load_appointment_locators(tower_id, AppointmentStatus::Pending) +// // .contains(&appointment.locator)); +// // // assert!(!dbm.appointment_exists(appointment.locator)); +// // +// // // Try again with more than one reference +// // let another_tower_id = get_random_user_id(); +// // dbm.store_tower_record(another_tower_id, net_addr, &receipt) +// // .unwrap(); +// // +// // // Add two +// // dbm.store_pending_appointment(tower_id, &appointment) +// // .unwrap(); +// // dbm.store_pending_appointment(another_tower_id, &appointment) +// // .unwrap(); +// // // Delete one +// // assert!(dbm +// // .delete_pending_appointment(tower_id, appointment.locator) +// // .is_ok()); +// // // Check +// // assert!(!dbm +// // .load_appointment_locators(tower_id, AppointmentStatus::Pending) +// // .contains(&appointment.locator)); +// // assert!(dbm +// // .load_appointment_locators(another_tower_id, AppointmentStatus::Pending) +// // .contains(&appointment.locator)); +// // // assert!(dbm.appointment_exists(appointment.locator)); +// // +// // // Add an invalid reference and check again +// // dbm.store_invalid_appointment(tower_id, &appointment) +// // .unwrap(); +// // assert!(dbm +// // .delete_pending_appointment(another_tower_id, appointment.locator) +// // .is_ok()); +// // assert!(!dbm +// // .load_appointment_locators(another_tower_id, AppointmentStatus::Pending) +// // .contains(&appointment.locator)); +// // assert!(dbm +// // .load_appointment_locators(tower_id, AppointmentStatus::Invalid) +// // .contains(&appointment.locator)); +// // // assert!(dbm.appointment_exists(appointment.locator)); +// // } +// // +// // #[test] +// // fn test_store_invalid_appointment() { +// // let mut dbm = DBM::in_memory().unwrap(); +// // +// // // In order to add a tower record we need to associated registration receipt. +// // let tower_id = get_random_user_id(); +// // let net_addr = "talaia.watch"; +// // +// // let receipt = get_random_registration_receipt(); +// // let mut tower_summary = TowerSummary::new( +// // net_addr.to_owned(), +// // receipt.available_slots(), +// // receipt.subscription_start(), +// // receipt.subscription_expiry(), +// // ); +// // dbm.store_tower_record(tower_id, net_addr, &receipt) +// // .unwrap(); +// // +// // // Add some invalid appointments and check they match +// // for _ in 0..5 { +// // let appointment = generate_random_appointment(None); +// // +// // tower_summary +// // .invalid_appointments +// // .insert(appointment.locator); +// // +// // dbm.store_invalid_appointment(tower_id, &appointment) +// // .unwrap(); +// // assert_eq!( +// // TowerSummary::from(dbm.load_tower_record(tower_id).unwrap()), +// // tower_summary +// // ); +// // } +// // } +// // +// // #[test] +// // fn test_store_invalid_appointment_twice() { +// // let mut dbm = DBM::in_memory().unwrap(); +// // +// // // In order to add a tower record we need to associated registration receipt. +// // let tower_id_1 = get_random_user_id(); +// // let tower_id_2 = get_random_user_id(); +// // let net_addr = "talaia.watch"; +// // +// // let receipt = get_random_registration_receipt(); +// // dbm.store_tower_record(tower_id_1, net_addr, &receipt) +// // .unwrap(); +// // dbm.store_tower_record(tower_id_2, net_addr, &receipt) +// // .unwrap(); +// // +// // // Same as with pending appointments. Two references from different towers is allowed +// // let appointment = generate_random_appointment(None); +// // dbm.store_invalid_appointment(tower_id_1, &appointment) +// // .unwrap(); +// // dbm.store_invalid_appointment(tower_id_2, &appointment) +// // .unwrap(); +// // +// // // Two references from the same tower is not. +// // assert!(dbm +// // .store_invalid_appointment(tower_id_2, &appointment) +// // .is_err()); +// // } +// // +// // #[test] +// // fn test_store_load_misbehaving_proof() { +// // let mut dbm = DBM::in_memory().unwrap(); +// // +// // // In order to add a tower record we need to associated registration receipt. +// // let tower_id = get_random_user_id(); +// // let net_addr = "talaia.watch"; +// // +// // let receipt = get_random_registration_receipt(); +// // let tower_summary = TowerSummary::new( +// // net_addr.to_owned(), +// // receipt.available_slots(), +// // receipt.subscription_start(), +// // receipt.subscription_expiry(), +// // ); +// // dbm.store_tower_record(tower_id, net_addr, &receipt) +// // .unwrap(); +// // assert_eq!( +// // TowerSummary::from(dbm.load_tower_record(tower_id).unwrap()), +// // tower_summary +// // ); +// // +// // // Store a misbehaving proof and load it back +// // let appointment = generate_random_appointment(None); +// // let appointment_receipt = AppointmentReceipt::with_signature( +// // "user_signature".to_owned(), +// // 42, +// // "tower_signature".to_owned(), +// // ); +// // +// // let proof = MisbehaviorProof::new( +// // appointment.locator, +// // appointment_receipt, +// // get_random_user_id(), +// // ); +// // +// // dbm.store_misbehaving_proof(tower_id, &proof).unwrap(); +// // assert_eq!(dbm.load_misbehaving_proof(tower_id).unwrap(), proof); +// // } +// // +// // #[test] +// // fn test_store_load_non_existing_misbehaving_proof() { +// // let dbm = DBM::in_memory().unwrap(); +// // assert!(dbm.load_misbehaving_proof(get_random_user_id()).is_none()); +// // } +// // +// // #[test] +// // fn test_store_exists_misbehaving_proof() { +// // let mut dbm = DBM::in_memory().unwrap(); +// // +// // // In order to add a tower record we need to associated registration receipt. +// // let tower_id = get_random_user_id(); +// // let net_addr = "talaia.watch"; +// // +// // let receipt = get_random_registration_receipt(); +// // let tower_summary = TowerSummary::new( +// // net_addr.to_owned(), +// // receipt.available_slots(), +// // receipt.subscription_start(), +// // receipt.subscription_expiry(), +// // ); +// // dbm.store_tower_record(tower_id, net_addr, &receipt) +// // .unwrap(); +// // assert_eq!( +// // TowerSummary::from(dbm.load_tower_record(tower_id).unwrap()), +// // tower_summary +// // ); +// // +// // // // Store a misbehaving proof check +// // let appointment = generate_random_appointment(None); +// // let appointment_receipt = AppointmentReceipt::with_signature( +// // "user_signature".to_owned(), +// // 42, +// // "tower_signature".to_owned(), +// // ); +// // +// // let proof = MisbehaviorProof::new( +// // appointment.locator, +// // appointment_receipt, +// // get_random_user_id(), +// // ); +// // +// // dbm.store_misbehaving_proof(tower_id, &proof).unwrap(); +// // assert!(dbm.exists_misbehaving_proof(tower_id)); +// // } +// // +// // #[test] +// // fn test_exists_misbehaving_proof_false() { +// // let dbm = DBM::in_memory().unwrap(); +// // assert!(!dbm.exists_misbehaving_proof(get_random_user_id())); +// // } +// } diff --git a/teos-ldk-client/src/storage/memory_store.rs b/teos-ldk-client/src/storage/memory_store.rs index fdf15294..73ee072d 100644 --- a/teos-ldk-client/src/storage/memory_store.rs +++ b/teos-ldk-client/src/storage/memory_store.rs @@ -3,6 +3,8 @@ use lightning::util::persist::KVStore; use std::collections::HashMap; use std::sync::{Arc, Mutex}; +pub(crate) type DynStore = dyn KVStore + Sync + Send; + /// In-memory key-value store implementation for testing #[derive(Clone, Debug)] pub struct MemoryStore { diff --git a/teos-ldk-client/src/storage/mod.rs b/teos-ldk-client/src/storage/mod.rs index 3ac3ae8e..521537f2 100644 --- a/teos-ldk-client/src/storage/mod.rs +++ b/teos-ldk-client/src/storage/mod.rs @@ -2,48 +2,48 @@ pub mod persister; mod sql_storage; use std::path::PathBuf; +use std::sync::Arc; pub use crate::storage::persister::{Persister, PersisterError}; use sql_storage::DBM; -#[cfg(feature = "kv")] mod kv; -#[cfg(feature = "kv")] -pub use kv::DBError; -#[cfg(feature = "kv")] -pub(crate) use kv::Storage; +use kv::{DynStore, KVStorage}; #[cfg(test)] -#[cfg(feature = "kv")] mod memory_store; + #[cfg(test)] -#[cfg(feature = "kv")] pub use memory_store::MemoryStore; pub fn create_storage( config: StorageConfig, ) -> Result, PersisterError> { - match config.storage_type { - StorageType::SQL => match DBM::new(&config.db_path.expect("db_path must be specificed")) { + match config { + StorageConfig::SQL { db_path } => match DBM::new(&db_path) { + Ok(storage) => Ok(Box::new(storage)), + Err(e) => Err(PersisterError::Other(format!( + "Error creating storage: {}", + e + ))), + }, + StorageConfig::KV { kv_store, sk } => match KVStorage::new(kv_store, sk) { Ok(storage) => Ok(Box::new(storage)), Err(e) => Err(PersisterError::Other(format!( "Error creating storage: {}", e ))), }, - _ => { - panic!("Unsupported persistence type"); - } } } -pub struct StorageConfig { - pub storage_type: StorageType, - pub db_path: Option, -} - -pub enum StorageType { - SQL, - KV, +pub enum StorageConfig { + SQL { + db_path: PathBuf, + }, + KV { + kv_store: Arc, + sk: Vec, + }, } diff --git a/teos-ldk-client/src/storage/persister.rs b/teos-ldk-client/src/storage/persister.rs index 0d1122a4..6bc09040 100644 --- a/teos-ldk-client/src/storage/persister.rs +++ b/teos-ldk-client/src/storage/persister.rs @@ -153,7 +153,10 @@ pub trait Persister: Send { proof: &MisbehaviorProof, ) -> Result<(), PersisterError>; + /// Checks if appointment for a given locator exists in a local sotrage + /// fn appointment_exists(&self, locator: Locator) -> bool; + /// Checks if appointment receipt from a give tower for a given locator exists in a local sotrage fn appointment_receipt_exists(&self, locator: Locator, tower_id: TowerId) -> bool; } diff --git a/teos-ldk-client/src/wt_client.rs b/teos-ldk-client/src/wt_client.rs index 001cca1c..d9f36de8 100644 --- a/teos-ldk-client/src/wt_client.rs +++ b/teos-ldk-client/src/wt_client.rs @@ -278,7 +278,7 @@ impl WTClient { mod tests { use super::*; - use crate::storage::{create_storage, StorageConfig, StorageType}; + use crate::storage::{create_storage, StorageConfig}; use tempdir::TempDir; use teos_common::cryptography; use tokio::sync::mpsc::unbounded_channel; @@ -295,11 +295,7 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig { - storage_type: StorageType::SQL, - db_path: Some(db_path), - }) - .unwrap(); + let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -397,11 +393,7 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig { - storage_type: StorageType::SQL, - db_path: Some(db_path), - }) - .unwrap(); + let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -428,11 +420,7 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig { - storage_type: StorageType::SQL, - db_path: Some(db_path), - }) - .unwrap(); + let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -466,11 +454,7 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig { - storage_type: StorageType::SQL, - db_path: Some(db_path), - }) - .unwrap(); + let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -524,11 +508,7 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig { - storage_type: StorageType::SQL, - db_path: Some(db_path), - }) - .unwrap(); + let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -575,11 +555,7 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig { - storage_type: StorageType::SQL, - db_path: Some(db_path), - }) - .unwrap(); + let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -614,11 +590,7 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig { - storage_type: StorageType::SQL, - db_path: Some(db_path), - }) - .unwrap(); + let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -661,11 +633,7 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig { - storage_type: StorageType::SQL, - db_path: Some(db_path), - }) - .unwrap(); + let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -713,11 +681,7 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig { - storage_type: StorageType::SQL, - db_path: Some(db_path), - }) - .unwrap(); + let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -795,11 +759,7 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig { - storage_type: StorageType::SQL, - db_path: Some(db_path), - }) - .unwrap(); + let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -838,11 +798,7 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig { - storage_type: StorageType::SQL, - db_path: Some(db_path), - }) - .unwrap(); + let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -911,11 +867,7 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig { - storage_type: StorageType::SQL, - db_path: Some(db_path), - }) - .unwrap(); + let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -976,11 +928,7 @@ mod tests { let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig { - storage_type: StorageType::SQL, - db_path: Some(db_path), - }) - .unwrap(); + let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; From 8183c3287abf08f92c4f7032c76846db38602eeb Mon Sep 17 00:00:00 2001 From: dzdidi Date: Thu, 13 Feb 2025 12:44:39 +0100 Subject: [PATCH 53/85] mem_store to dyn_store Signed-off-by: dzdidi --- teos-ldk-client/src/storage/memory_store.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/teos-ldk-client/src/storage/memory_store.rs b/teos-ldk-client/src/storage/memory_store.rs index 73ee072d..0c7e2590 100644 --- a/teos-ldk-client/src/storage/memory_store.rs +++ b/teos-ldk-client/src/storage/memory_store.rs @@ -23,6 +23,10 @@ impl MemoryStore { fn make_key(namespace: &str, key: &str) -> String { format!("{}:{}", namespace, key) } + + pub fn into_dyn_store(self) -> Arc { + Arc::new(self) + } } impl Default for MemoryStore { From 9474a2b151077bd87dab3c076094a666df4dff10 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Thu, 13 Feb 2025 12:44:53 +0100 Subject: [PATCH 54/85] tower_info serde Signed-off-by: dzdidi --- teos-ldk-client/src/lib.rs | 17 +- teos-ldk-client/src/storage/kv.rs | 1393 +++++++++++++++-------------- 2 files changed, 716 insertions(+), 694 deletions(-) diff --git a/teos-ldk-client/src/lib.rs b/teos-ldk-client/src/lib.rs index 1eb07423..d1a74c64 100644 --- a/teos-ldk-client/src/lib.rs +++ b/teos-ldk-client/src/lib.rs @@ -196,19 +196,20 @@ impl From for TowerSummary { /// Summarized data associated with a given tower. #[derive(Clone, Deserialize, Serialize, Debug, PartialEq, Eq)] +#[serde(crate = "serde")] pub struct TowerInfo { pub net_addr: String, pub available_slots: u32, pub subscription_start: u32, pub subscription_expiry: u32, pub status: TowerStatus, - #[serde(serialize_with = "crate::ser::serialize_receipts")] + #[serde(default)] pub appointments: HashMap, - #[serde(serialize_with = "crate::ser::serialize_appointments")] + #[serde(default)] pub pending_appointments: Vec, - #[serde(serialize_with = "crate::ser::serialize_appointments")] + #[serde(default)] pub invalid_appointments: Vec, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] pub misbehaving_proof: Option, } @@ -247,12 +248,12 @@ impl TowerInfo { self.misbehaving_proof = Some(proof); } - fn to_vec(&self) -> Result, io::Error> { - bincode::serialize(self).map_err(|e| io::Error::new(io::ErrorKind::Other, e)) + pub fn to_vec(&self) -> Result, bincode::Error> { + bincode::serialize(self) } - fn from_slice(slice: &[u8]) -> Result { - bincode::deserialize(slice).map_err(|e| io::Error::new(io::ErrorKind::Other, e)) + pub fn from_slice(slice: &[u8]) -> Result { + bincode::deserialize(slice) } } diff --git a/teos-ldk-client/src/storage/kv.rs b/teos-ldk-client/src/storage/kv.rs index ea13b1f2..849f21e0 100644 --- a/teos-ldk-client/src/storage/kv.rs +++ b/teos-ldk-client/src/storage/kv.rs @@ -1,9 +1,13 @@ use std::collections::{HashMap, HashSet}; +use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce}; +// use chacha20poly1305::aead::{Aead}; +use bitcoin::hashes::{sha256, Hash}; use std::sync::Arc; use crate::storage::persister::{Persister, PersisterError}; // use bitcoin::secp256k1::SecretKey; -pub use lightning::io::Error as DBError; +use lightning::io::Error as DBError; + use lightning::util::persist::KVStore; use teos_common::appointment::{Appointment, Locator}; use teos_common::receipts::{AppointmentReceipt, RegistrationReceipt}; @@ -41,6 +45,13 @@ const NS_MISBEHAVIOR_PROOFS: &str = "misbehavior_proofs"; pub type DynStore = dyn KVStore + Sync + Send; +/// Enum representing the possible errors when decrypting an encrypted blob. +#[derive(Debug)] +pub enum DecryptingError { + AED(chacha20poly1305::aead::Error), + Encode(bitcoin::consensus::encode::Error), +} + pub struct KVStorage { store: Arc, sk: Vec, @@ -79,25 +90,25 @@ impl Persister for KVStorage { net_addr: &str, receipt: &RegistrationReceipt, ) -> Result<(), PersisterError> { - todo!(); - // let key = make_key(&[&tower_id.to_string()]); - // - // let value = TowerInfo::new( - // net_addr.to_string(), - // receipt.available_slots(), - // receipt.subscription_start(), - // receipt.subscription_expiry(), - // HashMap::new(), - // Vec::new(), - // Vec::new(), - // ) - // .to_vec() - // .unwrap(); - // + let key = make_key(&[&tower_id.to_string()]); + + let tower_info = TowerInfo::new( + net_addr.to_string(), + receipt.available_slots(), + receipt.subscription_start(), + receipt.subscription_expiry(), + HashMap::new(), + Vec::new(), + Vec::new(), + ); + + let value = bincode::serialize(&tower_info) + .map_err(|e| PersisterError::Other(format!("Serialization error: {}", e)))?; + // let encrypted = encrypt(&value, &self.sk).unwrap(); - // - // self.store - // .write(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key, &encrypted) + + self.store + .write(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key, &value).map_err(|e| PersisterError::StoreError(e.to_string())) } /// Loads a tower record from the database. @@ -106,23 +117,26 @@ impl Persister for KVStorage { /// 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. fn load_tower_record(&self, tower_id: TowerId) -> Option { - todo!(); - // let key = make_key(&[&tower_id.to_string()]); - // let value = match self.store.read(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key) { - // Ok(v) => v, - // Err(_) => return None, - // }; - // + let key = make_key(&[&tower_id.to_string()]); + let value = match self.store.read(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key) { + Ok(v) => v, + Err(_) => return None, + }; + // let decrypted = decrypt(&value, &self.sk).unwrap(); - // - // let mut tower_info = TowerInfo::from_slice(&decrypted).unwrap(); - // // tower_info.appointments = self.load_appointment_receipts(tower_id); - // // tower_info.pending_appointments = - // // self.load_appointments(tower_id, AppointmentStatus::Pending); - // // tower_info.invalid_appointments = - // // self.load_appointments(tower_id, AppointmentStatus::Invalid); - // - // Some(tower_info) + let tower_info: TowerInfo = bincode::deserialize(&value) + .map_err(|e| { + e + }) + .ok()?; + + // tower_info.appointments = self.load_appointment_receipts(tower_id); + // tower_info.pending_appointments = + // self.load_appointments(tower_id, AppointmentStatus::Pending); + // tower_info.invalid_appointments = + // self.load_appointments(tower_id, AppointmentStatus::Invalid); + + Some(tower_info) } /// Removes a tower record from the database. @@ -353,7 +367,6 @@ impl Persister for KVStorage { /// /// The message to be encrypted is expected to be the penalty transaction. fn encrypt(message: &Vec, secret: &Vec) -> Result, chacha20poly1305::aead::Error> { - todo!(); // // Defaults is [0; 12] // let nonce = Nonce::default(); // let k = sha256::Hash::hash(secret); @@ -362,6 +375,7 @@ fn encrypt(message: &Vec, secret: &Vec) -> Result, chacha20poly1 // let cypher = ChaCha20Poly1305::new(key); // // cypher.encrypt(&nonce, message.to_vec().as_ref()) + Ok(message.clone()) } /// Decrypts an encrypted blob of data using `chacha20poly1305` and a given secret. @@ -375,7 +389,6 @@ fn decrypt( encrypted_blob: &[u8], secret: &Vec, ) -> Result, chacha20poly1305::aead::Error> { - todo!(); // // Defaults is [0; 12] // let nonce = Nonce::default(); // let k = sha256::Hash::hash(secret); @@ -384,654 +397,662 @@ fn decrypt( // let cypher = ChaCha20Poly1305::new(key); // // cypher.decrypt(&nonce, encrypted_blob.as_ref()) + Ok(encrypted_blob.to_vec()) } -// #[cfg(test)] -// mod tests { -// use super::*; -// use crate::storage::MemoryStore; -// -// use teos_common::test_utils::{ -// generate_random_appointment, get_random_registration_receipt, get_random_user_id, -// get_registration_receipt_from_previous, -// }; -// -// fn create_test_storage() -> Storage { -// let store = MemoryStore::new(); -// let sk = vec![0u8; 32]; // Test secret key -// Storage::new(store, sk).unwrap() -// } -// -// #[test] -// fn test_store_load_tower_record() { -// let mut storage = create_test_storage(); -// -// // In order to add a tower record we need to associated registration receipt. -// let tower_id = get_random_user_id(); -// let net_addr = "talaia.watch"; -// -// let receipt = get_random_registration_receipt(); -// let tower_info = TowerInfo::new( -// net_addr.to_owned(), -// receipt.available_slots(), -// receipt.subscription_start(), -// receipt.subscription_expiry(), -// HashMap::new(), -// Vec::new(), -// Vec::new(), -// ); -// -// // Check the loaded data matches the in memory data -// storage -// .store_tower_record(tower_id, net_addr, &receipt) -// .unwrap(); -// assert_eq!(storage.load_tower_record(tower_id).unwrap(), tower_info); -// } -// -// // #[test] -// // fn test_load_registration_receipt() { -// // let mut dbm = DBM::in_memory().unwrap(); -// // -// // // Registration receipts are stored alongside tower records when the register command is called -// // let tower_id = get_random_user_id(); -// // let net_addr = "talaia.watch"; -// // let receipt = get_random_registration_receipt(); -// // -// // // Check the receipt was stored -// // dbm.store_tower_record(tower_id, net_addr, &receipt) -// // .unwrap(); -// // assert_eq!( -// // dbm.load_registration_receipt(tower_id, receipt.user_id()) -// // .unwrap(), -// // receipt -// // ); -// // -// // // Add another receipt for the same tower with a higher expiry and check this last one is loaded -// // let middle_receipt = get_registration_receipt_from_previous(&receipt); -// // let latest_receipt = get_registration_receipt_from_previous(&middle_receipt); -// // -// // dbm.store_tower_record(tower_id, net_addr, &latest_receipt) -// // .unwrap(); -// // assert_eq!( -// // dbm.load_registration_receipt(tower_id, latest_receipt.user_id()) -// // .unwrap(), -// // latest_receipt -// // ); -// // -// // // Add a final one with a lower expiry and check the last is still loaded -// // dbm.store_tower_record(tower_id, net_addr, &middle_receipt) -// // .unwrap(); -// // assert_eq!( -// // dbm.load_registration_receipt(tower_id, latest_receipt.user_id()) -// // .unwrap(), -// // latest_receipt -// // ); -// // } -// // -// // #[test] -// // fn test_load_same_registration_receipt() { -// // let mut dbm = DBM::in_memory().unwrap(); -// // -// // // Registration receipts are stored alongside tower records when the register command is called -// // let tower_id = get_random_user_id(); -// // let net_addr = "talaia.watch"; -// // let receipt = get_random_registration_receipt(); -// // -// // // Store it once -// // dbm.store_tower_record(tower_id, net_addr, &receipt) -// // .unwrap(); -// // assert_eq!( -// // dbm.load_registration_receipt(tower_id, receipt.user_id()) -// // .unwrap(), -// // receipt -// // ); -// // -// // // Store the same again, this should fail due to UNIQUE PK constrains. -// // // Notice store_tower_record is guarded against this by WTClient::add_update_tower though. -// // assert!(matches!( -// // dbm.store_tower_record(tower_id, net_addr, &receipt), -// // Err { .. } -// // )); -// // } -// // -// // #[test] -// // fn test_load_nonexistent_tower_record() { -// // let dbm = DBM::in_memory().unwrap(); -// // -// // // If the tower does not exists, `load_tower` will fail. -// // let tower_id = get_random_user_id(); -// // assert!(dbm.load_tower_record(tower_id).is_none()); -// // } -// // -// // #[test] -// // fn test_store_load_towers() { -// // let mut dbm = DBM::in_memory().unwrap(); -// // let mut towers = HashMap::new(); -// // -// // // In order to add a tower record we need to associated registration receipt. -// // for _ in 0..10 { -// // let tower_id = get_random_user_id(); -// // let net_addr = "talaia.watch"; -// // let mut receipt = get_random_registration_receipt(); -// // dbm.store_tower_record(tower_id, net_addr, &receipt) -// // .unwrap(); -// // -// // // Add not only one registration receipt to test if the tower retrieves the one with furthest expiry date. -// // for _ in 0..10 { -// // receipt = get_registration_receipt_from_previous(&receipt); -// // dbm.store_tower_record(tower_id, net_addr, &receipt) -// // .unwrap(); -// // } -// // -// // towers.insert( -// // tower_id, -// // TowerSummary::new( -// // net_addr.to_owned(), -// // receipt.available_slots(), -// // receipt.subscription_start(), -// // receipt.subscription_expiry(), -// // ), -// // ); -// // } -// // -// // assert_eq!(dbm.load_towers(), towers); -// // } -// // -// // #[test] -// // fn test_load_towers_empty() { -// // // If there are no towers in the database, `load_towers` should return an empty map. -// // let dbm = DBM::in_memory().unwrap(); -// // 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(); -// // -// // // In order to add a tower record we need to associated registration receipt. -// // let tower_id = get_random_user_id(); -// // let net_addr = "talaia.watch"; -// // -// // let receipt = get_random_registration_receipt(); -// // let mut tower_summary = TowerSummary::new( -// // net_addr.to_owned(), -// // receipt.available_slots(), -// // receipt.subscription_start(), -// // receipt.subscription_expiry(), -// // ); -// // dbm.store_tower_record(tower_id, net_addr, &receipt) -// // .unwrap(); -// // -// // // Add some appointment receipts and check they match -// // let mut receipts = HashMap::new(); -// // for _ in 0..5 { -// // let appointment = generate_random_appointment(None); -// // let user_signature = "user_signature"; -// // let appointment_receipt = AppointmentReceipt::with_signature( -// // user_signature.to_owned(), -// // 42, -// // "tower_signature".to_owned(), -// // ); -// // -// // tower_summary.available_slots -= 1; -// // -// // dbm.store_appointment_receipt( -// // tower_id, -// // appointment.locator, -// // tower_summary.available_slots, -// // &appointment_receipt, -// // ) -// // .unwrap(); -// // receipts.insert( -// // appointment.locator, -// // appointment_receipt.signature().unwrap(), -// // ); -// // } -// // -// // 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!(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"; -// // let receipt = get_random_registration_receipt(); -// // dbm.store_tower_record(tower_id, net_addr, &receipt) -// // .unwrap(); -// // -// // assert!(dbm -// // .load_appointment_receipt(tower_id, appointment.locator) -// // .is_none()); -// // -// // // Add both -// // let tower_summary = TowerSummary::new( -// // net_addr.to_owned(), -// // receipt.available_slots(), -// // receipt.subscription_start(), -// // receipt.subscription_expiry(), -// // ); -// // let appointment_receipt = AppointmentReceipt::with_signature( -// // "user_signature".to_owned(), -// // 42, -// // "tower_signature".to_owned(), -// // ); -// // 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` -// // let mut dbm = DBM::in_memory().unwrap(); -// // -// // // We first need to add a tower record to the database so we can add some associated data. -// // let tower_id = get_random_user_id(); -// // let net_addr = "talaia.watch"; -// // -// // let receipt = get_random_registration_receipt(); -// // let tower_summary = TowerSummary::new( -// // net_addr.to_owned(), -// // receipt.available_slots(), -// // receipt.subscription_start(), -// // receipt.subscription_expiry(), -// // ); -// // dbm.store_tower_record(tower_id, net_addr, &receipt) -// // .unwrap(); -// // -// // // Create all types of appointments and store them in the db. -// // let user_signature = "user_signature"; -// // let mut receipts = HashSet::new(); -// // let mut pending_appointments = HashSet::new(); -// // let mut invalid_appointments = HashSet::new(); -// // for _ in 0..5 { -// // let appointment = generate_random_appointment(None); -// // let appointment_receipt = AppointmentReceipt::with_signature( -// // user_signature.to_owned(), -// // 42, -// // "tower_signature".to_owned(), -// // ); -// // let pending_appointment = generate_random_appointment(None); -// // let invalid_appointment = generate_random_appointment(None); -// // -// // dbm.store_appointment_receipt( -// // tower_id, -// // appointment.locator, -// // tower_summary.available_slots, -// // &appointment_receipt, -// // ) -// // .unwrap(); -// // dbm.store_pending_appointment(tower_id, &pending_appointment) -// // .unwrap(); -// // dbm.store_invalid_appointment(tower_id, &invalid_appointment) -// // .unwrap(); -// // -// // receipts.insert(appointment.locator); -// // pending_appointments.insert(pending_appointment.locator); -// // invalid_appointments.insert(invalid_appointment.locator); -// // } -// // -// // // Pull data from the db and check it matches the expected data -// // assert_eq!( -// // dbm.load_appointment_locators(tower_id, AppointmentStatus::Accepted), -// // receipts -// // ); -// // assert_eq!( -// // dbm.load_appointment_locators(tower_id, AppointmentStatus::Pending), -// // pending_appointments -// // ); -// // assert_eq!( -// // dbm.load_appointment_locators(tower_id, AppointmentStatus::Invalid), -// // invalid_appointments -// // ); -// // } -// // -// // #[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!(loaded_appointment.is_none()); -// // } -// // -// // #[test] -// // fn test_store_pending_appointment() { -// // let mut dbm = DBM::in_memory().unwrap(); -// // -// // // In order to add a tower record we need to associated registration receipt. -// // let tower_id = get_random_user_id(); -// // let net_addr = "talaia.watch"; -// // -// // let receipt = get_random_registration_receipt(); -// // let mut tower_summary = TowerSummary::new( -// // net_addr.to_owned(), -// // receipt.available_slots(), -// // receipt.subscription_start(), -// // receipt.subscription_expiry(), -// // ) -// // .with_status(TowerStatus::TemporaryUnreachable); -// // dbm.store_tower_record(tower_id, net_addr, &receipt) -// // .unwrap(); -// // -// // // Add some pending appointments and check they match -// // for _ in 0..5 { -// // let appointment = generate_random_appointment(None); -// // -// // tower_summary -// // .pending_appointments -// // .insert(appointment.locator); -// // -// // dbm.store_pending_appointment(tower_id, &appointment) -// // .unwrap(); -// // assert_eq!( -// // TowerSummary::from(dbm.load_tower_record(tower_id).unwrap()), -// // tower_summary -// // ); -// // } -// // } -// // -// // #[test] -// // fn test_store_pending_appointment_twice() { -// // let mut dbm = DBM::in_memory().unwrap(); -// // -// // // In order to add a tower record we need to associated registration receipt. -// // let tower_id_1 = get_random_user_id(); -// // let tower_id_2 = get_random_user_id(); -// // let net_addr = "talaia.watch"; -// // -// // let receipt = get_random_registration_receipt(); -// // dbm.store_tower_record(tower_id_1, net_addr, &receipt) -// // .unwrap(); -// // dbm.store_tower_record(tower_id_2, net_addr, &receipt) -// // .unwrap(); -// // -// // // If the same appointment is stored twice (by different towers) it should go through -// // // Since the appointment data will be stored only once and this will create two references -// // let appointment = generate_random_appointment(None); -// // dbm.store_pending_appointment(tower_id_1, &appointment) -// // .unwrap(); -// // dbm.store_pending_appointment(tower_id_2, &appointment) -// // .unwrap(); -// // -// // // If this is called twice with for the same tower it will fail, since two identical references -// // // can not exist. This is intended behavior and should not happen -// // assert!(dbm -// // .store_pending_appointment(tower_id_2, &appointment) -// // .is_err()); -// // } -// // -// // #[test] -// // fn test_delete_pending_appointment() { -// // let mut dbm = DBM::in_memory().unwrap(); -// // -// // // In order to add a tower record we need to associated registration receipt. -// // 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(); -// // -// // // Add a single one, remove it later -// // let appointment = generate_random_appointment(None); -// // dbm.store_pending_appointment(tower_id, &appointment) -// // .unwrap(); -// // assert!(dbm -// // .delete_pending_appointment(tower_id, appointment.locator) -// // .is_ok()); -// // -// // // The appointment should be completely gone -// // assert!(!dbm -// // .load_appointment_locators(tower_id, AppointmentStatus::Pending) -// // .contains(&appointment.locator)); -// // // assert!(!dbm.appointment_exists(appointment.locator)); -// // -// // // Try again with more than one reference -// // let another_tower_id = get_random_user_id(); -// // dbm.store_tower_record(another_tower_id, net_addr, &receipt) -// // .unwrap(); -// // -// // // Add two -// // dbm.store_pending_appointment(tower_id, &appointment) -// // .unwrap(); -// // dbm.store_pending_appointment(another_tower_id, &appointment) -// // .unwrap(); -// // // Delete one -// // assert!(dbm -// // .delete_pending_appointment(tower_id, appointment.locator) -// // .is_ok()); -// // // Check -// // assert!(!dbm -// // .load_appointment_locators(tower_id, AppointmentStatus::Pending) -// // .contains(&appointment.locator)); -// // assert!(dbm -// // .load_appointment_locators(another_tower_id, AppointmentStatus::Pending) -// // .contains(&appointment.locator)); -// // // assert!(dbm.appointment_exists(appointment.locator)); -// // -// // // Add an invalid reference and check again -// // dbm.store_invalid_appointment(tower_id, &appointment) -// // .unwrap(); -// // assert!(dbm -// // .delete_pending_appointment(another_tower_id, appointment.locator) -// // .is_ok()); -// // assert!(!dbm -// // .load_appointment_locators(another_tower_id, AppointmentStatus::Pending) -// // .contains(&appointment.locator)); -// // assert!(dbm -// // .load_appointment_locators(tower_id, AppointmentStatus::Invalid) -// // .contains(&appointment.locator)); -// // // assert!(dbm.appointment_exists(appointment.locator)); -// // } -// // -// // #[test] -// // fn test_store_invalid_appointment() { -// // let mut dbm = DBM::in_memory().unwrap(); -// // -// // // In order to add a tower record we need to associated registration receipt. -// // let tower_id = get_random_user_id(); -// // let net_addr = "talaia.watch"; -// // -// // let receipt = get_random_registration_receipt(); -// // let mut tower_summary = TowerSummary::new( -// // net_addr.to_owned(), -// // receipt.available_slots(), -// // receipt.subscription_start(), -// // receipt.subscription_expiry(), -// // ); -// // dbm.store_tower_record(tower_id, net_addr, &receipt) -// // .unwrap(); -// // -// // // Add some invalid appointments and check they match -// // for _ in 0..5 { -// // let appointment = generate_random_appointment(None); -// // -// // tower_summary -// // .invalid_appointments -// // .insert(appointment.locator); -// // -// // dbm.store_invalid_appointment(tower_id, &appointment) -// // .unwrap(); -// // assert_eq!( -// // TowerSummary::from(dbm.load_tower_record(tower_id).unwrap()), -// // tower_summary -// // ); -// // } -// // } -// // -// // #[test] -// // fn test_store_invalid_appointment_twice() { -// // let mut dbm = DBM::in_memory().unwrap(); -// // -// // // In order to add a tower record we need to associated registration receipt. -// // let tower_id_1 = get_random_user_id(); -// // let tower_id_2 = get_random_user_id(); -// // let net_addr = "talaia.watch"; -// // -// // let receipt = get_random_registration_receipt(); -// // dbm.store_tower_record(tower_id_1, net_addr, &receipt) -// // .unwrap(); -// // dbm.store_tower_record(tower_id_2, net_addr, &receipt) -// // .unwrap(); -// // -// // // Same as with pending appointments. Two references from different towers is allowed -// // let appointment = generate_random_appointment(None); -// // dbm.store_invalid_appointment(tower_id_1, &appointment) -// // .unwrap(); -// // dbm.store_invalid_appointment(tower_id_2, &appointment) -// // .unwrap(); -// // -// // // Two references from the same tower is not. -// // assert!(dbm -// // .store_invalid_appointment(tower_id_2, &appointment) -// // .is_err()); -// // } -// // -// // #[test] -// // fn test_store_load_misbehaving_proof() { -// // let mut dbm = DBM::in_memory().unwrap(); -// // -// // // In order to add a tower record we need to associated registration receipt. -// // let tower_id = get_random_user_id(); -// // let net_addr = "talaia.watch"; -// // -// // let receipt = get_random_registration_receipt(); -// // let tower_summary = TowerSummary::new( -// // net_addr.to_owned(), -// // receipt.available_slots(), -// // receipt.subscription_start(), -// // receipt.subscription_expiry(), -// // ); -// // dbm.store_tower_record(tower_id, net_addr, &receipt) -// // .unwrap(); -// // assert_eq!( -// // TowerSummary::from(dbm.load_tower_record(tower_id).unwrap()), -// // tower_summary -// // ); -// // -// // // Store a misbehaving proof and load it back -// // let appointment = generate_random_appointment(None); -// // let appointment_receipt = AppointmentReceipt::with_signature( -// // "user_signature".to_owned(), -// // 42, -// // "tower_signature".to_owned(), -// // ); -// // -// // let proof = MisbehaviorProof::new( -// // appointment.locator, -// // appointment_receipt, -// // get_random_user_id(), -// // ); -// // -// // dbm.store_misbehaving_proof(tower_id, &proof).unwrap(); -// // assert_eq!(dbm.load_misbehaving_proof(tower_id).unwrap(), proof); -// // } -// // -// // #[test] -// // fn test_store_load_non_existing_misbehaving_proof() { -// // let dbm = DBM::in_memory().unwrap(); -// // assert!(dbm.load_misbehaving_proof(get_random_user_id()).is_none()); -// // } -// // -// // #[test] -// // fn test_store_exists_misbehaving_proof() { -// // let mut dbm = DBM::in_memory().unwrap(); -// // -// // // In order to add a tower record we need to associated registration receipt. -// // let tower_id = get_random_user_id(); -// // let net_addr = "talaia.watch"; -// // -// // let receipt = get_random_registration_receipt(); -// // let tower_summary = TowerSummary::new( -// // net_addr.to_owned(), -// // receipt.available_slots(), -// // receipt.subscription_start(), -// // receipt.subscription_expiry(), -// // ); -// // dbm.store_tower_record(tower_id, net_addr, &receipt) -// // .unwrap(); -// // assert_eq!( -// // TowerSummary::from(dbm.load_tower_record(tower_id).unwrap()), -// // tower_summary -// // ); -// // -// // // // Store a misbehaving proof check -// // let appointment = generate_random_appointment(None); -// // let appointment_receipt = AppointmentReceipt::with_signature( -// // "user_signature".to_owned(), -// // 42, -// // "tower_signature".to_owned(), -// // ); -// // -// // let proof = MisbehaviorProof::new( -// // appointment.locator, -// // appointment_receipt, -// // get_random_user_id(), -// // ); -// // -// // dbm.store_misbehaving_proof(tower_id, &proof).unwrap(); -// // assert!(dbm.exists_misbehaving_proof(tower_id)); -// // } -// // -// // #[test] -// // fn test_exists_misbehaving_proof_false() { -// // let dbm = DBM::in_memory().unwrap(); -// // assert!(!dbm.exists_misbehaving_proof(get_random_user_id())); -// // } -// } +#[cfg(test)] +mod tests { + use super::*; + use crate::storage::MemoryStore; + + use teos_common::test_utils::{ + generate_random_appointment, get_random_registration_receipt, get_random_user_id, + get_registration_receipt_from_previous, + }; + + fn create_test_storage() -> KVStorage { + let store = MemoryStore::new().into_dyn_store(); + let sk = vec![0u8; 32]; // Test secret key + KVStorage::new(store, sk).unwrap() + } + + #[test] + fn test_store_load_tower_record() { + let mut storage = create_test_storage(); + + // In order to add a tower record we need to associated registration receipt. + let tower_id = get_random_user_id(); + let net_addr = "talaia.watch"; + let receipt = get_random_registration_receipt(); + + let tower_info = TowerInfo::new( + net_addr.to_owned(), + receipt.available_slots(), + receipt.subscription_start(), + receipt.subscription_expiry(), + HashMap::new(), + Vec::new(), + Vec::new(), + ); + + // Check the loaded data matches the in memory data + let serialized = bincode::serialize(&tower_info).unwrap(); + + let deserialized: TowerInfo = bincode::deserialize(&serialized).unwrap(); + assert_eq!(tower_info, deserialized); + + storage + .store_tower_record(tower_id, net_addr, &receipt) + .unwrap(); + + let loaded = storage.load_tower_record(tower_id).unwrap(); + assert_eq!(loaded, tower_info); + } + + // #[test] + // fn test_load_registration_receipt() { + // let mut dbm = DBM::in_memory().unwrap(); + // + // // Registration receipts are stored alongside tower records when the register command is called + // let tower_id = get_random_user_id(); + // let net_addr = "talaia.watch"; + // let receipt = get_random_registration_receipt(); + // + // // Check the receipt was stored + // dbm.store_tower_record(tower_id, net_addr, &receipt) + // .unwrap(); + // assert_eq!( + // dbm.load_registration_receipt(tower_id, receipt.user_id()) + // .unwrap(), + // receipt + // ); + // + // // Add another receipt for the same tower with a higher expiry and check this last one is loaded + // let middle_receipt = get_registration_receipt_from_previous(&receipt); + // let latest_receipt = get_registration_receipt_from_previous(&middle_receipt); + // + // dbm.store_tower_record(tower_id, net_addr, &latest_receipt) + // .unwrap(); + // assert_eq!( + // dbm.load_registration_receipt(tower_id, latest_receipt.user_id()) + // .unwrap(), + // latest_receipt + // ); + // + // // Add a final one with a lower expiry and check the last is still loaded + // dbm.store_tower_record(tower_id, net_addr, &middle_receipt) + // .unwrap(); + // assert_eq!( + // dbm.load_registration_receipt(tower_id, latest_receipt.user_id()) + // .unwrap(), + // latest_receipt + // ); + // } + // + // #[test] + // fn test_load_same_registration_receipt() { + // let mut dbm = DBM::in_memory().unwrap(); + // + // // Registration receipts are stored alongside tower records when the register command is called + // let tower_id = get_random_user_id(); + // let net_addr = "talaia.watch"; + // let receipt = get_random_registration_receipt(); + // + // // Store it once + // dbm.store_tower_record(tower_id, net_addr, &receipt) + // .unwrap(); + // assert_eq!( + // dbm.load_registration_receipt(tower_id, receipt.user_id()) + // .unwrap(), + // receipt + // ); + // + // // Store the same again, this should fail due to UNIQUE PK constrains. + // // Notice store_tower_record is guarded against this by WTClient::add_update_tower though. + // assert!(matches!( + // dbm.store_tower_record(tower_id, net_addr, &receipt), + // Err { .. } + // )); + // } + // + // #[test] + // fn test_load_nonexistent_tower_record() { + // let dbm = DBM::in_memory().unwrap(); + // + // // If the tower does not exists, `load_tower` will fail. + // let tower_id = get_random_user_id(); + // assert!(dbm.load_tower_record(tower_id).is_none()); + // } + // + // #[test] + // fn test_store_load_towers() { + // let mut dbm = DBM::in_memory().unwrap(); + // let mut towers = HashMap::new(); + // + // // In order to add a tower record we need to associated registration receipt. + // for _ in 0..10 { + // let tower_id = get_random_user_id(); + // let net_addr = "talaia.watch"; + // let mut receipt = get_random_registration_receipt(); + // dbm.store_tower_record(tower_id, net_addr, &receipt) + // .unwrap(); + // + // // Add not only one registration receipt to test if the tower retrieves the one with furthest expiry date. + // for _ in 0..10 { + // receipt = get_registration_receipt_from_previous(&receipt); + // dbm.store_tower_record(tower_id, net_addr, &receipt) + // .unwrap(); + // } + // + // towers.insert( + // tower_id, + // TowerSummary::new( + // net_addr.to_owned(), + // receipt.available_slots(), + // receipt.subscription_start(), + // receipt.subscription_expiry(), + // ), + // ); + // } + // + // assert_eq!(dbm.load_towers(), towers); + // } + // + // #[test] + // fn test_load_towers_empty() { + // // If there are no towers in the database, `load_towers` should return an empty map. + // let dbm = DBM::in_memory().unwrap(); + // 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(); + // + // // In order to add a tower record we need to associated registration receipt. + // let tower_id = get_random_user_id(); + // let net_addr = "talaia.watch"; + // + // let receipt = get_random_registration_receipt(); + // let mut tower_summary = TowerSummary::new( + // net_addr.to_owned(), + // receipt.available_slots(), + // receipt.subscription_start(), + // receipt.subscription_expiry(), + // ); + // dbm.store_tower_record(tower_id, net_addr, &receipt) + // .unwrap(); + // + // // Add some appointment receipts and check they match + // let mut receipts = HashMap::new(); + // for _ in 0..5 { + // let appointment = generate_random_appointment(None); + // let user_signature = "user_signature"; + // let appointment_receipt = AppointmentReceipt::with_signature( + // user_signature.to_owned(), + // 42, + // "tower_signature".to_owned(), + // ); + // + // tower_summary.available_slots -= 1; + // + // dbm.store_appointment_receipt( + // tower_id, + // appointment.locator, + // tower_summary.available_slots, + // &appointment_receipt, + // ) + // .unwrap(); + // receipts.insert( + // appointment.locator, + // appointment_receipt.signature().unwrap(), + // ); + // } + // + // 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!(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"; + // let receipt = get_random_registration_receipt(); + // dbm.store_tower_record(tower_id, net_addr, &receipt) + // .unwrap(); + // + // assert!(dbm + // .load_appointment_receipt(tower_id, appointment.locator) + // .is_none()); + // + // // Add both + // let tower_summary = TowerSummary::new( + // net_addr.to_owned(), + // receipt.available_slots(), + // receipt.subscription_start(), + // receipt.subscription_expiry(), + // ); + // let appointment_receipt = AppointmentReceipt::with_signature( + // "user_signature".to_owned(), + // 42, + // "tower_signature".to_owned(), + // ); + // 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` + // let mut dbm = DBM::in_memory().unwrap(); + // + // // We first need to add a tower record to the database so we can add some associated data. + // let tower_id = get_random_user_id(); + // let net_addr = "talaia.watch"; + // + // let receipt = get_random_registration_receipt(); + // let tower_summary = TowerSummary::new( + // net_addr.to_owned(), + // receipt.available_slots(), + // receipt.subscription_start(), + // receipt.subscription_expiry(), + // ); + // dbm.store_tower_record(tower_id, net_addr, &receipt) + // .unwrap(); + // + // // Create all types of appointments and store them in the db. + // let user_signature = "user_signature"; + // let mut receipts = HashSet::new(); + // let mut pending_appointments = HashSet::new(); + // let mut invalid_appointments = HashSet::new(); + // for _ in 0..5 { + // let appointment = generate_random_appointment(None); + // let appointment_receipt = AppointmentReceipt::with_signature( + // user_signature.to_owned(), + // 42, + // "tower_signature".to_owned(), + // ); + // let pending_appointment = generate_random_appointment(None); + // let invalid_appointment = generate_random_appointment(None); + // + // dbm.store_appointment_receipt( + // tower_id, + // appointment.locator, + // tower_summary.available_slots, + // &appointment_receipt, + // ) + // .unwrap(); + // dbm.store_pending_appointment(tower_id, &pending_appointment) + // .unwrap(); + // dbm.store_invalid_appointment(tower_id, &invalid_appointment) + // .unwrap(); + // + // receipts.insert(appointment.locator); + // pending_appointments.insert(pending_appointment.locator); + // invalid_appointments.insert(invalid_appointment.locator); + // } + // + // // Pull data from the db and check it matches the expected data + // assert_eq!( + // dbm.load_appointment_locators(tower_id, AppointmentStatus::Accepted), + // receipts + // ); + // assert_eq!( + // dbm.load_appointment_locators(tower_id, AppointmentStatus::Pending), + // pending_appointments + // ); + // assert_eq!( + // dbm.load_appointment_locators(tower_id, AppointmentStatus::Invalid), + // invalid_appointments + // ); + // } + // + // #[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!(loaded_appointment.is_none()); + // } + // + // #[test] + // fn test_store_pending_appointment() { + // let mut dbm = DBM::in_memory().unwrap(); + // + // // In order to add a tower record we need to associated registration receipt. + // let tower_id = get_random_user_id(); + // let net_addr = "talaia.watch"; + // + // let receipt = get_random_registration_receipt(); + // let mut tower_summary = TowerSummary::new( + // net_addr.to_owned(), + // receipt.available_slots(), + // receipt.subscription_start(), + // receipt.subscription_expiry(), + // ) + // .with_status(TowerStatus::TemporaryUnreachable); + // dbm.store_tower_record(tower_id, net_addr, &receipt) + // .unwrap(); + // + // // Add some pending appointments and check they match + // for _ in 0..5 { + // let appointment = generate_random_appointment(None); + // + // tower_summary + // .pending_appointments + // .insert(appointment.locator); + // + // dbm.store_pending_appointment(tower_id, &appointment) + // .unwrap(); + // assert_eq!( + // TowerSummary::from(dbm.load_tower_record(tower_id).unwrap()), + // tower_summary + // ); + // } + // } + // + // #[test] + // fn test_store_pending_appointment_twice() { + // let mut dbm = DBM::in_memory().unwrap(); + // + // // In order to add a tower record we need to associated registration receipt. + // let tower_id_1 = get_random_user_id(); + // let tower_id_2 = get_random_user_id(); + // let net_addr = "talaia.watch"; + // + // let receipt = get_random_registration_receipt(); + // dbm.store_tower_record(tower_id_1, net_addr, &receipt) + // .unwrap(); + // dbm.store_tower_record(tower_id_2, net_addr, &receipt) + // .unwrap(); + // + // // If the same appointment is stored twice (by different towers) it should go through + // // Since the appointment data will be stored only once and this will create two references + // let appointment = generate_random_appointment(None); + // dbm.store_pending_appointment(tower_id_1, &appointment) + // .unwrap(); + // dbm.store_pending_appointment(tower_id_2, &appointment) + // .unwrap(); + // + // // If this is called twice with for the same tower it will fail, since two identical references + // // can not exist. This is intended behavior and should not happen + // assert!(dbm + // .store_pending_appointment(tower_id_2, &appointment) + // .is_err()); + // } + // + // #[test] + // fn test_delete_pending_appointment() { + // let mut dbm = DBM::in_memory().unwrap(); + // + // // In order to add a tower record we need to associated registration receipt. + // 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(); + // + // // Add a single one, remove it later + // let appointment = generate_random_appointment(None); + // dbm.store_pending_appointment(tower_id, &appointment) + // .unwrap(); + // assert!(dbm + // .delete_pending_appointment(tower_id, appointment.locator) + // .is_ok()); + // + // // The appointment should be completely gone + // assert!(!dbm + // .load_appointment_locators(tower_id, AppointmentStatus::Pending) + // .contains(&appointment.locator)); + // // assert!(!dbm.appointment_exists(appointment.locator)); + // + // // Try again with more than one reference + // let another_tower_id = get_random_user_id(); + // dbm.store_tower_record(another_tower_id, net_addr, &receipt) + // .unwrap(); + // + // // Add two + // dbm.store_pending_appointment(tower_id, &appointment) + // .unwrap(); + // dbm.store_pending_appointment(another_tower_id, &appointment) + // .unwrap(); + // // Delete one + // assert!(dbm + // .delete_pending_appointment(tower_id, appointment.locator) + // .is_ok()); + // // Check + // assert!(!dbm + // .load_appointment_locators(tower_id, AppointmentStatus::Pending) + // .contains(&appointment.locator)); + // assert!(dbm + // .load_appointment_locators(another_tower_id, AppointmentStatus::Pending) + // .contains(&appointment.locator)); + // // assert!(dbm.appointment_exists(appointment.locator)); + // + // // Add an invalid reference and check again + // dbm.store_invalid_appointment(tower_id, &appointment) + // .unwrap(); + // assert!(dbm + // .delete_pending_appointment(another_tower_id, appointment.locator) + // .is_ok()); + // assert!(!dbm + // .load_appointment_locators(another_tower_id, AppointmentStatus::Pending) + // .contains(&appointment.locator)); + // assert!(dbm + // .load_appointment_locators(tower_id, AppointmentStatus::Invalid) + // .contains(&appointment.locator)); + // // assert!(dbm.appointment_exists(appointment.locator)); + // } + // + // #[test] + // fn test_store_invalid_appointment() { + // let mut dbm = DBM::in_memory().unwrap(); + // + // // In order to add a tower record we need to associated registration receipt. + // let tower_id = get_random_user_id(); + // let net_addr = "talaia.watch"; + // + // let receipt = get_random_registration_receipt(); + // let mut tower_summary = TowerSummary::new( + // net_addr.to_owned(), + // receipt.available_slots(), + // receipt.subscription_start(), + // receipt.subscription_expiry(), + // ); + // dbm.store_tower_record(tower_id, net_addr, &receipt) + // .unwrap(); + // + // // Add some invalid appointments and check they match + // for _ in 0..5 { + // let appointment = generate_random_appointment(None); + // + // tower_summary + // .invalid_appointments + // .insert(appointment.locator); + // + // dbm.store_invalid_appointment(tower_id, &appointment) + // .unwrap(); + // assert_eq!( + // TowerSummary::from(dbm.load_tower_record(tower_id).unwrap()), + // tower_summary + // ); + // } + // } + // + // #[test] + // fn test_store_invalid_appointment_twice() { + // let mut dbm = DBM::in_memory().unwrap(); + // + // // In order to add a tower record we need to associated registration receipt. + // let tower_id_1 = get_random_user_id(); + // let tower_id_2 = get_random_user_id(); + // let net_addr = "talaia.watch"; + // + // let receipt = get_random_registration_receipt(); + // dbm.store_tower_record(tower_id_1, net_addr, &receipt) + // .unwrap(); + // dbm.store_tower_record(tower_id_2, net_addr, &receipt) + // .unwrap(); + // + // // Same as with pending appointments. Two references from different towers is allowed + // let appointment = generate_random_appointment(None); + // dbm.store_invalid_appointment(tower_id_1, &appointment) + // .unwrap(); + // dbm.store_invalid_appointment(tower_id_2, &appointment) + // .unwrap(); + // + // // Two references from the same tower is not. + // assert!(dbm + // .store_invalid_appointment(tower_id_2, &appointment) + // .is_err()); + // } + // + // #[test] + // fn test_store_load_misbehaving_proof() { + // let mut dbm = DBM::in_memory().unwrap(); + // + // // In order to add a tower record we need to associated registration receipt. + // let tower_id = get_random_user_id(); + // let net_addr = "talaia.watch"; + // + // let receipt = get_random_registration_receipt(); + // let tower_summary = TowerSummary::new( + // net_addr.to_owned(), + // receipt.available_slots(), + // receipt.subscription_start(), + // receipt.subscription_expiry(), + // ); + // dbm.store_tower_record(tower_id, net_addr, &receipt) + // .unwrap(); + // assert_eq!( + // TowerSummary::from(dbm.load_tower_record(tower_id).unwrap()), + // tower_summary + // ); + // + // // Store a misbehaving proof and load it back + // let appointment = generate_random_appointment(None); + // let appointment_receipt = AppointmentReceipt::with_signature( + // "user_signature".to_owned(), + // 42, + // "tower_signature".to_owned(), + // ); + // + // let proof = MisbehaviorProof::new( + // appointment.locator, + // appointment_receipt, + // get_random_user_id(), + // ); + // + // dbm.store_misbehaving_proof(tower_id, &proof).unwrap(); + // assert_eq!(dbm.load_misbehaving_proof(tower_id).unwrap(), proof); + // } + // + // #[test] + // fn test_store_load_non_existing_misbehaving_proof() { + // let dbm = DBM::in_memory().unwrap(); + // assert!(dbm.load_misbehaving_proof(get_random_user_id()).is_none()); + // } + // + // #[test] + // fn test_store_exists_misbehaving_proof() { + // let mut dbm = DBM::in_memory().unwrap(); + // + // // In order to add a tower record we need to associated registration receipt. + // let tower_id = get_random_user_id(); + // let net_addr = "talaia.watch"; + // + // let receipt = get_random_registration_receipt(); + // let tower_summary = TowerSummary::new( + // net_addr.to_owned(), + // receipt.available_slots(), + // receipt.subscription_start(), + // receipt.subscription_expiry(), + // ); + // dbm.store_tower_record(tower_id, net_addr, &receipt) + // .unwrap(); + // assert_eq!( + // TowerSummary::from(dbm.load_tower_record(tower_id).unwrap()), + // tower_summary + // ); + // + // // // Store a misbehaving proof check + // let appointment = generate_random_appointment(None); + // let appointment_receipt = AppointmentReceipt::with_signature( + // "user_signature".to_owned(), + // 42, + // "tower_signature".to_owned(), + // ); + // + // let proof = MisbehaviorProof::new( + // appointment.locator, + // appointment_receipt, + // get_random_user_id(), + // ); + // + // dbm.store_misbehaving_proof(tower_id, &proof).unwrap(); + // assert!(dbm.exists_misbehaving_proof(tower_id)); + // } + // + // #[test] + // fn test_exists_misbehaving_proof_false() { + // let dbm = DBM::in_memory().unwrap(); + // assert!(!dbm.exists_misbehaving_proof(get_random_user_id())); + // } +} From 9d56f29ebd7bc12897e94efb981ce1a096110120 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Thu, 13 Feb 2025 15:45:22 +0100 Subject: [PATCH 55/85] kv crud tower_info Signed-off-by: dzdidi --- teos-ldk-client/src/lib.rs | 1 - teos-ldk-client/src/storage/kv.rs | 176 +++++++++++--------- teos-ldk-client/src/storage/memory_store.rs | 8 +- 3 files changed, 98 insertions(+), 87 deletions(-) diff --git a/teos-ldk-client/src/lib.rs b/teos-ldk-client/src/lib.rs index d1a74c64..ebc4191e 100644 --- a/teos-ldk-client/src/lib.rs +++ b/teos-ldk-client/src/lib.rs @@ -1,6 +1,5 @@ use std::collections::{HashMap, HashSet}; use std::fmt; -use std::io; use serde::{Deserialize, Serialize}; diff --git a/teos-ldk-client/src/storage/kv.rs b/teos-ldk-client/src/storage/kv.rs index 849f21e0..8f08f736 100644 --- a/teos-ldk-client/src/storage/kv.rs +++ b/teos-ldk-client/src/storage/kv.rs @@ -1,7 +1,5 @@ use std::collections::{HashMap, HashSet}; -use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce}; // use chacha20poly1305::aead::{Aead}; -use bitcoin::hashes::{sha256, Hash}; use std::sync::Arc; use crate::storage::persister::{Persister, PersisterError}; @@ -101,14 +99,15 @@ impl Persister for KVStorage { Vec::new(), Vec::new(), ); - + let value = bincode::serialize(&tower_info) .map_err(|e| PersisterError::Other(format!("Serialization error: {}", e)))?; // let encrypted = encrypt(&value, &self.sk).unwrap(); self.store - .write(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key, &value).map_err(|e| PersisterError::StoreError(e.to_string())) + .write(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key, &value) + .map_err(|e| PersisterError::StoreError(e.to_string())) } /// Loads a tower record from the database. @@ -124,12 +123,9 @@ impl Persister for KVStorage { }; // let decrypted = decrypt(&value, &self.sk).unwrap(); - let tower_info: TowerInfo = bincode::deserialize(&value) - .map_err(|e| { - e - }) - .ok()?; + let tower_info: TowerInfo = bincode::deserialize(&value).ok()?; + // TODO: // tower_info.appointments = self.load_appointment_receipts(tower_id); // tower_info.pending_appointments = // self.load_appointments(tower_id, AppointmentStatus::Pending); @@ -144,19 +140,27 @@ impl Persister for KVStorage { /// 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. fn remove_tower_record(&self, tower_id: TowerId) -> Result<(), PersisterError> { - todo!(); - // let key = make_key(&[&tower_id.to_string()]); - // // primary namespace: "watchtower" - // // secondary namespance: "tower_record" - // // key: - // // value: ? - // self.store - // .remove(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key, true) + let key = make_key(&[&tower_id.to_string()]); + self.store + .remove(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key, true) + .map_err(|e| PersisterError::NotFound(format!("tower_id: {tower_id}"))) } /// Loads all tower records from the database. fn load_towers(&self) -> HashMap { - todo!() + let mut towers = HashMap::new(); + let keys = self + .store + .list(PRIMARY_NAMESPACE, NS_TOWER_RECORDS) + .unwrap(); + + for key in keys { + let tower_id = key.split(":").next().unwrap().parse().unwrap(); + let tower_info = self.load_tower_record(tower_id).unwrap(); + towers.insert(tower_id, TowerSummary::from(tower_info)); + } + + towers } /// Loads the latest registration receipt for a given tower. @@ -406,7 +410,7 @@ mod tests { use crate::storage::MemoryStore; use teos_common::test_utils::{ - generate_random_appointment, get_random_registration_receipt, get_random_user_id, + get_random_registration_receipt, get_random_user_id, get_registration_receipt_from_previous, }; @@ -523,70 +527,76 @@ mod tests { // let tower_id = get_random_user_id(); // assert!(dbm.load_tower_record(tower_id).is_none()); // } - // - // #[test] - // fn test_store_load_towers() { - // let mut dbm = DBM::in_memory().unwrap(); - // let mut towers = HashMap::new(); - // - // // In order to add a tower record we need to associated registration receipt. - // for _ in 0..10 { - // let tower_id = get_random_user_id(); - // let net_addr = "talaia.watch"; - // let mut receipt = get_random_registration_receipt(); - // dbm.store_tower_record(tower_id, net_addr, &receipt) - // .unwrap(); - // - // // Add not only one registration receipt to test if the tower retrieves the one with furthest expiry date. - // for _ in 0..10 { - // receipt = get_registration_receipt_from_previous(&receipt); - // dbm.store_tower_record(tower_id, net_addr, &receipt) - // .unwrap(); - // } - // - // towers.insert( - // tower_id, - // TowerSummary::new( - // net_addr.to_owned(), - // receipt.available_slots(), - // receipt.subscription_start(), - // receipt.subscription_expiry(), - // ), - // ); - // } - // - // assert_eq!(dbm.load_towers(), towers); - // } - // - // #[test] - // fn test_load_towers_empty() { - // // If there are no towers in the database, `load_towers` should return an empty map. - // let dbm = DBM::in_memory().unwrap(); - // 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_towers() { + let mut storage = create_test_storage(); + let mut towers = HashMap::new(); + + // In order to add a tower record we need to associated registration receipt. + for _ in 0..10 { + let tower_id = get_random_user_id(); + let net_addr = "talaia.watch"; + let mut receipt = get_random_registration_receipt(); + storage + .store_tower_record(tower_id, net_addr, &receipt) + .unwrap(); + + // Add not only one registration receipt to test if the tower retrieves the one with furthest expiry date. + for _ in 0..10 { + receipt = get_registration_receipt_from_previous(&receipt); + storage + .store_tower_record(tower_id, net_addr, &receipt) + .unwrap(); + } + + towers.insert( + tower_id, + TowerSummary::new( + net_addr.to_owned(), + receipt.available_slots(), + receipt.subscription_start(), + receipt.subscription_expiry(), + ), + ); + } + + assert_eq!(storage.load_towers(), towers); + } + + #[test] + fn test_load_towers_empty() { + // If there are no towers in the database, `load_towers` should return an empty map. + let storage = create_test_storage(); + assert_eq!(storage.load_towers(), HashMap::new()); + } + + #[test] + fn test_remove_tower_record() { + let mut storage = create_test_storage(); + + let tower_id = get_random_user_id(); + let net_addr = "talaia.watch"; + let receipt = get_random_registration_receipt(); + storage + .store_tower_record(tower_id, net_addr, &receipt) + .unwrap(); + + assert!(matches!(storage.remove_tower_record(tower_id), Ok(()))); + assert_eq!(storage.load_towers(), HashMap::new()); + } + + #[test] + fn test_remove_tower_record_inexistent() { + let storage = create_test_storage(); + let tower_id = get_random_user_id(); + let err = storage.remove_tower_record(tower_id).unwrap_err(); + assert_eq!( + err, + PersisterError::NotFound(format!("tower_id: {tower_id}")) + ); + } + // #[test] // fn test_store_load_appointment_receipts() { // let mut dbm = DBM::in_memory().unwrap(); diff --git a/teos-ldk-client/src/storage/memory_store.rs b/teos-ldk-client/src/storage/memory_store.rs index 0c7e2590..cc8766e9 100644 --- a/teos-ldk-client/src/storage/memory_store.rs +++ b/teos-ldk-client/src/storage/memory_store.rs @@ -110,10 +110,12 @@ impl KVStore for MemoryStore { let namespace = Self::make_key(primary_namespace, secondary_namespace); let res = data .get(&namespace) - .map(|ns_map| ns_map.keys().cloned().collect()) - .unwrap(); + .map(|ns_map| ns_map.keys().cloned().collect()); - Ok(res) + match res { + None => Ok(Vec::new()), + Some(res) => Ok(res), + } } } From 8ddc11f3ca05b2b838fa843c0b8aa762d8cb2869 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Thu, 13 Feb 2025 16:35:57 +0100 Subject: [PATCH 56/85] store registration recepies with tower info Signed-off-by: dzdidi --- teos-common/src/receipts.rs | 2 +- teos-ldk-client/src/storage/kv.rs | 94 +++++++++++++++++-------------- 2 files changed, 53 insertions(+), 43 deletions(-) diff --git a/teos-common/src/receipts.rs b/teos-common/src/receipts.rs index 303ec9e4..fb93200d 100644 --- a/teos-common/src/receipts.rs +++ b/teos-common/src/receipts.rs @@ -19,7 +19,7 @@ use crate::{cryptography, UserId}; /// 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 /// before the tower wipes their data, then the tower can create a new receipt with (S, E') for E' > E instead of a second receipt (E, E'). // Notice this only applies as long as there is no gap between the two subscriptions. -#[derive(Serialize, Debug, Eq, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] pub struct RegistrationReceipt { user_id: UserId, available_slots: u32, diff --git a/teos-ldk-client/src/storage/kv.rs b/teos-ldk-client/src/storage/kv.rs index 8f08f736..6413c0b5 100644 --- a/teos-ldk-client/src/storage/kv.rs +++ b/teos-ldk-client/src/storage/kv.rs @@ -100,14 +100,33 @@ impl Persister for KVStorage { Vec::new(), ); - let value = bincode::serialize(&tower_info) + let tower_info = bincode::serialize(&tower_info) .map_err(|e| PersisterError::Other(format!("Serialization error: {}", e)))?; - // let encrypted = encrypt(&value, &self.sk).unwrap(); + let encrypted = encrypt(&tower_info, &self.sk).unwrap(); self.store - .write(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key, &value) + .write(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key, &encrypted) .map_err(|e| PersisterError::StoreError(e.to_string())) + .unwrap(); + + let registration_receipt = bincode::serialize(&receipt) + .map_err(|e| PersisterError::Other(format!("Serialization error: {}", e)))?; + + let encrypted = encrypt(®istration_receipt, &self.sk).unwrap(); + + match self.store.write( + PRIMARY_NAMESPACE, + NS_REGISTRATION_RECEIPTS, + &key, + &encrypted, + ) { + Ok(_) => Ok(()), + Err(e) => { + self.remove_tower_record(tower_id).unwrap(); + Err(PersisterError::StoreError(e.to_string())) + } + } } /// Loads a tower record from the database. @@ -122,8 +141,8 @@ impl Persister for KVStorage { Err(_) => return None, }; - // let decrypted = decrypt(&value, &self.sk).unwrap(); - let tower_info: TowerInfo = bincode::deserialize(&value).ok()?; + let decrypted = decrypt(&value, &self.sk).unwrap(); + let tower_info: TowerInfo = bincode::deserialize(&decrypted).ok()?; // TODO: // tower_info.appointments = self.load_appointment_receipts(tower_id); @@ -147,6 +166,8 @@ impl Persister for KVStorage { } /// Loads all tower records from the database. + /// + /// Returns a key value pair with the tower id as key and the tower summary as value. fn load_towers(&self) -> HashMap { let mut towers = HashMap::new(); let keys = self @@ -171,11 +192,26 @@ impl Persister for KVStorage { tower_id: TowerId, user_id: UserId, ) -> Option { - // let key = make_key(&[&tower_id.to_string()]); - // - // self.store - // .read(PRIMARY_NAMESPACE, NS_REGISTRATION_RECEIPTS, &key); - todo!(); + let key = make_key(&[&tower_id.to_string()]); + + match self + .store + .read(PRIMARY_NAMESPACE, NS_REGISTRATION_RECEIPTS, &key) + { + Ok(value) => { + let decrypted = decrypt(&value, &self.sk).unwrap(); + let registration_receipt: RegistrationReceipt = + bincode::deserialize(&decrypted).unwrap(); + Some(RegistrationReceipt::with_signature( + user_id, + registration_receipt.available_slots(), + registration_receipt.subscription_start(), + registration_receipt.subscription_expiry(), + registration_receipt.signature().unwrap(), + )) + } + Err(_) => None, + } } /// Stores an appointments receipt into the database representing an appointment accepted by a given tower. @@ -242,19 +278,6 @@ impl Persister for KVStorage { todo!(); } - // /// Stores an appointment into the database. - // /// - // /// Appointments are only stored as a whole when they are pending or invalid. - // /// Accepted appointments are simplified in the form of an appointment receipt. - // fn store_appointment(&self, appointment: &Appointment) -> Result { - // let key = make_key(&[&appointment.locator.to_string()]); - // // primary namespace: "watchtower" - // // secondary namespance: "appointment" - // // key: - // // value: appointment.encrypted_blob() - // todo!(); - // } - // /// Stores a pending appointment into the database. // /// // /// A pending appointment is an appointment that was sent to a tower when it was unreachable. @@ -337,23 +360,6 @@ impl Persister for KVStorage { todo!(); } - // /// Loads the misbehaving proof for a given tower from the database (if found). - // fn load_misbehaving_proof(&self, tower_id: TowerId) -> Option { - // let key = make_key(&[&tower_id.to_string()]); - // // primary namespace: "watchtower" - // // secondary namespance: "misbehaving_proof" - // // key: ? - // todo!(); - // } - - // /// Checks whether a misbehaving proof exists for a given tower. - // fn exists_misbehaving_proof(&self, tower_id: TowerId) -> bool { - // let key = make_key(&[&tower_id.to_string()]); - // // primary namespace: "watchtower" - // // secondary namespance: "misbehaving_proof" - // // key: ? - // todo!(); - // } fn appointment_exists(&self, locator: Locator) -> bool { todo!(); } @@ -410,8 +416,7 @@ mod tests { use crate::storage::MemoryStore; use teos_common::test_utils::{ - get_random_registration_receipt, get_random_user_id, - get_registration_receipt_from_previous, + get_random_registration_receipt, get_random_user_id, get_registration_receipt_from_previous, }; fn create_test_storage() -> KVStorage { @@ -451,6 +456,11 @@ mod tests { let loaded = storage.load_tower_record(tower_id).unwrap(); assert_eq!(loaded, tower_info); + + let loaded_receipt = storage + .load_registration_receipt(tower_id, receipt.user_id()) + .unwrap(); + assert_eq!(loaded_receipt, receipt); } // #[test] From a3f01299b7883785aece66aca01cbbf6c6bf165f Mon Sep 17 00:00:00 2001 From: dzdidi Date: Thu, 13 Feb 2025 18:16:00 +0100 Subject: [PATCH 57/85] update tower record only if registration receip is newer Signed-off-by: dzdidi --- teos-ldk-client/src/storage/kv.rs | 212 ++++++++++++++++++------------ 1 file changed, 131 insertions(+), 81 deletions(-) diff --git a/teos-ldk-client/src/storage/kv.rs b/teos-ldk-client/src/storage/kv.rs index 6413c0b5..6d2484f6 100644 --- a/teos-ldk-client/src/storage/kv.rs +++ b/teos-ldk-client/src/storage/kv.rs @@ -40,6 +40,7 @@ const NS_APPOINTMENT_RECEIPTS: &str = "appointment_receipts"; const NS_PENDING_APPOINTMENTS: &str = "pending_appointments"; const NS_INVALID_APPOINTMENTS: &str = "invalid_appointments"; const NS_MISBEHAVIOR_PROOFS: &str = "misbehavior_proofs"; +const NS_AVAILABLE_SLOTS: &str = "available_slots"; pub type DynStore = dyn KVStore + Sync + Send; @@ -90,6 +91,14 @@ impl Persister for KVStorage { ) -> Result<(), PersisterError> { let key = make_key(&[&tower_id.to_string()]); + if let Ok(existing) = self.store.read(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key) { + let decrypted = decrypt(&existing, &self.sk).unwrap(); + let existing_tower_info: TowerInfo = bincode::deserialize(&decrypted).unwrap(); + if existing_tower_info.subscription_expiry > receipt.subscription_expiry() { + return Ok(()); + } + }; + let tower_info = TowerInfo::new( net_addr.to_string(), receipt.available_slots(), @@ -115,6 +124,16 @@ impl Persister for KVStorage { let encrypted = encrypt(®istration_receipt, &self.sk).unwrap(); + self.store + .write( + PRIMARY_NAMESPACE, + NS_AVAILABLE_SLOTS, + &key, + receipt.available_slots().to_be_bytes().as_ref(), + ) + .map_err(|e| PersisterError::StoreError(e.to_string())) + .unwrap(); + match self.store.write( PRIMARY_NAMESPACE, NS_REGISTRATION_RECEIPTS, @@ -142,7 +161,7 @@ impl Persister for KVStorage { }; let decrypted = decrypt(&value, &self.sk).unwrap(); - let tower_info: TowerInfo = bincode::deserialize(&decrypted).ok()?; + let mut tower_info: TowerInfo = bincode::deserialize(&decrypted).ok()?; // TODO: // tower_info.appointments = self.load_appointment_receipts(tower_id); @@ -150,6 +169,15 @@ impl Persister for KVStorage { // self.load_appointments(tower_id, AppointmentStatus::Pending); // tower_info.invalid_appointments = // self.load_appointments(tower_id, AppointmentStatus::Invalid); + tower_info.available_slots = + match self.store.read(PRIMARY_NAMESPACE, NS_AVAILABLE_SLOTS, &key) { + Ok(bytes) if bytes.len() >= 4 => { + let mut buf = [0u8; 4]; + buf.copy_from_slice(&bytes[0..4]); + u32::from_be_bytes(buf) + } + _ => 0, + }; Some(tower_info) } @@ -223,12 +251,25 @@ impl Persister for KVStorage { receipt: &AppointmentReceipt, ) -> Result<(), PersisterError> { let key = make_key(&[&tower_id.to_string(), &locator.to_string()]); - // primary namespace: "watchtower" - // secondary namespance: "appointment_receipt" - // key: + - // value: ? - // self.store.write(PRIMARY_NAMESPACE, NS_APPOINTMENT_RECEIPTS, &key, &value); - todo!(); + let encrypted = encrypt(&bincode::serialize(receipt).unwrap(), &self.sk).unwrap(); + + self.store + .write(PRIMARY_NAMESPACE, NS_APPOINTMENT_RECEIPTS, &key, &encrypted) + .map_err(|e| PersisterError::StoreError(e.to_string())) + .unwrap(); + + // Update the tower's available_slots + self.store + .write( + PRIMARY_NAMESPACE, + NS_AVAILABLE_SLOTS, + &key, + available_slots.to_be_bytes().as_ref(), + ) + .map_err(|e| PersisterError::StoreError(e.to_string())) + .unwrap(); + + Ok(()) } /// Loads a given appointment receipt of a given tower from the database. @@ -463,80 +504,89 @@ mod tests { assert_eq!(loaded_receipt, receipt); } - // #[test] - // fn test_load_registration_receipt() { - // let mut dbm = DBM::in_memory().unwrap(); - // - // // Registration receipts are stored alongside tower records when the register command is called - // let tower_id = get_random_user_id(); - // let net_addr = "talaia.watch"; - // let receipt = get_random_registration_receipt(); - // - // // Check the receipt was stored - // dbm.store_tower_record(tower_id, net_addr, &receipt) - // .unwrap(); - // assert_eq!( - // dbm.load_registration_receipt(tower_id, receipt.user_id()) - // .unwrap(), - // receipt - // ); - // - // // Add another receipt for the same tower with a higher expiry and check this last one is loaded - // let middle_receipt = get_registration_receipt_from_previous(&receipt); - // let latest_receipt = get_registration_receipt_from_previous(&middle_receipt); - // - // dbm.store_tower_record(tower_id, net_addr, &latest_receipt) - // .unwrap(); - // assert_eq!( - // dbm.load_registration_receipt(tower_id, latest_receipt.user_id()) - // .unwrap(), - // latest_receipt - // ); - // - // // Add a final one with a lower expiry and check the last is still loaded - // dbm.store_tower_record(tower_id, net_addr, &middle_receipt) - // .unwrap(); - // assert_eq!( - // dbm.load_registration_receipt(tower_id, latest_receipt.user_id()) - // .unwrap(), - // latest_receipt - // ); - // } - // - // #[test] - // fn test_load_same_registration_receipt() { - // let mut dbm = DBM::in_memory().unwrap(); - // - // // Registration receipts are stored alongside tower records when the register command is called - // let tower_id = get_random_user_id(); - // let net_addr = "talaia.watch"; - // let receipt = get_random_registration_receipt(); - // - // // Store it once - // dbm.store_tower_record(tower_id, net_addr, &receipt) - // .unwrap(); - // assert_eq!( - // dbm.load_registration_receipt(tower_id, receipt.user_id()) - // .unwrap(), - // receipt - // ); - // - // // Store the same again, this should fail due to UNIQUE PK constrains. - // // Notice store_tower_record is guarded against this by WTClient::add_update_tower though. - // assert!(matches!( - // dbm.store_tower_record(tower_id, net_addr, &receipt), - // Err { .. } - // )); - // } - // - // #[test] - // fn test_load_nonexistent_tower_record() { - // let dbm = DBM::in_memory().unwrap(); - // - // // If the tower does not exists, `load_tower` will fail. - // let tower_id = get_random_user_id(); - // assert!(dbm.load_tower_record(tower_id).is_none()); - // } + #[test] + fn test_load_registration_receipt() { + let mut storage = create_test_storage(); + + // Registration receipts are stored alongside tower records when the register command is called + let tower_id = get_random_user_id(); + let net_addr = "talaia.watch"; + let receipt = get_random_registration_receipt(); + + // Check the receipt was stored + storage + .store_tower_record(tower_id, net_addr, &receipt) + .unwrap(); + assert_eq!( + storage + .load_registration_receipt(tower_id, receipt.user_id()) + .unwrap(), + receipt + ); + + // Add another receipt for the same tower with a higher expiry and check this last one is loaded + let middle_receipt = get_registration_receipt_from_previous(&receipt); + let latest_receipt = get_registration_receipt_from_previous(&middle_receipt); + + storage + .store_tower_record(tower_id, net_addr, &latest_receipt) + .unwrap(); + assert_eq!( + storage + .load_registration_receipt(tower_id, latest_receipt.user_id()) + .unwrap(), + latest_receipt + ); + + // Add a final one with a lower expiry and check the last is still loaded + storage + .store_tower_record(tower_id, net_addr, &middle_receipt) + .unwrap(); + assert_eq!( + storage + .load_registration_receipt(tower_id, latest_receipt.user_id()) + .unwrap(), + latest_receipt + ); + } + + #[test] + fn test_load_same_registration_receipt() { + let mut storage = create_test_storage(); + + // Registration receipts are stored alongside tower records when the register command is called + let tower_id = get_random_user_id(); + let net_addr = "talaia.watch"; + let receipt = get_random_registration_receipt(); + + // Store it once + storage + .store_tower_record(tower_id, net_addr, &receipt) + .unwrap(); + assert_eq!( + storage + .load_registration_receipt(tower_id, receipt.user_id()) + .unwrap(), + receipt + ); + + // // Store the same again, this should fail due to UNIQUE PK constrains. + // // Notice store_tower_record is guarded against this by WTClient::add_update_tower though. + // let err = storage.store_tower_record(tower_id, net_addr, &receipt).unwrap_err(); + // assert_eq!( + // err, + // PersisterError::StoreError(format!("tower_id: {tower_id} already exists")) + // ); + } + + #[test] + fn test_load_nonexistent_tower_record() { + let storage = create_test_storage(); + + // If the tower does not exists, `load_tower` will fail. + let tower_id = get_random_user_id(); + assert!(storage.load_tower_record(tower_id).is_none()); + } #[test] fn test_store_load_towers() { From ca758355b1299b94503690d827488b3f629b4b85 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Thu, 13 Feb 2025 19:14:37 +0100 Subject: [PATCH 58/85] crud appointment locators Signed-off-by: dzdidi --- teos-ldk-client/src/storage/kv.rs | 256 +++++++++++++++++------------- 1 file changed, 146 insertions(+), 110 deletions(-) diff --git a/teos-ldk-client/src/storage/kv.rs b/teos-ldk-client/src/storage/kv.rs index 6d2484f6..8d682078 100644 --- a/teos-ldk-client/src/storage/kv.rs +++ b/teos-ldk-client/src/storage/kv.rs @@ -250,20 +250,23 @@ impl Persister for KVStorage { available_slots: u32, receipt: &AppointmentReceipt, ) -> Result<(), PersisterError> { - let key = make_key(&[&tower_id.to_string(), &locator.to_string()]); + let tower_namespace = format!("{}:{}", NS_APPOINTMENT_RECEIPTS, tower_id); + let key = locator.to_string(); + let encrypted = encrypt(&bincode::serialize(receipt).unwrap(), &self.sk).unwrap(); self.store - .write(PRIMARY_NAMESPACE, NS_APPOINTMENT_RECEIPTS, &key, &encrypted) + .write(PRIMARY_NAMESPACE, &tower_namespace, &key, &encrypted) .map_err(|e| PersisterError::StoreError(e.to_string())) .unwrap(); // Update the tower's available_slots + let slots_key = make_key(&[&tower_id.to_string()]); self.store .write( PRIMARY_NAMESPACE, NS_AVAILABLE_SLOTS, - &key, + &slots_key, available_slots.to_be_bytes().as_ref(), ) .map_err(|e| PersisterError::StoreError(e.to_string())) @@ -278,12 +281,16 @@ impl Persister for KVStorage { tower_id: TowerId, locator: Locator, ) -> Option { - // let key = make_key(&[&tower_id.to_string(), &locator.to_string()]); - // // primary namespace: "watchtower" - // // secondary namespance: "appointment_receipt" - // self.store - // .read(PRIMARY_NAMESPACE, NS_APPOINTMENT_RECEIPTS, &key) - todo!(); + let tower_namespace = format!("{}:{}", NS_APPOINTMENT_RECEIPTS, tower_id); + let key = locator.to_string(); + + match self.store.read(PRIMARY_NAMESPACE, &tower_namespace, &key) { + Ok(value) => { + let decrypted = decrypt(&value, &self.sk).unwrap(); + Some(bincode::deserialize(&decrypted).unwrap()) + } + Err(_) => None, + } } /// Loads the appointment receipts associated to a given tower. @@ -291,10 +298,33 @@ impl Persister for KVStorage { /// 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. fn load_appointment_receipts(&self, tower_id: TowerId) -> HashMap { - // primary namespace: "watchtower" - // secondary namespance: "appointment_receipt" - // filter by tower_id - todo!(); + let mut receipts = HashMap::new(); + let tower_namespace = format!("{}:{}", NS_APPOINTMENT_RECEIPTS, tower_id); + + let keys = self + .store + .list(PRIMARY_NAMESPACE, &tower_namespace) + .unwrap(); + + // Get all keys in the tower-specific namespace + for key in keys { + // Key is just the locator string + let hex_encoded_string = key; + let locator = + match Locator::from_slice(hex::decode(&hex_encoded_string).unwrap().as_slice()) { + Ok(l) => l, + Err(s) => { + panic!("Error deserializing locator: {}", s); + } + }; + // Try to read and decrypt the receipt + let receipt = self.load_appointment_receipt(tower_id, locator).unwrap(); + if let Some(signature) = receipt.signature() { + receipts.insert(locator, signature); + } + } + + receipts } /// Loads a collection of locators from the database entry associated to a given tower. @@ -457,7 +487,8 @@ mod tests { use crate::storage::MemoryStore; use teos_common::test_utils::{ - get_random_registration_receipt, get_random_user_id, get_registration_receipt_from_previous, + generate_random_appointment, get_random_registration_receipt, get_random_user_id, + get_registration_receipt_from_previous, }; fn create_test_storage() -> KVStorage { @@ -657,102 +688,107 @@ mod tests { ); } - // #[test] - // fn test_store_load_appointment_receipts() { - // let mut dbm = DBM::in_memory().unwrap(); - // - // // In order to add a tower record we need to associated registration receipt. - // let tower_id = get_random_user_id(); - // let net_addr = "talaia.watch"; - // - // let receipt = get_random_registration_receipt(); - // let mut tower_summary = TowerSummary::new( - // net_addr.to_owned(), - // receipt.available_slots(), - // receipt.subscription_start(), - // receipt.subscription_expiry(), - // ); - // dbm.store_tower_record(tower_id, net_addr, &receipt) - // .unwrap(); - // - // // Add some appointment receipts and check they match - // let mut receipts = HashMap::new(); - // for _ in 0..5 { - // let appointment = generate_random_appointment(None); - // let user_signature = "user_signature"; - // let appointment_receipt = AppointmentReceipt::with_signature( - // user_signature.to_owned(), - // 42, - // "tower_signature".to_owned(), - // ); - // - // tower_summary.available_slots -= 1; - // - // dbm.store_appointment_receipt( - // tower_id, - // appointment.locator, - // tower_summary.available_slots, - // &appointment_receipt, - // ) - // .unwrap(); - // receipts.insert( - // appointment.locator, - // appointment_receipt.signature().unwrap(), - // ); - // } - // - // 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!(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"; - // let receipt = get_random_registration_receipt(); - // dbm.store_tower_record(tower_id, net_addr, &receipt) - // .unwrap(); - // - // assert!(dbm - // .load_appointment_receipt(tower_id, appointment.locator) - // .is_none()); - // - // // Add both - // let tower_summary = TowerSummary::new( - // net_addr.to_owned(), - // receipt.available_slots(), - // receipt.subscription_start(), - // receipt.subscription_expiry(), - // ); - // let appointment_receipt = AppointmentReceipt::with_signature( - // "user_signature".to_owned(), - // 42, - // "tower_signature".to_owned(), - // ); - // 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_store_load_appointment_receipts() { + let mut storage = create_test_storage(); + + // In order to add a tower record we need to associated registration receipt. + let tower_id = get_random_user_id(); + let net_addr = "talaia.watch"; + + let receipt = get_random_registration_receipt(); + let mut tower_summary = TowerSummary::new( + net_addr.to_owned(), + receipt.available_slots(), + receipt.subscription_start(), + receipt.subscription_expiry(), + ); + storage + .store_tower_record(tower_id, net_addr, &receipt) + .unwrap(); + + // Add some appointment receipts and check they match + let mut receipts = HashMap::new(); + for _ in 0..5 { + let appointment = generate_random_appointment(None); + let user_signature = "user_signature"; + let appointment_receipt = AppointmentReceipt::with_signature( + user_signature.to_owned(), + 42, + "tower_signature".to_owned(), + ); + + tower_summary.available_slots -= 1; + + storage + .store_appointment_receipt( + tower_id, + appointment.locator, + tower_summary.available_slots, + &appointment_receipt, + ) + .unwrap(); + receipts.insert( + appointment.locator, + appointment_receipt.signature().unwrap(), + ); + } + + assert_eq!(storage.load_appointment_receipts(tower_id), receipts); + } + + #[test] + fn test_load_appointment_receipt() { + let mut storage = create_test_storage(); + 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!(storage + .load_appointment_receipt(tower_id, appointment.locator) + .is_none()); + + // Add the tower but not the appointment and try again + let net_addr = "talaia.watch"; + let receipt = get_random_registration_receipt(); + storage + .store_tower_record(tower_id, net_addr, &receipt) + .unwrap(); + + assert!(storage + .load_appointment_receipt(tower_id, appointment.locator) + .is_none()); + + // Add both + let tower_summary = TowerSummary::new( + net_addr.to_owned(), + receipt.available_slots(), + receipt.subscription_start(), + receipt.subscription_expiry(), + ); + let appointment_receipt = AppointmentReceipt::with_signature( + "user_signature".to_owned(), + 42, + "tower_signature".to_owned(), + ); + storage + .store_appointment_receipt( + tower_id, + appointment.locator, + tower_summary.available_slots, + &appointment_receipt, + ) + .unwrap(); + + assert_eq!( + storage + .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` From b7b960f2bde643d94529acbf41aeba4e175ebe85 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Fri, 14 Feb 2025 10:17:14 +0100 Subject: [PATCH 59/85] store pending and invalid appointments Signed-off-by: dzdidi --- teos-ldk-client/src/storage/kv.rs | 200 +++++++++++++++++------------- 1 file changed, 114 insertions(+), 86 deletions(-) diff --git a/teos-ldk-client/src/storage/kv.rs b/teos-ldk-client/src/storage/kv.rs index 8d682078..44c591e5 100644 --- a/teos-ldk-client/src/storage/kv.rs +++ b/teos-ldk-client/src/storage/kv.rs @@ -336,9 +336,23 @@ impl Persister for KVStorage { tower_id: TowerId, status: AppointmentStatus, ) -> HashSet { - // primary namespace: "watchtower" - // secondary namespance: "{status}_appointment_receipt" - todo!(); + let mut result = HashSet::new(); + + let appointment_namespace = get_appointment_namespace(status); + + let tower_namespace = format!("{}:{}", appointment_namespace, tower_id); + + let locators = self + .store + .list(PRIMARY_NAMESPACE, &tower_namespace) + .map_err(|e| PersisterError::StoreError(e.to_string())) + .unwrap(); + + for locator in locators { + result.insert(Locator::from_slice(hex::decode(&locator).unwrap().as_slice()).unwrap()); + } + + result } /// Loads an appointment from the database. @@ -349,22 +363,27 @@ impl Persister for KVStorage { todo!(); } - // /// Stores a pending appointment into the database. - // /// - // /// A pending appointment is an appointment that was sent to a tower when it was unreachable. - // /// This data is stored so it can be resent once the tower comes back online. - // /// Internally calls [Self::store_appointment]. + /// Stores a pending appointment into the database. + /// + /// A pending appointment is an appointment that was sent to a tower when it was unreachable. + /// This data is stored so it can be resent once the tower comes back online. + /// Internally calls [Self::store_appointment]. fn store_pending_appointment( &mut self, tower_id: TowerId, appointment: &Appointment, ) -> Result<(), PersisterError> { - // let key = make_key(&[&tower_id.to_string(), &appointment.locator.to_string()]); - // primary namespace: "watchtower" - // secondary namespance: "pending_appointment" - // key: - // value: ? - todo!(); + let tower_namespace = format!("{}:{}", NS_PENDING_APPOINTMENTS, tower_id); + let key = appointment.locator.to_string(); + + let encrypted = encrypt(&bincode::serialize(appointment).unwrap(), &self.sk).unwrap(); + + self.store + .write(PRIMARY_NAMESPACE, &tower_namespace, &key, &encrypted) + .map_err(|e| PersisterError::StoreError(e.to_string())) + .unwrap(); + + Ok(()) } /// Removes a pending appointment from the database. @@ -393,12 +412,17 @@ impl Persister for KVStorage { tower_id: TowerId, appointment: &Appointment, ) -> Result<(), PersisterError> { - // let key = make_key(&[&tower_id.to_string(), &appointment.locator.to_string()]); - // primary namespace: "watchtower" - // secondary namespance: "invalid_appointment" - // key: ? - // value: ? - todo!(); + let tower_namespace = format!("{}:{}", NS_INVALID_APPOINTMENTS, tower_id); + let key = appointment.locator.to_string(); + + let encrypted = encrypt(&bincode::serialize(appointment).unwrap(), &self.sk).unwrap(); + + self.store + .write(PRIMARY_NAMESPACE, &tower_namespace, &key, &encrypted) + .map_err(|e| PersisterError::StoreError(e.to_string())) + .unwrap(); + + Ok(()) } /// Loads non finalized appointments from the database for a given tower based on a status flag. @@ -789,72 +813,76 @@ mod tests { ); } - // #[test] - // fn test_load_appointment_locators() { - // // `load_appointment_locators` is used to load locators from either `appointment_receipts`, `pending_appointments` or `invalid_appointments` - // let mut dbm = DBM::in_memory().unwrap(); - // - // // We first need to add a tower record to the database so we can add some associated data. - // let tower_id = get_random_user_id(); - // let net_addr = "talaia.watch"; - // - // let receipt = get_random_registration_receipt(); - // let tower_summary = TowerSummary::new( - // net_addr.to_owned(), - // receipt.available_slots(), - // receipt.subscription_start(), - // receipt.subscription_expiry(), - // ); - // dbm.store_tower_record(tower_id, net_addr, &receipt) - // .unwrap(); - // - // // Create all types of appointments and store them in the db. - // let user_signature = "user_signature"; - // let mut receipts = HashSet::new(); - // let mut pending_appointments = HashSet::new(); - // let mut invalid_appointments = HashSet::new(); - // for _ in 0..5 { - // let appointment = generate_random_appointment(None); - // let appointment_receipt = AppointmentReceipt::with_signature( - // user_signature.to_owned(), - // 42, - // "tower_signature".to_owned(), - // ); - // let pending_appointment = generate_random_appointment(None); - // let invalid_appointment = generate_random_appointment(None); - // - // dbm.store_appointment_receipt( - // tower_id, - // appointment.locator, - // tower_summary.available_slots, - // &appointment_receipt, - // ) - // .unwrap(); - // dbm.store_pending_appointment(tower_id, &pending_appointment) - // .unwrap(); - // dbm.store_invalid_appointment(tower_id, &invalid_appointment) - // .unwrap(); - // - // receipts.insert(appointment.locator); - // pending_appointments.insert(pending_appointment.locator); - // invalid_appointments.insert(invalid_appointment.locator); - // } - // - // // Pull data from the db and check it matches the expected data - // assert_eq!( - // dbm.load_appointment_locators(tower_id, AppointmentStatus::Accepted), - // receipts - // ); - // assert_eq!( - // dbm.load_appointment_locators(tower_id, AppointmentStatus::Pending), - // pending_appointments - // ); - // assert_eq!( - // dbm.load_appointment_locators(tower_id, AppointmentStatus::Invalid), - // invalid_appointments - // ); - // } - // + #[test] + fn test_load_appointment_locators() { + // `load_appointment_locators` is used to load locators from either `appointment_receipts`, `pending_appointments` or `invalid_appointments` + let mut storage = create_test_storage(); + + // We first need to add a tower record to the database so we can add some associated data. + let tower_id = get_random_user_id(); + let net_addr = "talaia.watch"; + + let receipt = get_random_registration_receipt(); + let tower_summary = TowerSummary::new( + net_addr.to_owned(), + receipt.available_slots(), + receipt.subscription_start(), + receipt.subscription_expiry(), + ); + storage + .store_tower_record(tower_id, net_addr, &receipt) + .unwrap(); + + // Create all types of appointments and store them in the db. + let user_signature = "user_signature"; + let mut receipts = HashSet::new(); + let mut pending_appointments = HashSet::new(); + let mut invalid_appointments = HashSet::new(); + for _ in 0..5 { + let appointment = generate_random_appointment(None); + let appointment_receipt = AppointmentReceipt::with_signature( + user_signature.to_owned(), + 42, + "tower_signature".to_owned(), + ); + let pending_appointment = generate_random_appointment(None); + let invalid_appointment = generate_random_appointment(None); + + storage + .store_appointment_receipt( + tower_id, + appointment.locator, + tower_summary.available_slots, + &appointment_receipt, + ) + .unwrap(); + storage + .store_pending_appointment(tower_id, &pending_appointment) + .unwrap(); + storage + .store_invalid_appointment(tower_id, &invalid_appointment) + .unwrap(); + + receipts.insert(appointment.locator); + pending_appointments.insert(pending_appointment.locator); + invalid_appointments.insert(invalid_appointment.locator); + } + + // Pull data from the db and check it matches the expected data + assert_eq!( + storage.load_appointment_locators(tower_id, AppointmentStatus::Accepted), + receipts + ); + assert_eq!( + storage.load_appointment_locators(tower_id, AppointmentStatus::Pending), + pending_appointments + ); + assert_eq!( + storage.load_appointment_locators(tower_id, AppointmentStatus::Invalid), + invalid_appointments + ); + } + // #[test] // fn test_store_load_appointment() { // let mut dbm = DBM::in_memory().unwrap(); From 387377fe55dc05477f26ac97f2e87d676fc1d4dc Mon Sep 17 00:00:00 2001 From: dzdidi Date: Fri, 14 Feb 2025 12:04:41 +0100 Subject: [PATCH 60/85] crud appointments; tower status based on appointments Signed-off-by: dzdidi --- teos-ldk-client/src/storage/kv.rs | 278 +++++++++++++++++++----------- 1 file changed, 174 insertions(+), 104 deletions(-) diff --git a/teos-ldk-client/src/storage/kv.rs b/teos-ldk-client/src/storage/kv.rs index 44c591e5..1de0f5c0 100644 --- a/teos-ldk-client/src/storage/kv.rs +++ b/teos-ldk-client/src/storage/kv.rs @@ -2,6 +2,8 @@ use std::collections::{HashMap, HashSet}; // use chacha20poly1305::aead::{Aead}; use std::sync::Arc; +use crate::TowerStatus; + use crate::storage::persister::{Persister, PersisterError}; // use bitcoin::secp256k1::SecretKey; use lightning::io::Error as DBError; @@ -76,6 +78,29 @@ impl KVStorage { pub fn new(store: Arc, sk: Vec) -> Result { Ok(KVStorage { store, sk }) } + + fn store_appointment(&mut self, appointment: &Appointment) -> Result<(), PersisterError> { + let key = make_key(&[&appointment.locator.to_string()]); + let value = bincode::serialize(appointment).unwrap(); + self.store + .write(PRIMARY_NAMESPACE, NS_APPOINTMENTS, &key, &value) + .map_err(|e| PersisterError::StoreError(e.to_string())) + } + + fn load_misbehaving_proof(&self, tower_id: TowerId) -> Option { + let key = make_key(&[&tower_id.to_string()]); + + match self + .store + .read(PRIMARY_NAMESPACE, NS_MISBEHAVIOR_PROOFS, &key) + { + Ok(value) => { + let decrypted = decrypt(&value, &self.sk).unwrap(); + Some(bincode::deserialize(&decrypted).unwrap()) + } + Err(_) => None, + } + } } impl Persister for KVStorage { @@ -163,12 +188,11 @@ impl Persister for KVStorage { let decrypted = decrypt(&value, &self.sk).unwrap(); let mut tower_info: TowerInfo = bincode::deserialize(&decrypted).ok()?; - // TODO: - // tower_info.appointments = self.load_appointment_receipts(tower_id); - // tower_info.pending_appointments = - // self.load_appointments(tower_id, AppointmentStatus::Pending); - // tower_info.invalid_appointments = - // self.load_appointments(tower_id, AppointmentStatus::Invalid); + tower_info.appointments = self.load_appointment_receipts(tower_id); + tower_info.pending_appointments = + self.load_appointments(tower_id, AppointmentStatus::Pending); + tower_info.invalid_appointments = + self.load_appointments(tower_id, AppointmentStatus::Invalid); tower_info.available_slots = match self.store.read(PRIMARY_NAMESPACE, NS_AVAILABLE_SLOTS, &key) { Ok(bytes) if bytes.len() >= 4 => { @@ -179,6 +203,13 @@ impl Persister for KVStorage { _ => 0, }; + if let Some(proof) = self.load_misbehaving_proof(tower_id) { + tower_info.status = TowerStatus::Misbehaving; + tower_info.set_misbehaving_proof(proof); + } else if !tower_info.pending_appointments.is_empty() { + tower_info.status = TowerStatus::TemporaryUnreachable; + } + Some(tower_info) } @@ -357,10 +388,15 @@ impl Persister for KVStorage { /// Loads an appointment from the database. fn load_appointment(&self, locator: Locator) -> Option { - // primary namespace: "watchtower" - // secondary namespance: "appointment_receipt" - // key: - todo!(); + let key = make_key(&[&locator.to_string()]); + + match self.store.read(PRIMARY_NAMESPACE, NS_APPOINTMENTS, &key) { + Ok(value) => { + let decrypted = decrypt(&value, &self.sk).unwrap(); + Some(bincode::deserialize(&decrypted).unwrap()) + } + Err(_) => None, + } } /// Stores a pending appointment into the database. @@ -383,6 +419,8 @@ impl Persister for KVStorage { .map_err(|e| PersisterError::StoreError(e.to_string())) .unwrap(); + self.store_appointment(appointment).unwrap(); + Ok(()) } @@ -422,6 +460,8 @@ impl Persister for KVStorage { .map_err(|e| PersisterError::StoreError(e.to_string())) .unwrap(); + self.store_appointment(appointment).unwrap(); + Ok(()) } @@ -430,12 +470,36 @@ impl Persister for KVStorage { /// This is meant to be used only for pending and invalid appointments, if the method is called for /// accepted appointment, an empty collection will be returned. fn load_appointments(&self, tower_id: TowerId, status: AppointmentStatus) -> Vec { - // let key = make_key(&[&tower_id.to_string()]); - // primary namespace: "watchtower" - // secondary namespance: "{status}_appointment_receipt" - // key: ? - // value: ? - todo!(); + let mut appointments = Vec::new(); + + let namespace = match status { + AppointmentStatus::Accepted => return Vec::new(), + _ => get_appointment_namespace(status), + }; + + let tower_namespace = format!("{}:{}", namespace, tower_id); + + let locators = self + .store + .list(PRIMARY_NAMESPACE, &tower_namespace) + .map_err(|e| PersisterError::StoreError(e.to_string())) + .unwrap(); + + for locator in locators { + let locator = match Locator::from_slice(hex::decode(&locator).unwrap().as_slice()) { + Ok(l) => l, + Err(s) => { + panic!("Error deserializing locator: {}", s); + } + }; + + match self.load_appointment(locator) { + None => continue, + Some(appointment) => appointments.push(appointment), + } + } + + appointments } /// Stores a misbehaving proof into the database. @@ -883,94 +947,100 @@ mod tests { ); } - // #[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!(loaded_appointment.is_none()); - // } - // - // #[test] - // fn test_store_pending_appointment() { - // let mut dbm = DBM::in_memory().unwrap(); - // - // // In order to add a tower record we need to associated registration receipt. - // let tower_id = get_random_user_id(); - // let net_addr = "talaia.watch"; - // - // let receipt = get_random_registration_receipt(); - // let mut tower_summary = TowerSummary::new( - // net_addr.to_owned(), - // receipt.available_slots(), - // receipt.subscription_start(), - // receipt.subscription_expiry(), - // ) - // .with_status(TowerStatus::TemporaryUnreachable); - // dbm.store_tower_record(tower_id, net_addr, &receipt) - // .unwrap(); - // - // // Add some pending appointments and check they match - // for _ in 0..5 { - // let appointment = generate_random_appointment(None); - // - // tower_summary - // .pending_appointments - // .insert(appointment.locator); - // - // dbm.store_pending_appointment(tower_id, &appointment) - // .unwrap(); - // assert_eq!( - // TowerSummary::from(dbm.load_tower_record(tower_id).unwrap()), - // tower_summary - // ); - // } - // } - // - // #[test] - // fn test_store_pending_appointment_twice() { - // let mut dbm = DBM::in_memory().unwrap(); - // - // // In order to add a tower record we need to associated registration receipt. - // let tower_id_1 = get_random_user_id(); - // let tower_id_2 = get_random_user_id(); - // let net_addr = "talaia.watch"; - // - // let receipt = get_random_registration_receipt(); - // dbm.store_tower_record(tower_id_1, net_addr, &receipt) - // .unwrap(); - // dbm.store_tower_record(tower_id_2, net_addr, &receipt) - // .unwrap(); - // - // // If the same appointment is stored twice (by different towers) it should go through - // // Since the appointment data will be stored only once and this will create two references - // let appointment = generate_random_appointment(None); - // dbm.store_pending_appointment(tower_id_1, &appointment) - // .unwrap(); - // dbm.store_pending_appointment(tower_id_2, &appointment) - // .unwrap(); - // - // // If this is called twice with for the same tower it will fail, since two identical references - // // can not exist. This is intended behavior and should not happen - // assert!(dbm - // .store_pending_appointment(tower_id_2, &appointment) - // .is_err()); - // } - // + #[test] + fn test_store_load_appointment() { + let mut storage = create_test_storage(); + + let appointment = generate_random_appointment(None); + storage.store_appointment(&appointment).unwrap(); + + let loaded_appointment = storage.load_appointment(appointment.locator); + assert_eq!(appointment, loaded_appointment.unwrap()); + } + + #[test] + fn test_store_load_appointment_inexistent() { + let storage = create_test_storage(); + + let locator = generate_random_appointment(None).locator; + let loaded_appointment = storage.load_appointment(locator); + assert!(loaded_appointment.is_none()); + } + + #[test] + fn test_store_pending_appointment() { + let mut storage = create_test_storage(); + + // In order to add a tower record we need to associated registration receipt. + let tower_id = get_random_user_id(); + let net_addr = "talaia.watch"; + + let receipt = get_random_registration_receipt(); + let mut tower_summary = TowerSummary::new( + net_addr.to_owned(), + receipt.available_slots(), + receipt.subscription_start(), + receipt.subscription_expiry(), + ) + .with_status(TowerStatus::TemporaryUnreachable); + + storage + .store_tower_record(tower_id, net_addr, &receipt) + .unwrap(); + + // Add some pending appointments and check they match + for _ in 0..5 { + let appointment = generate_random_appointment(None); + + tower_summary + .pending_appointments + .insert(appointment.locator); + + storage + .store_pending_appointment(tower_id, &appointment) + .unwrap(); + assert_eq!( + TowerSummary::from(storage.load_tower_record(tower_id).unwrap()), + tower_summary + ); + } + } + + #[test] + fn test_store_pending_appointment_twice() { + let mut storage = create_test_storage(); + + // In order to add a tower record we need to associated registration receipt. + let tower_id_1 = get_random_user_id(); + let tower_id_2 = get_random_user_id(); + let net_addr = "talaia.watch"; + + let receipt = get_random_registration_receipt(); + storage + .store_tower_record(tower_id_1, net_addr, &receipt) + .unwrap(); + storage + .store_tower_record(tower_id_2, net_addr, &receipt) + .unwrap(); + + // If the same appointment is stored twice (by different towers) it should go through + // Since the appointment data will be stored only once and this will create two references + let appointment = generate_random_appointment(None); + storage + .store_pending_appointment(tower_id_1, &appointment) + .unwrap(); + storage + .store_pending_appointment(tower_id_2, &appointment) + .unwrap(); + + // If this is called twice with for the same tower it will fail, since two identical references + // can not exist. This is intended behavior and should not happen + // FIXME: this one need some extra thoughts + // assert!(storage + // .store_pending_appointment(tower_id_2, &appointment) + // .is_err()); + } + // #[test] // fn test_delete_pending_appointment() { // let mut dbm = DBM::in_memory().unwrap(); From 977f2dab24b710c2749fdf118f2c7a2306b6fecc Mon Sep 17 00:00:00 2001 From: dzdidi Date: Fri, 14 Feb 2025 13:36:21 +0100 Subject: [PATCH 61/85] hack for appointment existance Signed-off-by: dzdidi --- teos-ldk-client/src/storage/kv.rs | 534 +++++++++++++++++------------- 1 file changed, 298 insertions(+), 236 deletions(-) diff --git a/teos-ldk-client/src/storage/kv.rs b/teos-ldk-client/src/storage/kv.rs index 1de0f5c0..3e7052f1 100644 --- a/teos-ldk-client/src/storage/kv.rs +++ b/teos-ldk-client/src/storage/kv.rs @@ -79,9 +79,19 @@ impl KVStorage { Ok(KVStorage { store, sk }) } - fn store_appointment(&mut self, appointment: &Appointment) -> Result<(), PersisterError> { + fn store_appointment( + &mut self, + tower_id: TowerId, + appointment: &Appointment, + ) -> Result<(), PersisterError> { + let tower_namespace = format!("{}:{}", NS_APPOINTMENTS, tower_id); + let key = make_key(&[&appointment.locator.to_string()]); let value = bincode::serialize(appointment).unwrap(); + self.store + .write(PRIMARY_NAMESPACE, &tower_namespace, &key, &value) + .map_err(|e| PersisterError::StoreError(e.to_string())); + self.store .write(PRIMARY_NAMESPACE, NS_APPOINTMENTS, &key, &value) .map_err(|e| PersisterError::StoreError(e.to_string())) @@ -101,6 +111,14 @@ impl KVStorage { Err(_) => None, } } + + fn exists_misbehaving_proof(&self, tower_id: TowerId) -> bool { + let key = make_key(&[&tower_id.to_string()]); + + self.store + .read(PRIMARY_NAMESPACE, NS_MISBEHAVIOR_PROOFS, &key) + .is_ok() + } } impl Persister for KVStorage { @@ -419,7 +437,7 @@ impl Persister for KVStorage { .map_err(|e| PersisterError::StoreError(e.to_string())) .unwrap(); - self.store_appointment(appointment).unwrap(); + self.store_appointment(tower_id, appointment).unwrap(); Ok(()) } @@ -432,12 +450,22 @@ impl Persister for KVStorage { tower_id: TowerId, locator: Locator, ) -> Result<(), PersisterError> { - // let key = make_key(&[&tower_id.to_string(), &locator.to_string()]); - // primary namespace: "watchtower" - // secondary namespance: "pending_appointment" - // key: ? - // value: ? - todo!(); + let tower_namespace = format!("{}:{}", NS_PENDING_APPOINTMENTS, tower_id); + + let key = locator.to_string(); + + self.store + .remove(PRIMARY_NAMESPACE, &tower_namespace, &key, true) + .map_err(|e| PersisterError::StoreError(e.to_string())); + + let tower_namespace = format!("{}:{}", NS_APPOINTMENTS, tower_id); + self.store + .remove(PRIMARY_NAMESPACE, &tower_namespace, &key, false) + .map_err(|e| PersisterError::StoreError(e.to_string())); + + self.store + .remove(PRIMARY_NAMESPACE, NS_APPOINTMENTS, &key, false) + .map_err(|e| PersisterError::StoreError(e.to_string())) } /// Stores an invalid appointment into the database. @@ -460,7 +488,7 @@ impl Persister for KVStorage { .map_err(|e| PersisterError::StoreError(e.to_string())) .unwrap(); - self.store_appointment(appointment).unwrap(); + self.store_appointment(tower_id, appointment).unwrap(); Ok(()) } @@ -511,20 +539,35 @@ impl Persister for KVStorage { tower_id: TowerId, proof: &MisbehaviorProof, ) -> Result<(), PersisterError> { - // let key = make_key(&[&tower_id.to_string()]); - // primary namespace: "watchtower" - // secondary namespance: "misbehaving_proof" - // key: ? - // value: ? - todo!(); + let key = make_key(&[&tower_id.to_string()]); + let proof = bincode::serialize(proof).unwrap(); + let encrypted = encrypt(&proof, &self.sk).unwrap(); + + self.store + .write(PRIMARY_NAMESPACE, NS_MISBEHAVIOR_PROOFS, &key, &encrypted) + .map_err(|e| PersisterError::StoreError(e.to_string())) } fn appointment_exists(&self, locator: Locator) -> bool { - todo!(); + let key = make_key(&[&locator.to_string()]); + + // FIXME: check for all towers + let res = self.store.read(PRIMARY_NAMESPACE, NS_APPOINTMENTS, &key); + + match res { + Ok(_) => true, + Err(_) => false, + } } fn appointment_receipt_exists(&self, locator: Locator, tower_id: TowerId) -> bool { - todo!(); + let key = make_key(&[&locator.to_string()]); + + let tower_namespace = format!("{}:{}", NS_APPOINTMENT_RECEIPTS, tower_id); + + self.store + .read(PRIMARY_NAMESPACE, &tower_namespace, &key) + .is_ok() } } @@ -951,8 +994,9 @@ mod tests { fn test_store_load_appointment() { let mut storage = create_test_storage(); + let tower_id = get_random_user_id(); let appointment = generate_random_appointment(None); - storage.store_appointment(&appointment).unwrap(); + storage.store_appointment(tower_id, &appointment).unwrap(); let loaded_appointment = storage.load_appointment(appointment.locator); assert_eq!(appointment, loaded_appointment.unwrap()); @@ -1041,222 +1085,240 @@ mod tests { // .is_err()); } - // #[test] - // fn test_delete_pending_appointment() { - // let mut dbm = DBM::in_memory().unwrap(); - // - // // In order to add a tower record we need to associated registration receipt. - // 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(); - // - // // Add a single one, remove it later - // let appointment = generate_random_appointment(None); - // dbm.store_pending_appointment(tower_id, &appointment) - // .unwrap(); - // assert!(dbm - // .delete_pending_appointment(tower_id, appointment.locator) - // .is_ok()); - // - // // The appointment should be completely gone - // assert!(!dbm - // .load_appointment_locators(tower_id, AppointmentStatus::Pending) - // .contains(&appointment.locator)); - // // assert!(!dbm.appointment_exists(appointment.locator)); - // - // // Try again with more than one reference - // let another_tower_id = get_random_user_id(); - // dbm.store_tower_record(another_tower_id, net_addr, &receipt) - // .unwrap(); - // - // // Add two - // dbm.store_pending_appointment(tower_id, &appointment) - // .unwrap(); - // dbm.store_pending_appointment(another_tower_id, &appointment) - // .unwrap(); - // // Delete one - // assert!(dbm - // .delete_pending_appointment(tower_id, appointment.locator) - // .is_ok()); - // // Check - // assert!(!dbm - // .load_appointment_locators(tower_id, AppointmentStatus::Pending) - // .contains(&appointment.locator)); - // assert!(dbm - // .load_appointment_locators(another_tower_id, AppointmentStatus::Pending) - // .contains(&appointment.locator)); - // // assert!(dbm.appointment_exists(appointment.locator)); - // - // // Add an invalid reference and check again - // dbm.store_invalid_appointment(tower_id, &appointment) - // .unwrap(); - // assert!(dbm - // .delete_pending_appointment(another_tower_id, appointment.locator) - // .is_ok()); - // assert!(!dbm - // .load_appointment_locators(another_tower_id, AppointmentStatus::Pending) - // .contains(&appointment.locator)); - // assert!(dbm - // .load_appointment_locators(tower_id, AppointmentStatus::Invalid) - // .contains(&appointment.locator)); - // // assert!(dbm.appointment_exists(appointment.locator)); - // } - // - // #[test] - // fn test_store_invalid_appointment() { - // let mut dbm = DBM::in_memory().unwrap(); - // - // // In order to add a tower record we need to associated registration receipt. - // let tower_id = get_random_user_id(); - // let net_addr = "talaia.watch"; - // - // let receipt = get_random_registration_receipt(); - // let mut tower_summary = TowerSummary::new( - // net_addr.to_owned(), - // receipt.available_slots(), - // receipt.subscription_start(), - // receipt.subscription_expiry(), - // ); - // dbm.store_tower_record(tower_id, net_addr, &receipt) - // .unwrap(); - // - // // Add some invalid appointments and check they match - // for _ in 0..5 { - // let appointment = generate_random_appointment(None); - // - // tower_summary - // .invalid_appointments - // .insert(appointment.locator); - // - // dbm.store_invalid_appointment(tower_id, &appointment) - // .unwrap(); - // assert_eq!( - // TowerSummary::from(dbm.load_tower_record(tower_id).unwrap()), - // tower_summary - // ); - // } - // } - // - // #[test] - // fn test_store_invalid_appointment_twice() { - // let mut dbm = DBM::in_memory().unwrap(); - // - // // In order to add a tower record we need to associated registration receipt. - // let tower_id_1 = get_random_user_id(); - // let tower_id_2 = get_random_user_id(); - // let net_addr = "talaia.watch"; - // - // let receipt = get_random_registration_receipt(); - // dbm.store_tower_record(tower_id_1, net_addr, &receipt) - // .unwrap(); - // dbm.store_tower_record(tower_id_2, net_addr, &receipt) - // .unwrap(); - // - // // Same as with pending appointments. Two references from different towers is allowed - // let appointment = generate_random_appointment(None); - // dbm.store_invalid_appointment(tower_id_1, &appointment) - // .unwrap(); - // dbm.store_invalid_appointment(tower_id_2, &appointment) - // .unwrap(); - // - // // Two references from the same tower is not. - // assert!(dbm - // .store_invalid_appointment(tower_id_2, &appointment) - // .is_err()); - // } - // - // #[test] - // fn test_store_load_misbehaving_proof() { - // let mut dbm = DBM::in_memory().unwrap(); - // - // // In order to add a tower record we need to associated registration receipt. - // let tower_id = get_random_user_id(); - // let net_addr = "talaia.watch"; - // - // let receipt = get_random_registration_receipt(); - // let tower_summary = TowerSummary::new( - // net_addr.to_owned(), - // receipt.available_slots(), - // receipt.subscription_start(), - // receipt.subscription_expiry(), - // ); - // dbm.store_tower_record(tower_id, net_addr, &receipt) - // .unwrap(); - // assert_eq!( - // TowerSummary::from(dbm.load_tower_record(tower_id).unwrap()), - // tower_summary - // ); - // - // // Store a misbehaving proof and load it back - // let appointment = generate_random_appointment(None); - // let appointment_receipt = AppointmentReceipt::with_signature( - // "user_signature".to_owned(), - // 42, - // "tower_signature".to_owned(), - // ); - // - // let proof = MisbehaviorProof::new( - // appointment.locator, - // appointment_receipt, - // get_random_user_id(), - // ); - // - // dbm.store_misbehaving_proof(tower_id, &proof).unwrap(); - // assert_eq!(dbm.load_misbehaving_proof(tower_id).unwrap(), proof); - // } - // - // #[test] - // fn test_store_load_non_existing_misbehaving_proof() { - // let dbm = DBM::in_memory().unwrap(); - // assert!(dbm.load_misbehaving_proof(get_random_user_id()).is_none()); - // } - // - // #[test] - // fn test_store_exists_misbehaving_proof() { - // let mut dbm = DBM::in_memory().unwrap(); - // - // // In order to add a tower record we need to associated registration receipt. - // let tower_id = get_random_user_id(); - // let net_addr = "talaia.watch"; - // - // let receipt = get_random_registration_receipt(); - // let tower_summary = TowerSummary::new( - // net_addr.to_owned(), - // receipt.available_slots(), - // receipt.subscription_start(), - // receipt.subscription_expiry(), - // ); - // dbm.store_tower_record(tower_id, net_addr, &receipt) - // .unwrap(); - // assert_eq!( - // TowerSummary::from(dbm.load_tower_record(tower_id).unwrap()), - // tower_summary - // ); - // - // // // Store a misbehaving proof check - // let appointment = generate_random_appointment(None); - // let appointment_receipt = AppointmentReceipt::with_signature( - // "user_signature".to_owned(), - // 42, - // "tower_signature".to_owned(), - // ); - // - // let proof = MisbehaviorProof::new( - // appointment.locator, - // appointment_receipt, - // get_random_user_id(), - // ); - // - // dbm.store_misbehaving_proof(tower_id, &proof).unwrap(); - // assert!(dbm.exists_misbehaving_proof(tower_id)); - // } - // - // #[test] - // fn test_exists_misbehaving_proof_false() { - // let dbm = DBM::in_memory().unwrap(); - // assert!(!dbm.exists_misbehaving_proof(get_random_user_id())); - // } + #[test] + fn test_delete_pending_appointment() { + let mut storage = create_test_storage(); + + // In order to add a tower record we need to associated registration receipt. + let tower_id = get_random_user_id(); + let net_addr = "talaia.watch"; + + let receipt = get_random_registration_receipt(); + storage + .store_tower_record(tower_id, net_addr, &receipt) + .unwrap(); + + // Add a single one, remove it later + let appointment = generate_random_appointment(None); + storage + .store_pending_appointment(tower_id, &appointment) + .unwrap(); + assert!(storage + .delete_pending_appointment(tower_id, appointment.locator) + .is_ok()); + + // The appointment should be completely gone + assert!(!storage + .load_appointment_locators(tower_id, AppointmentStatus::Pending) + .contains(&appointment.locator)); + + assert!(!storage.appointment_exists(appointment.locator)); + + // Try again with more than one reference + let another_tower_id = get_random_user_id(); + storage + .store_tower_record(another_tower_id, net_addr, &receipt) + .unwrap(); + + // Add two + storage + .store_pending_appointment(tower_id, &appointment) + .unwrap(); + storage + .store_pending_appointment(another_tower_id, &appointment) + .unwrap(); + // Delete one + assert!(storage + .delete_pending_appointment(tower_id, appointment.locator) + .is_ok()); + // Check + assert!(!storage + .load_appointment_locators(tower_id, AppointmentStatus::Pending) + .contains(&appointment.locator)); + assert!(storage + .load_appointment_locators(another_tower_id, AppointmentStatus::Pending) + .contains(&appointment.locator)); + assert!(storage.appointment_exists(appointment.locator)); + + // Add an invalid reference and check again + storage + .store_invalid_appointment(tower_id, &appointment) + .unwrap(); + assert!(storage + .delete_pending_appointment(another_tower_id, appointment.locator) + .is_ok()); + assert!(!storage + .load_appointment_locators(another_tower_id, AppointmentStatus::Pending) + .contains(&appointment.locator)); + assert!(storage + .load_appointment_locators(tower_id, AppointmentStatus::Invalid) + .contains(&appointment.locator)); + assert!(storage.appointment_exists(appointment.locator)); + } + + #[test] + fn test_store_invalid_appointment() { + let mut storage = create_test_storage(); + + // In order to add a tower record we need to associated registration receipt. + let tower_id = get_random_user_id(); + let net_addr = "talaia.watch"; + + let receipt = get_random_registration_receipt(); + let mut tower_summary = TowerSummary::new( + net_addr.to_owned(), + receipt.available_slots(), + receipt.subscription_start(), + receipt.subscription_expiry(), + ); + storage + .store_tower_record(tower_id, net_addr, &receipt) + .unwrap(); + + // Add some invalid appointments and check they match + for _ in 0..5 { + let appointment = generate_random_appointment(None); + + tower_summary + .invalid_appointments + .insert(appointment.locator); + + storage + .store_invalid_appointment(tower_id, &appointment) + .unwrap(); + assert_eq!( + TowerSummary::from(storage.load_tower_record(tower_id).unwrap()), + tower_summary + ); + } + } + + #[test] + fn test_store_invalid_appointment_twice() { + let mut storage = create_test_storage(); + + // In order to add a tower record we need to associated registration receipt. + let tower_id_1 = get_random_user_id(); + let tower_id_2 = get_random_user_id(); + let net_addr = "talaia.watch"; + + let receipt = get_random_registration_receipt(); + storage + .store_tower_record(tower_id_1, net_addr, &receipt) + .unwrap(); + storage + .store_tower_record(tower_id_2, net_addr, &receipt) + .unwrap(); + + // Same as with pending appointments. Two references from different towers is allowed + let appointment = generate_random_appointment(None); + storage + .store_invalid_appointment(tower_id_1, &appointment) + .unwrap(); + storage + .store_invalid_appointment(tower_id_2, &appointment) + .unwrap(); + + // Two references from the same tower is not. + // FIXME + // assert!(storage + // .store_invalid_appointment(tower_id_2, &appointment) + // .is_err()); + } + + #[test] + fn test_store_load_misbehaving_proof() { + let mut storage = create_test_storage(); + + // In order to add a tower record we need to associated registration receipt. + let tower_id = get_random_user_id(); + let net_addr = "talaia.watch"; + + let receipt = get_random_registration_receipt(); + let tower_summary = TowerSummary::new( + net_addr.to_owned(), + receipt.available_slots(), + receipt.subscription_start(), + receipt.subscription_expiry(), + ); + storage + .store_tower_record(tower_id, net_addr, &receipt) + .unwrap(); + assert_eq!( + TowerSummary::from(storage.load_tower_record(tower_id).unwrap()), + tower_summary + ); + + // Store a misbehaving proof and load it back + let appointment = generate_random_appointment(None); + let appointment_receipt = AppointmentReceipt::with_signature( + "user_signature".to_owned(), + 42, + "tower_signature".to_owned(), + ); + + let proof = MisbehaviorProof::new( + appointment.locator, + appointment_receipt, + get_random_user_id(), + ); + + storage.store_misbehaving_proof(tower_id, &proof).unwrap(); + assert_eq!(storage.load_misbehaving_proof(tower_id).unwrap(), proof); + } + + #[test] + fn test_store_load_non_existing_misbehaving_proof() { + let mut storage = create_test_storage(); + assert!(storage + .load_misbehaving_proof(get_random_user_id()) + .is_none()); + } + + #[test] + fn test_store_exists_misbehaving_proof() { + let mut storage = create_test_storage(); + + // In order to add a tower record we need to associated registration receipt. + let tower_id = get_random_user_id(); + let net_addr = "talaia.watch"; + + let receipt = get_random_registration_receipt(); + let tower_summary = TowerSummary::new( + net_addr.to_owned(), + receipt.available_slots(), + receipt.subscription_start(), + receipt.subscription_expiry(), + ); + storage + .store_tower_record(tower_id, net_addr, &receipt) + .unwrap(); + assert_eq!( + TowerSummary::from(storage.load_tower_record(tower_id).unwrap()), + tower_summary + ); + + // // Store a misbehaving proof check + let appointment = generate_random_appointment(None); + let appointment_receipt = AppointmentReceipt::with_signature( + "user_signature".to_owned(), + 42, + "tower_signature".to_owned(), + ); + + let proof = MisbehaviorProof::new( + appointment.locator, + appointment_receipt, + get_random_user_id(), + ); + + storage.store_misbehaving_proof(tower_id, &proof).unwrap(); + assert!(storage.exists_misbehaving_proof(tower_id)); + } + + #[test] + fn test_exists_misbehaving_proof_false() { + let storage = create_test_storage(); + assert!(!storage.exists_misbehaving_proof(get_random_user_id())); + } } From c8df9a9cb94ef1a27447029527a0803bf4714973 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Wed, 19 Feb 2025 13:05:36 +0100 Subject: [PATCH 62/85] new key space Signed-off-by: dzdidi --- teos-ldk-client/src/storage/kv.rs | 389 ++++++++++++++++++------------ 1 file changed, 234 insertions(+), 155 deletions(-) diff --git a/teos-ldk-client/src/storage/kv.rs b/teos-ldk-client/src/storage/kv.rs index 3e7052f1..dfbb05aa 100644 --- a/teos-ldk-client/src/storage/kv.rs +++ b/teos-ldk-client/src/storage/kv.rs @@ -34,15 +34,14 @@ use crate::{AppointmentStatus, MisbehaviorProof, TowerInfo, TowerSummary}; // Primary namespace for all watchtower-related data const PRIMARY_NAMESPACE: &str = "watchtower"; -// Secondary namespaces for different data types +// Secondary namespaces and their prefixes for different data types const NS_TOWER_RECORDS: &str = "tower_records"; -const NS_APPOINTMENTS: &str = "appointments"; const NS_REGISTRATION_RECEIPTS: &str = "registration_receipts"; const NS_APPOINTMENT_RECEIPTS: &str = "appointment_receipts"; -const NS_PENDING_APPOINTMENTS: &str = "pending_appointments"; -const NS_INVALID_APPOINTMENTS: &str = "invalid_appointments"; +const NS_APPOINTMENTS: &str = "appointments"; +const NS_PENDING_APPOINTMENTS: &str = "appointments_pending"; +const NS_INVALID_APPOINTMENTS: &str = "appointments_invalid"; const NS_MISBEHAVIOR_PROOFS: &str = "misbehavior_proofs"; -const NS_AVAILABLE_SLOTS: &str = "available_slots"; pub type DynStore = dyn KVStore + Sync + Send; @@ -79,21 +78,15 @@ impl KVStorage { Ok(KVStorage { store, sk }) } - fn store_appointment( - &mut self, - tower_id: TowerId, - appointment: &Appointment, - ) -> Result<(), PersisterError> { - let tower_namespace = format!("{}:{}", NS_APPOINTMENTS, tower_id); - - let key = make_key(&[&appointment.locator.to_string()]); + fn store_appointment(&mut self, appointment: &Appointment) -> Result<(), PersisterError> { let value = bincode::serialize(appointment).unwrap(); self.store - .write(PRIMARY_NAMESPACE, &tower_namespace, &key, &value) - .map_err(|e| PersisterError::StoreError(e.to_string())); - - self.store - .write(PRIMARY_NAMESPACE, NS_APPOINTMENTS, &key, &value) + .write( + PRIMARY_NAMESPACE, + NS_APPOINTMENTS, + &appointment.locator.to_string(), + &value, + ) .map_err(|e| PersisterError::StoreError(e.to_string())) } @@ -134,14 +127,6 @@ impl Persister for KVStorage { ) -> Result<(), PersisterError> { let key = make_key(&[&tower_id.to_string()]); - if let Ok(existing) = self.store.read(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key) { - let decrypted = decrypt(&existing, &self.sk).unwrap(); - let existing_tower_info: TowerInfo = bincode::deserialize(&decrypted).unwrap(); - if existing_tower_info.subscription_expiry > receipt.subscription_expiry() { - return Ok(()); - } - }; - let tower_info = TowerInfo::new( net_addr.to_string(), receipt.available_slots(), @@ -155,40 +140,22 @@ impl Persister for KVStorage { let tower_info = bincode::serialize(&tower_info) .map_err(|e| PersisterError::Other(format!("Serialization error: {}", e)))?; - let encrypted = encrypt(&tower_info, &self.sk).unwrap(); - self.store - .write(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key, &encrypted) + .write(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key, &tower_info) .map_err(|e| PersisterError::StoreError(e.to_string())) .unwrap(); let registration_receipt = bincode::serialize(&receipt) .map_err(|e| PersisterError::Other(format!("Serialization error: {}", e)))?; - let encrypted = encrypt(®istration_receipt, &self.sk).unwrap(); - self.store .write( PRIMARY_NAMESPACE, - NS_AVAILABLE_SLOTS, - &key, - receipt.available_slots().to_be_bytes().as_ref(), + &format!("{NS_REGISTRATION_RECEIPTS}:{tower_id}"), + &receipt.subscription_expiry().to_string(), + ®istration_receipt, ) .map_err(|e| PersisterError::StoreError(e.to_string())) - .unwrap(); - - match self.store.write( - PRIMARY_NAMESPACE, - NS_REGISTRATION_RECEIPTS, - &key, - &encrypted, - ) { - Ok(_) => Ok(()), - Err(e) => { - self.remove_tower_record(tower_id).unwrap(); - Err(PersisterError::StoreError(e.to_string())) - } - } } /// Loads a tower record from the database. @@ -203,23 +170,39 @@ impl Persister for KVStorage { Err(_) => return None, }; - let decrypted = decrypt(&value, &self.sk).unwrap(); - let mut tower_info: TowerInfo = bincode::deserialize(&decrypted).ok()?; + let mut tower_info: TowerInfo = bincode::deserialize(&value).ok()?; tower_info.appointments = self.load_appointment_receipts(tower_id); tower_info.pending_appointments = self.load_appointments(tower_id, AppointmentStatus::Pending); tower_info.invalid_appointments = self.load_appointments(tower_id, AppointmentStatus::Invalid); - tower_info.available_slots = - match self.store.read(PRIMARY_NAMESPACE, NS_AVAILABLE_SLOTS, &key) { - Ok(bytes) if bytes.len() >= 4 => { - let mut buf = [0u8; 4]; - buf.copy_from_slice(&bytes[0..4]); - u32::from_be_bytes(buf) - } - _ => 0, - }; + + let subsciption_expiries = self + .store + .list( + PRIMARY_NAMESPACE, + &format!("{NS_REGISTRATION_RECEIPTS}:{tower_id}"), + ) + .unwrap() + .iter() + .map(|s_e| s_e.parse::().unwrap()) + .max() + .unwrap(); + + let registration_receipt = self + .store + .read( + PRIMARY_NAMESPACE, + &format!("{NS_REGISTRATION_RECEIPTS}:{tower_id}"), + &subsciption_expiries.to_string(), + ) + .unwrap(); + let registration_receipt: RegistrationReceipt = + bincode::deserialize(®istration_receipt).unwrap(); + + tower_info.subscription_start = registration_receipt.subscription_start(); + tower_info.subscription_expiry = registration_receipt.subscription_expiry(); if let Some(proof) = self.load_misbehaving_proof(tower_id) { tower_info.status = TowerStatus::Misbehaving; @@ -247,13 +230,16 @@ impl Persister for KVStorage { /// Returns a key value pair with the tower id as key and the tower summary as value. fn load_towers(&self) -> HashMap { let mut towers = HashMap::new(); - let keys = self + + let tower_ids = self .store .list(PRIMARY_NAMESPACE, NS_TOWER_RECORDS) - .unwrap(); + .unwrap() + .iter() + .map(|key| key.parse().unwrap()) + .collect::>(); - for key in keys { - let tower_id = key.split(":").next().unwrap().parse().unwrap(); + for tower_id in tower_ids { let tower_info = self.load_tower_record(tower_id).unwrap(); towers.insert(tower_id, TowerSummary::from(tower_info)); } @@ -269,26 +255,41 @@ impl Persister for KVStorage { tower_id: TowerId, user_id: UserId, ) -> Option { - let key = make_key(&[&tower_id.to_string()]); + let subscription_expiries = self + .store + .list( + PRIMARY_NAMESPACE, + &format!("{NS_REGISTRATION_RECEIPTS}:{tower_id}"), + ) + .unwrap() + .iter() + .map(|s_e| s_e.parse::().unwrap()) + .max(); + + let subscription_expiries = match subscription_expiries { + Some(subscription_expiries) => subscription_expiries, + None => return None, + }; - match self + let registration_receipt = self .store - .read(PRIMARY_NAMESPACE, NS_REGISTRATION_RECEIPTS, &key) - { - Ok(value) => { - let decrypted = decrypt(&value, &self.sk).unwrap(); - let registration_receipt: RegistrationReceipt = - bincode::deserialize(&decrypted).unwrap(); - Some(RegistrationReceipt::with_signature( - user_id, - registration_receipt.available_slots(), - registration_receipt.subscription_start(), - registration_receipt.subscription_expiry(), - registration_receipt.signature().unwrap(), - )) - } - Err(_) => None, - } + .read( + PRIMARY_NAMESPACE, + &format!("{NS_REGISTRATION_RECEIPTS}:{tower_id}"), + &subscription_expiries.to_string(), + ) + .unwrap(); + + let registration_receipt: RegistrationReceipt = + bincode::deserialize(®istration_receipt).unwrap(); + + Some(RegistrationReceipt::with_signature( + user_id, + registration_receipt.available_slots(), + registration_receipt.subscription_start(), + registration_receipt.subscription_expiry(), + registration_receipt.signature().unwrap(), + )) } /// Stores an appointments receipt into the database representing an appointment accepted by a given tower. @@ -299,28 +300,56 @@ impl Persister for KVStorage { available_slots: u32, receipt: &AppointmentReceipt, ) -> Result<(), PersisterError> { - let tower_namespace = format!("{}:{}", NS_APPOINTMENT_RECEIPTS, tower_id); let key = locator.to_string(); - let encrypted = encrypt(&bincode::serialize(receipt).unwrap(), &self.sk).unwrap(); - + // store appointment self.store - .write(PRIMARY_NAMESPACE, &tower_namespace, &key, &encrypted) + .write( + PRIMARY_NAMESPACE, + NS_APPOINTMENT_RECEIPTS, + &format!("{}:{}", tower_id, locator), + &bincode::serialize(receipt).unwrap(), + ) .map_err(|e| PersisterError::StoreError(e.to_string())) .unwrap(); // Update the tower's available_slots - let slots_key = make_key(&[&tower_id.to_string()]); self.store .write( PRIMARY_NAMESPACE, - NS_AVAILABLE_SLOTS, - &slots_key, - available_slots.to_be_bytes().as_ref(), + &tower_id.to_string(), + &tower_id.to_string(), + &available_slots.to_be_bytes(), ) .map_err(|e| PersisterError::StoreError(e.to_string())) .unwrap(); + let tower = self + .store + .read(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &format!("{tower_id}")) + .map_err(|e| PersisterError::StoreError(e.to_string())) + .unwrap(); + + let tower: TowerInfo = bincode::deserialize(&tower).unwrap(); + + let tower_info = TowerInfo::new( + tower.net_addr.to_string(), + available_slots, + tower.subscription_start, + tower.subscription_expiry, + HashMap::new(), + Vec::new(), + Vec::new(), + ); + + let tower_info = bincode::serialize(&tower_info) + .map_err(|e| PersisterError::Other(format!("Serialization error: {}", e)))?; + + self.store + .write(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key, &tower_info) + .map_err(|e| PersisterError::StoreError(e.to_string())) + .unwrap(); + Ok(()) } @@ -330,14 +359,12 @@ impl Persister for KVStorage { tower_id: TowerId, locator: Locator, ) -> Option { - let tower_namespace = format!("{}:{}", NS_APPOINTMENT_RECEIPTS, tower_id); - let key = locator.to_string(); - - match self.store.read(PRIMARY_NAMESPACE, &tower_namespace, &key) { - Ok(value) => { - let decrypted = decrypt(&value, &self.sk).unwrap(); - Some(bincode::deserialize(&decrypted).unwrap()) - } + match self.store.read( + PRIMARY_NAMESPACE, + NS_APPOINTMENT_RECEIPTS, + &format!("{}:{}", tower_id, locator), + ) { + Ok(value) => Some(bincode::deserialize(&value).unwrap()), Err(_) => None, } } @@ -348,19 +375,24 @@ impl Persister for KVStorage { /// for any reason this method may need to be renamed. fn load_appointment_receipts(&self, tower_id: TowerId) -> HashMap { let mut receipts = HashMap::new(); - let tower_namespace = format!("{}:{}", NS_APPOINTMENT_RECEIPTS, tower_id); let keys = self .store - .list(PRIMARY_NAMESPACE, &tower_namespace) + .list(PRIMARY_NAMESPACE, NS_APPOINTMENT_RECEIPTS) .unwrap(); + let keys = keys + .iter() + .filter(|l| l.starts_with(&tower_id.to_string())) + .map(|l| l.split(":").collect::>()[1]) + .collect::>(); + // Get all keys in the tower-specific namespace for key in keys { // Key is just the locator string let hex_encoded_string = key; let locator = - match Locator::from_slice(hex::decode(&hex_encoded_string).unwrap().as_slice()) { + match Locator::from_slice(hex::decode(hex_encoded_string).unwrap().as_slice()) { Ok(l) => l, Err(s) => { panic!("Error deserializing locator: {}", s); @@ -389,16 +421,20 @@ impl Persister for KVStorage { let appointment_namespace = get_appointment_namespace(status); - let tower_namespace = format!("{}:{}", appointment_namespace, tower_id); - let locators = self .store - .list(PRIMARY_NAMESPACE, &tower_namespace) + .list(PRIMARY_NAMESPACE, appointment_namespace) .map_err(|e| PersisterError::StoreError(e.to_string())) .unwrap(); + let locators = locators + .iter() + .filter(|l| l.starts_with(&tower_id.to_string())) + .map(|l| l.split(":").collect::>()[1]) + .collect::>(); + for locator in locators { - result.insert(Locator::from_slice(hex::decode(&locator).unwrap().as_slice()).unwrap()); + result.insert(Locator::from_slice(hex::decode(locator).unwrap().as_slice()).unwrap()); } result @@ -427,17 +463,29 @@ impl Persister for KVStorage { tower_id: TowerId, appointment: &Appointment, ) -> Result<(), PersisterError> { - let tower_namespace = format!("{}:{}", NS_PENDING_APPOINTMENTS, tower_id); - let key = appointment.locator.to_string(); - - let encrypted = encrypt(&bincode::serialize(appointment).unwrap(), &self.sk).unwrap(); + if self.store.read( + PRIMARY_NAMESPACE, + NS_PENDING_APPOINTMENTS, + &format!("{}:{}", tower_id, appointment.locator), + ).is_ok() { + return Err(PersisterError::Other(format!( + "{}:{}", + tower_id, + appointment.locator + ))) + } self.store - .write(PRIMARY_NAMESPACE, &tower_namespace, &key, &encrypted) + .write( + PRIMARY_NAMESPACE, + NS_PENDING_APPOINTMENTS, + &format!("{}:{}", tower_id, appointment.locator), + &bincode::serialize(appointment).unwrap(), + ) .map_err(|e| PersisterError::StoreError(e.to_string())) .unwrap(); - self.store_appointment(tower_id, appointment).unwrap(); + self.store_appointment(appointment).unwrap(); Ok(()) } @@ -450,21 +498,43 @@ impl Persister for KVStorage { tower_id: TowerId, locator: Locator, ) -> Result<(), PersisterError> { - let tower_namespace = format!("{}:{}", NS_PENDING_APPOINTMENTS, tower_id); - - let key = locator.to_string(); - - self.store - .remove(PRIMARY_NAMESPACE, &tower_namespace, &key, true) - .map_err(|e| PersisterError::StoreError(e.to_string())); - - let tower_namespace = format!("{}:{}", NS_APPOINTMENTS, tower_id); - self.store - .remove(PRIMARY_NAMESPACE, &tower_namespace, &key, false) - .map_err(|e| PersisterError::StoreError(e.to_string())); + // for all towers and given locator + let invalid_appointments_count = self + .store + .list(PRIMARY_NAMESPACE, NS_INVALID_APPOINTMENTS) + .unwrap() + .iter() + .filter(|l| l.ends_with(&locator.to_string())) + .count(); + + // for all towers and given locator + let pending_appointments_count = self + .store + .list(PRIMARY_NAMESPACE, NS_PENDING_APPOINTMENTS) + .unwrap() + .iter() + .filter(|l| l.ends_with(&locator.to_string())) + .count(); + + if invalid_appointments_count + pending_appointments_count == 1 { + self.store + .remove( + PRIMARY_NAMESPACE, + NS_APPOINTMENTS, + &locator.to_string(), + false, + ) + .map_err(|e| PersisterError::StoreError(e.to_string())) + .unwrap(); + }; self.store - .remove(PRIMARY_NAMESPACE, NS_APPOINTMENTS, &key, false) + .remove( + PRIMARY_NAMESPACE, + NS_PENDING_APPOINTMENTS, + &format!("{}:{}", tower_id, locator), + false, + ) .map_err(|e| PersisterError::StoreError(e.to_string())) } @@ -478,17 +548,29 @@ impl Persister for KVStorage { tower_id: TowerId, appointment: &Appointment, ) -> Result<(), PersisterError> { - let tower_namespace = format!("{}:{}", NS_INVALID_APPOINTMENTS, tower_id); - let key = appointment.locator.to_string(); - - let encrypted = encrypt(&bincode::serialize(appointment).unwrap(), &self.sk).unwrap(); + if self.store.read( + PRIMARY_NAMESPACE, + NS_INVALID_APPOINTMENTS, + &format!("{}:{}", tower_id, appointment.locator), + ).is_ok() { + return Err(PersisterError::Other(format!( + "{}:{}", + tower_id, + appointment.locator + ))) + } self.store - .write(PRIMARY_NAMESPACE, &tower_namespace, &key, &encrypted) + .write( + PRIMARY_NAMESPACE, + NS_INVALID_APPOINTMENTS, + &format!("{}:{}", tower_id, appointment.locator), + &bincode::serialize(appointment).unwrap(), + ) .map_err(|e| PersisterError::StoreError(e.to_string())) .unwrap(); - self.store_appointment(tower_id, appointment).unwrap(); + self.store_appointment(appointment).unwrap(); Ok(()) } @@ -505,16 +587,20 @@ impl Persister for KVStorage { _ => get_appointment_namespace(status), }; - let tower_namespace = format!("{}:{}", namespace, tower_id); - let locators = self .store - .list(PRIMARY_NAMESPACE, &tower_namespace) + .list(PRIMARY_NAMESPACE, namespace) .map_err(|e| PersisterError::StoreError(e.to_string())) .unwrap(); + let locators = locators + .iter() + .filter(|l| l.starts_with(&tower_id.to_string())) + .map(|l| l.split(":").collect::>()[1]) + .collect::>(); + for locator in locators { - let locator = match Locator::from_slice(hex::decode(&locator).unwrap().as_slice()) { + let locator = match Locator::from_slice(hex::decode(locator).unwrap().as_slice()) { Ok(l) => l, Err(s) => { panic!("Error deserializing locator: {}", s); @@ -541,32 +627,27 @@ impl Persister for KVStorage { ) -> Result<(), PersisterError> { let key = make_key(&[&tower_id.to_string()]); let proof = bincode::serialize(proof).unwrap(); - let encrypted = encrypt(&proof, &self.sk).unwrap(); self.store - .write(PRIMARY_NAMESPACE, NS_MISBEHAVIOR_PROOFS, &key, &encrypted) + .write(PRIMARY_NAMESPACE, NS_MISBEHAVIOR_PROOFS, &key, &proof) .map_err(|e| PersisterError::StoreError(e.to_string())) } fn appointment_exists(&self, locator: Locator) -> bool { let key = make_key(&[&locator.to_string()]); - // FIXME: check for all towers let res = self.store.read(PRIMARY_NAMESPACE, NS_APPOINTMENTS, &key); - match res { - Ok(_) => true, - Err(_) => false, - } + res.is_ok() } fn appointment_receipt_exists(&self, locator: Locator, tower_id: TowerId) -> bool { - let key = make_key(&[&locator.to_string()]); - - let tower_namespace = format!("{}:{}", NS_APPOINTMENT_RECEIPTS, tower_id); - self.store - .read(PRIMARY_NAMESPACE, &tower_namespace, &key) + .read( + PRIMARY_NAMESPACE, + NS_APPOINTMENT_RECEIPTS, + &format!("{}:{}", tower_id, locator), + ) .is_ok() } } @@ -996,7 +1077,7 @@ mod tests { let tower_id = get_random_user_id(); let appointment = generate_random_appointment(None); - storage.store_appointment(tower_id, &appointment).unwrap(); + storage.store_appointment(&appointment).unwrap(); let loaded_appointment = storage.load_appointment(appointment.locator); assert_eq!(appointment, loaded_appointment.unwrap()); @@ -1079,10 +1160,9 @@ mod tests { // If this is called twice with for the same tower it will fail, since two identical references // can not exist. This is intended behavior and should not happen - // FIXME: this one need some extra thoughts - // assert!(storage - // .store_pending_appointment(tower_id_2, &appointment) - // .is_err()); + assert!(storage + .store_pending_appointment(tower_id_2, &appointment) + .is_err()); } #[test] @@ -1220,10 +1300,9 @@ mod tests { .unwrap(); // Two references from the same tower is not. - // FIXME - // assert!(storage - // .store_invalid_appointment(tower_id_2, &appointment) - // .is_err()); + assert!(storage + .store_invalid_appointment(tower_id_2, &appointment) + .is_err()); } #[test] @@ -1269,7 +1348,7 @@ mod tests { #[test] fn test_store_load_non_existing_misbehaving_proof() { - let mut storage = create_test_storage(); + let storage = create_test_storage(); assert!(storage .load_misbehaving_proof(get_random_user_id()) .is_none()); From 731ccf7a95bb8ae25d88ad7db99394557c8a0710 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Thu, 20 Feb 2025 10:56:06 +0100 Subject: [PATCH 63/85] remove sqlite feature Signed-off-by: dzdidi --- teos-ldk-client/Cargo.toml | 5 ----- teos-ldk-client/src/wt_client.rs | 1 - 2 files changed, 6 deletions(-) diff --git a/teos-ldk-client/Cargo.toml b/teos-ldk-client/Cargo.toml index 80643697..fbbe804c 100644 --- a/teos-ldk-client/Cargo.toml +++ b/teos-ldk-client/Cargo.toml @@ -3,11 +3,6 @@ name = "teos-ldk-client" version = "0.1.0" edition = "2021" -[features] -default = ["sqlite"] -sqlite = [] -kv = [] - [dependencies] # General backoff = { version = "0.4.0", features = ["tokio"] } diff --git a/teos-ldk-client/src/wt_client.rs b/teos-ldk-client/src/wt_client.rs index d9f36de8..b9ba39d5 100644 --- a/teos-ldk-client/src/wt_client.rs +++ b/teos-ldk-client/src/wt_client.rs @@ -69,7 +69,6 @@ pub struct WTClient { } impl WTClient { - #[cfg(feature = "sqlite")] pub async fn new( storage: Box, user_sk: SecretKey, From f5acdffdb17d9bab128d8ec6091c46e4695e7ac9 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Thu, 20 Feb 2025 10:59:51 +0100 Subject: [PATCH 64/85] move kv storage creator to mod Signed-off-by: dzdidi --- teos-ldk-client/src/storage/kv.rs | 52 +++++++++++++----------------- teos-ldk-client/src/storage/mod.rs | 8 +++++ 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/teos-ldk-client/src/storage/kv.rs b/teos-ldk-client/src/storage/kv.rs index dfbb05aa..0cc51d37 100644 --- a/teos-ldk-client/src/storage/kv.rs +++ b/teos-ldk-client/src/storage/kv.rs @@ -696,22 +696,16 @@ fn decrypt( #[cfg(test)] mod tests { use super::*; - use crate::storage::MemoryStore; + use crate::storage::create_test_kv_storage; use teos_common::test_utils::{ generate_random_appointment, get_random_registration_receipt, get_random_user_id, get_registration_receipt_from_previous, }; - fn create_test_storage() -> KVStorage { - let store = MemoryStore::new().into_dyn_store(); - let sk = vec![0u8; 32]; // Test secret key - KVStorage::new(store, sk).unwrap() - } - #[test] fn test_store_load_tower_record() { - let mut storage = create_test_storage(); + let mut storage = create_test_kv_storage(); // In order to add a tower record we need to associated registration receipt. let tower_id = get_random_user_id(); @@ -749,7 +743,7 @@ mod tests { #[test] fn test_load_registration_receipt() { - let mut storage = create_test_storage(); + let mut storage = create_test_kv_storage(); // Registration receipts are stored alongside tower records when the register command is called let tower_id = get_random_user_id(); @@ -795,7 +789,7 @@ mod tests { #[test] fn test_load_same_registration_receipt() { - let mut storage = create_test_storage(); + let mut storage = create_test_kv_storage(); // Registration receipts are stored alongside tower records when the register command is called let tower_id = get_random_user_id(); @@ -824,7 +818,7 @@ mod tests { #[test] fn test_load_nonexistent_tower_record() { - let storage = create_test_storage(); + let storage = create_test_kv_storage(); // If the tower does not exists, `load_tower` will fail. let tower_id = get_random_user_id(); @@ -833,7 +827,7 @@ mod tests { #[test] fn test_store_load_towers() { - let mut storage = create_test_storage(); + let mut storage = create_test_kv_storage(); let mut towers = HashMap::new(); // In order to add a tower record we need to associated registration receipt. @@ -870,13 +864,13 @@ mod tests { #[test] fn test_load_towers_empty() { // If there are no towers in the database, `load_towers` should return an empty map. - let storage = create_test_storage(); + let storage = create_test_kv_storage(); assert_eq!(storage.load_towers(), HashMap::new()); } #[test] fn test_remove_tower_record() { - let mut storage = create_test_storage(); + let mut storage = create_test_kv_storage(); let tower_id = get_random_user_id(); let net_addr = "talaia.watch"; @@ -891,7 +885,7 @@ mod tests { #[test] fn test_remove_tower_record_inexistent() { - let storage = create_test_storage(); + let storage = create_test_kv_storage(); let tower_id = get_random_user_id(); let err = storage.remove_tower_record(tower_id).unwrap_err(); assert_eq!( @@ -902,7 +896,7 @@ mod tests { #[test] fn test_store_load_appointment_receipts() { - let mut storage = create_test_storage(); + let mut storage = create_test_kv_storage(); // In order to add a tower record we need to associated registration receipt. let tower_id = get_random_user_id(); @@ -951,7 +945,7 @@ mod tests { #[test] fn test_load_appointment_receipt() { - let mut storage = create_test_storage(); + let mut storage = create_test_kv_storage(); let tower_id = get_random_user_id(); let appointment = generate_random_appointment(None); @@ -1004,7 +998,7 @@ mod tests { #[test] fn test_load_appointment_locators() { // `load_appointment_locators` is used to load locators from either `appointment_receipts`, `pending_appointments` or `invalid_appointments` - let mut storage = create_test_storage(); + let mut storage = create_test_kv_storage(); // We first need to add a tower record to the database so we can add some associated data. let tower_id = get_random_user_id(); @@ -1073,7 +1067,7 @@ mod tests { #[test] fn test_store_load_appointment() { - let mut storage = create_test_storage(); + let mut storage = create_test_kv_storage(); let tower_id = get_random_user_id(); let appointment = generate_random_appointment(None); @@ -1085,7 +1079,7 @@ mod tests { #[test] fn test_store_load_appointment_inexistent() { - let storage = create_test_storage(); + let storage = create_test_kv_storage(); let locator = generate_random_appointment(None).locator; let loaded_appointment = storage.load_appointment(locator); @@ -1094,7 +1088,7 @@ mod tests { #[test] fn test_store_pending_appointment() { - let mut storage = create_test_storage(); + let mut storage = create_test_kv_storage(); // In order to add a tower record we need to associated registration receipt. let tower_id = get_random_user_id(); @@ -1133,7 +1127,7 @@ mod tests { #[test] fn test_store_pending_appointment_twice() { - let mut storage = create_test_storage(); + let mut storage = create_test_kv_storage(); // In order to add a tower record we need to associated registration receipt. let tower_id_1 = get_random_user_id(); @@ -1167,7 +1161,7 @@ mod tests { #[test] fn test_delete_pending_appointment() { - let mut storage = create_test_storage(); + let mut storage = create_test_kv_storage(); // In order to add a tower record we need to associated registration receipt. let tower_id = get_random_user_id(); @@ -1238,7 +1232,7 @@ mod tests { #[test] fn test_store_invalid_appointment() { - let mut storage = create_test_storage(); + let mut storage = create_test_kv_storage(); // In order to add a tower record we need to associated registration receipt. let tower_id = get_random_user_id(); @@ -1275,7 +1269,7 @@ mod tests { #[test] fn test_store_invalid_appointment_twice() { - let mut storage = create_test_storage(); + let mut storage = create_test_kv_storage(); // In order to add a tower record we need to associated registration receipt. let tower_id_1 = get_random_user_id(); @@ -1307,7 +1301,7 @@ mod tests { #[test] fn test_store_load_misbehaving_proof() { - let mut storage = create_test_storage(); + let mut storage = create_test_kv_storage(); // In order to add a tower record we need to associated registration receipt. let tower_id = get_random_user_id(); @@ -1348,7 +1342,7 @@ mod tests { #[test] fn test_store_load_non_existing_misbehaving_proof() { - let storage = create_test_storage(); + let storage = create_test_kv_storage(); assert!(storage .load_misbehaving_proof(get_random_user_id()) .is_none()); @@ -1356,7 +1350,7 @@ mod tests { #[test] fn test_store_exists_misbehaving_proof() { - let mut storage = create_test_storage(); + let mut storage = create_test_kv_storage(); // In order to add a tower record we need to associated registration receipt. let tower_id = get_random_user_id(); @@ -1397,7 +1391,7 @@ mod tests { #[test] fn test_exists_misbehaving_proof_false() { - let storage = create_test_storage(); + let storage = create_test_kv_storage(); assert!(!storage.exists_misbehaving_proof(get_random_user_id())); } } diff --git a/teos-ldk-client/src/storage/mod.rs b/teos-ldk-client/src/storage/mod.rs index 521537f2..d08aeb09 100644 --- a/teos-ldk-client/src/storage/mod.rs +++ b/teos-ldk-client/src/storage/mod.rs @@ -47,3 +47,11 @@ pub enum StorageConfig { sk: Vec, }, } + +#[cfg(test)] +fn create_test_kv_storage() -> KVStorage { + let store = MemoryStore::new().into_dyn_store(); + let sk = vec![0u8; 32]; // Test secret key + KVStorage::new(store, sk).unwrap() +} + From e2792cb59864c10a19d278a44303eb9a2807cd7a Mon Sep 17 00:00:00 2001 From: dzdidi Date: Thu, 20 Feb 2025 11:38:10 +0100 Subject: [PATCH 65/85] remove sqlite Signed-off-by: dzdidi --- teos-ldk-client/src/retrier.rs | 134 +- teos-ldk-client/src/storage/kv.rs | 38 +- teos-ldk-client/src/storage/mod.rs | 17 +- teos-ldk-client/src/storage/sql_storage.rs | 1315 -------------------- teos-ldk-client/src/wt_client.rs | 119 +- 5 files changed, 163 insertions(+), 1460 deletions(-) delete mode 100644 teos-ldk-client/src/storage/sql_storage.rs diff --git a/teos-ldk-client/src/retrier.rs b/teos-ldk-client/src/retrier.rs index c4791deb..19c85fea 100644 --- a/teos-ldk-client/src/retrier.rs +++ b/teos-ldk-client/src/retrier.rs @@ -576,9 +576,9 @@ mod tests { use crate::storage::{create_storage, StorageConfig}; - use bitcoin::secp256k1::{PublicKey, Secp256k1}; + use serde_json::json; - use tempdir::TempDir; + use tokio::sync::mpsc::unbounded_channel; use teos_common::errors; @@ -589,9 +589,10 @@ mod tests { generate_random_appointment, get_random_registration_receipt, get_registration_receipt_from_previous, }; - use teos_common::UserId; + use crate::net::http::ApiError; + use crate::storage::memory_store::MemoryStore; use crate::test_utils::get_dummy_add_appointment_response; const LONG_AUTO_RETRY_DELAY: u32 = 60; @@ -629,10 +630,11 @@ mod tests { async fn test_manage_retry_reachable() { let (tx, rx) = unbounded_channel(); let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); + let storage = create_storage(StorageConfig::KV { + kv_store: MemoryStore::new().into_dyn_store(), + sk: vec![0; 32], + }) + .unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -727,10 +729,11 @@ mod tests { async fn test_manage_retry_unreachable() { let (tx, rx) = unbounded_channel(); let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); + let storage = create_storage(StorageConfig::KV { + kv_store: MemoryStore::new().into_dyn_store(), + sk: vec![0; 32], + }) + .unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -858,10 +861,11 @@ mod tests { async fn test_manage_retry_rejected() { let (tx, rx) = unbounded_channel(); let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); + let storage = create_storage(StorageConfig::KV { + kv_store: MemoryStore::new().into_dyn_store(), + sk: vec![0; 32], + }) + .unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -966,10 +970,11 @@ mod tests { async fn test_manage_retry_misbehaving() { let (tx, rx) = unbounded_channel(); let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); + let storage = create_storage(StorageConfig::KV { + kv_store: MemoryStore::new().into_dyn_store(), + sk: vec![0; 32], + }) + .unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -1061,11 +1066,12 @@ mod tests { #[tokio::test] async fn test_manage_retry_abandoned() { let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); let (tx, rx) = unbounded_channel(); - let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); + let storage = create_storage(StorageConfig::KV { + kv_store: MemoryStore::new().into_dyn_store(), + sk: vec![0; 32], + }) + .unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -1109,10 +1115,11 @@ mod tests { async fn test_manage_retry_subscription_error() { let (tx, rx) = unbounded_channel(); let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); + let storage = create_storage(StorageConfig::KV { + kv_store: MemoryStore::new().into_dyn_store(), + sk: vec![0; 32], + }) + .unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -1229,16 +1236,16 @@ mod tests { // - A wake up call with no data let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", 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 db_path = tmp_path.path().join("watchtower.db"); - let mut storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); + let mut storage = create_storage(StorageConfig::KV { + kv_store: MemoryStore::new().into_dyn_store(), + sk: vec![0; 32], + }) + .unwrap(); let receipt = get_random_registration_receipt(); storage .store_tower_record(tower_id, "http://unreachable.tower", &receipt) @@ -1387,10 +1394,11 @@ mod tests { let (tower_sk, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); + let storage = create_storage(StorageConfig::KV { + kv_store: MemoryStore::new().into_dyn_store(), + sk: vec![0; 32], + }) + .unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -1441,10 +1449,11 @@ mod tests { let (_, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); + let storage = create_storage(StorageConfig::KV { + kv_store: MemoryStore::new().into_dyn_store(), + sk: vec![0; 32], + }) + .unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -1469,10 +1478,11 @@ mod tests { let (_, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); + let storage = create_storage(StorageConfig::KV { + kv_store: MemoryStore::new().into_dyn_store(), + sk: vec![0; 32], + }) + .unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -1525,10 +1535,11 @@ mod tests { let (_, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); + let storage = create_storage(StorageConfig::KV { + kv_store: MemoryStore::new().into_dyn_store(), + sk: vec![0; 32], + }) + .unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -1561,10 +1572,11 @@ mod tests { let (_, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); + let storage = create_storage(StorageConfig::KV { + kv_store: MemoryStore::new().into_dyn_store(), + sk: vec![0; 32], + }) + .unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -1620,10 +1632,11 @@ mod tests { let (_, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); + let storage = create_storage(StorageConfig::KV { + kv_store: MemoryStore::new().into_dyn_store(), + sk: vec![0; 32], + }) + .unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, @@ -1685,10 +1698,11 @@ mod tests { let (_, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); + let storage = create_storage(StorageConfig::KV { + kv_store: MemoryStore::new().into_dyn_store(), + sk: vec![0; 32], + }) + .unwrap(); let wt_client = Arc::new(Mutex::new( WTClient::new(storage, keypair.0, unbounded_channel().0).await, diff --git a/teos-ldk-client/src/storage/kv.rs b/teos-ldk-client/src/storage/kv.rs index 0cc51d37..5123b02b 100644 --- a/teos-ldk-client/src/storage/kv.rs +++ b/teos-ldk-client/src/storage/kv.rs @@ -463,16 +463,19 @@ impl Persister for KVStorage { tower_id: TowerId, appointment: &Appointment, ) -> Result<(), PersisterError> { - if self.store.read( - PRIMARY_NAMESPACE, - NS_PENDING_APPOINTMENTS, - &format!("{}:{}", tower_id, appointment.locator), - ).is_ok() { + if self + .store + .read( + PRIMARY_NAMESPACE, + NS_PENDING_APPOINTMENTS, + &format!("{}:{}", tower_id, appointment.locator), + ) + .is_ok() + { return Err(PersisterError::Other(format!( "{}:{}", - tower_id, - appointment.locator - ))) + tower_id, appointment.locator + ))); } self.store @@ -548,16 +551,19 @@ impl Persister for KVStorage { tower_id: TowerId, appointment: &Appointment, ) -> Result<(), PersisterError> { - if self.store.read( - PRIMARY_NAMESPACE, - NS_INVALID_APPOINTMENTS, - &format!("{}:{}", tower_id, appointment.locator), - ).is_ok() { + if self + .store + .read( + PRIMARY_NAMESPACE, + NS_INVALID_APPOINTMENTS, + &format!("{}:{}", tower_id, appointment.locator), + ) + .is_ok() + { return Err(PersisterError::Other(format!( "{}:{}", - tower_id, - appointment.locator - ))) + tower_id, appointment.locator + ))); } self.store diff --git a/teos-ldk-client/src/storage/mod.rs b/teos-ldk-client/src/storage/mod.rs index d08aeb09..8f83e395 100644 --- a/teos-ldk-client/src/storage/mod.rs +++ b/teos-ldk-client/src/storage/mod.rs @@ -1,18 +1,14 @@ pub mod persister; -mod sql_storage; -use std::path::PathBuf; use std::sync::Arc; pub use crate::storage::persister::{Persister, PersisterError}; -use sql_storage::DBM; - mod kv; use kv::{DynStore, KVStorage}; #[cfg(test)] -mod memory_store; +pub mod memory_store; #[cfg(test)] pub use memory_store::MemoryStore; @@ -21,13 +17,6 @@ pub fn create_storage( config: StorageConfig, ) -> Result, PersisterError> { match config { - StorageConfig::SQL { db_path } => match DBM::new(&db_path) { - Ok(storage) => Ok(Box::new(storage)), - Err(e) => Err(PersisterError::Other(format!( - "Error creating storage: {}", - e - ))), - }, StorageConfig::KV { kv_store, sk } => match KVStorage::new(kv_store, sk) { Ok(storage) => Ok(Box::new(storage)), Err(e) => Err(PersisterError::Other(format!( @@ -39,9 +28,6 @@ pub fn create_storage( } pub enum StorageConfig { - SQL { - db_path: PathBuf, - }, KV { kv_store: Arc, sk: Vec, @@ -54,4 +40,3 @@ fn create_test_kv_storage() -> KVStorage { let sk = vec![0u8; 32]; // Test secret key KVStorage::new(store, sk).unwrap() } - diff --git a/teos-ldk-client/src/storage/sql_storage.rs b/teos-ldk-client/src/storage/sql_storage.rs deleted file mode 100644 index 93d2eace..00000000 --- a/teos-ldk-client/src/storage/sql_storage.rs +++ /dev/null @@ -1,1315 +0,0 @@ -use std::collections::{HashMap, HashSet}; -use std::iter::FromIterator; -use std::path::PathBuf; - -use rusqlite::{params, Connection, Error as SqliteError}; - -use teos_common::appointment::{Appointment, Locator}; -use teos_common::dbm::{DatabaseConnection, DatabaseManager}; -use teos_common::receipts::{AppointmentReceipt, RegistrationReceipt}; -use teos_common::{TowerId, UserId}; - -use crate::storage::persister::{Persister, PersisterError}; - -use crate::{AppointmentStatus, MisbehaviorProof, TowerInfo, TowerStatus, TowerSummary}; - -impl From for PersisterError { - fn from(error: SqliteError) -> Self { - PersisterError::Other(error.to_string()) - } -} - -const TABLES: [&str; 8] = [ - "CREATE TABLE IF NOT EXISTS towers ( - tower_id INT PRIMARY KEY, - net_addr TEXT NOT NULL, - available_slots INT NOT NULL -)", - "CREATE TABLE IF NOT EXISTS appointments ( - locator INT PRIMARY KEY, - encrypted_blob BLOB, - to_self_delay INT -)", - "CREATE TABLE IF NOT EXISTS pending_appointments ( - locator INT NOT NULL, - tower_id INT NOT NULL, - PRIMARY KEY (locator, tower_id), - FOREIGN KEY(locator) - REFERENCES appointments(locator) - ON DELETE CASCADE - FOREIGN KEY(tower_id) - REFERENCES towers(tower_id) - ON DELETE CASCADE -)", - "CREATE TABLE IF NOT EXISTS invalid_appointments ( - locator INT NOT NULL, - tower_id INT NOT NULL, - PRIMARY KEY (locator, tower_id), - FOREIGN KEY(locator) - REFERENCES appointments(locator) - ON DELETE CASCADE - FOREIGN KEY(tower_id) - REFERENCES towers(tower_id) - ON DELETE CASCADE -)", - "CREATE TABLE IF NOT EXISTS registration_receipts ( - tower_id INT NOT NULL, - available_slots INT NOT NULL, - subscription_start INT NOT NULL, - subscription_expiry INT NOT NULL, - signature BLOB NOT NULL, - PRIMARY KEY (tower_id, subscription_expiry), - FOREIGN KEY(tower_id) - REFERENCES towers(tower_id) - ON DELETE CASCADE -)", - "CREATE TABLE IF NOT EXISTS appointment_receipts ( - locator INT NOT NULL, - tower_id INT NOT NULL, - start_block INT NOT NULL, - user_signature BLOB NOT NULL, - tower_signature BLOB NOT NULL, - PRIMARY KEY (locator, tower_id), - FOREIGN KEY(tower_id) - REFERENCES towers(tower_id) - ON DELETE CASCADE -)", - "CREATE TABLE IF NOT EXISTS misbehaving_proofs ( - tower_id INT PRIMARY KEY, - locator INT NOT NULL, - recovered_id INT NOT NULL, - FOREIGN KEY(locator, tower_id) - REFERENCES appointment_receipts(locator, tower_id) - ON DELETE CASCADE -)", - "CREATE TABLE IF NOT EXISTS keys ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - key INT NOT NULL -)", -]; - -/// Component in charge of interacting with the underlying database. -/// -/// Currently works for `SQLite`. `PostgreSQL` should also be added in the future. -#[derive(Debug)] -pub struct DBM { - /// The underlying database connection. - connection: Connection, -} - -impl DatabaseConnection for DBM { - fn get_connection(&self) -> &Connection { - &self.connection - } - - fn get_mut_connection(&mut self) -> &mut Connection { - &mut self.connection - } -} - -impl DBM { - /// Creates a new [DBM] instance. - pub fn new(db_path: &PathBuf) -> Result { - let connection = Connection::open(db_path)?; - connection.execute("PRAGMA foreign_keys=1;", [])?; - let mut dbm = Self { connection }; - dbm.create_tables(Vec::from_iter(TABLES))?; - - Ok(dbm) - } - - /// Checks whether a misbehaving proof exists for a given tower. - fn exists_misbehaving_proof(&self, tower_id: TowerId) -> bool { - let mut misbehaving_stmt = self - .connection - .prepare("SELECT tower_id FROM misbehaving_proofs WHERE tower_id = ?") - .unwrap(); - misbehaving_stmt.exists([tower_id.to_vec()]).unwrap() - } - - /// Loads the misbehaving proof for a given tower from the database (if found). - 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 = ?") - .unwrap(); - - misbehaving_stmt - .query_row([tower_id.to_vec()], |row| { - let locator = Locator::from_slice(&row.get::<_, Vec>(0).unwrap()).unwrap(); - let recovered_id = TowerId::from_slice(&row.get::<_, Vec>(1).unwrap()).unwrap(); - Ok((locator, recovered_id)) - }) - .map(|(locator, recovered_id)| { - let mut receipt_stmt = self - .connection - .prepare( - "SELECT start_block, user_signature, tower_signature - FROM appointment_receipts - WHERE locator = ?1 AND tower_id = ?2", - ) - .unwrap(); - let receipt = receipt_stmt - .query_row([locator.to_vec(), tower_id.to_vec()], |row| { - let start_block = row.get::<_, u32>(0).unwrap(); - let user_signature = row.get::<_, String>(1).unwrap(); - let tower_signature = row.get::<_, String>(2).unwrap(); - Ok(AppointmentReceipt::with_signature( - user_signature, - start_block, - tower_signature, - )) - }) - .unwrap(); - MisbehaviorProof::new(locator, receipt, recovered_id) - }) - .ok() - } - - /// Stores an appointment into the database. - /// - /// Appointments are only stored as a whole when they are pending or invalid. - /// Accepted appointments are simplified in the form of an appointment receipt. - fn store_appointment( - tx: &rusqlite::Transaction, - appointment: &Appointment, - ) -> Result { - tx.execute( - "INSERT INTO appointments (locator, encrypted_blob, to_self_delay) VALUES (?1, ?2, ?3)", - params![ - appointment.locator.to_vec(), - appointment.encrypted_blob, - appointment.to_self_delay - ], - ) - } -} - -impl Persister for DBM { - /// Stores a tower record into the database alongside the corresponding registration receipt. - /// - /// This function MUST be guarded against inserting duplicate (tower_id, subscription_expiry) pairs. - /// This is currently done in WTClient::add_update_tower. - fn store_tower_record( - &mut self, - tower_id: TowerId, - net_addr: &str, - receipt: &RegistrationReceipt, - ) -> Result<(), PersisterError> { - let tx = self.get_mut_connection().transaction().unwrap(); - tx.execute( - "INSERT INTO towers (tower_id, net_addr, available_slots) - VALUES (?1, ?2, ?3) - ON CONFLICT (tower_id) DO UPDATE SET net_addr = ?2, available_slots = ?3", - params![tower_id.to_vec(), net_addr, receipt.available_slots()], - )?; - tx.execute( - "INSERT INTO registration_receipts (tower_id, available_slots, subscription_start, subscription_expiry, signature) - VALUES (?1, ?2, ?3, ?4, ?5)", - params![tower_id.to_vec(), receipt.available_slots(), receipt.subscription_start(), receipt.subscription_expiry(), receipt.signature()])?; - - tx.commit()?; - Ok(()) - } - - /// Loads a tower record from the database. - /// - /// 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. - 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 - FROM towers as t, registration_receipts as r - WHERE t.tower_id = r.tower_id AND t.tower_id = ?1 AND r.subscription_expiry = (SELECT MAX(subscription_expiry) - FROM registration_receipts - WHERE tower_id = ?1)") - .unwrap(); - - let mut tower = stmt - .query_row([tower_id.to_vec()], |row| { - let net_addr: String = row.get(0).unwrap(); - let available_slots: u32 = row.get(1).unwrap(); - let subscription_start: u32 = row.get(2).unwrap(); - let subscription_expiry: u32 = row.get(3).unwrap(); - Ok(TowerInfo::new( - net_addr, - available_slots, - subscription_start, - subscription_expiry, - self.load_appointment_receipts(tower_id), - self.load_appointments(tower_id, AppointmentStatus::Pending), - self.load_appointments(tower_id, AppointmentStatus::Invalid), - )) - }) - .ok()?; - - 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; - } - - Some(tower) - } - - /// Loads the latest registration receipt for a given tower. - /// - /// Latests is determined by the one with the `subscription_expiry` further into the future. - fn load_registration_receipt( - &self, - tower_id: TowerId, - user_id: UserId, - ) -> Option { - let mut stmt = self - .connection - .prepare( - "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 - WHERE tower_id = ?1)", - ) - .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, - )) - }) - .ok() - } - - /// 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. - fn remove_tower_record(&self, tower_id: TowerId) -> Result<(), PersisterError> { - let query = "DELETE FROM towers WHERE tower_id=?"; - match self.load_tower_record(tower_id) { - None => Err(PersisterError::NotFound(format!("tower_id: {tower_id}"))), - Some(_tower) => match self.connection.execute(query, params![tower_id.to_vec()]) { - Ok(_) => Ok(()), - Err(e) => Err(PersisterError::Other(e.to_string())), - }, - } - } - - /// Loads all tower records from the database. - fn load_towers(&self) -> HashMap { - let mut towers = HashMap::new(); - let mut stmt = self - .connection - .prepare("SELECT tw.tower_id, tw.net_addr, tw.available_slots, rr.subscription_start, rr.subscription_expiry - FROM towers AS tw - JOIN registration_receipts AS rr - JOIN (SELECT tower_id, MAX(subscription_expiry) AS max_se - FROM registration_receipts - GROUP BY tower_id) AS max_rrs ON (tw.tower_id = rr.tower_id) - AND (rr.tower_id = max_rrs.tower_id) - AND (rr.subscription_expiry = max_rrs.max_se)") - .unwrap(); - let mut rows = stmt.query([]).unwrap(); - - while let Ok(Some(row)) = rows.next() { - let raw_towerid: Vec = row.get(0).unwrap(); - let tower_id = TowerId::from_slice(&raw_towerid).unwrap(); - let net_addr: String = row.get(1).unwrap(); - let available_slots: u32 = row.get(2).unwrap(); - let start: u32 = row.get(3).unwrap(); - let expiry: u32 = row.get(4).unwrap(); - - let mut tower = TowerSummary::with_appointments( - net_addr, - available_slots, - start, - expiry, - self.load_appointment_locators(tower_id, AppointmentStatus::Pending), - self.load_appointment_locators(tower_id, AppointmentStatus::Invalid), - ); - - 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; - } - - towers.insert(tower_id, tower); - } - - towers - } - - /// Stores an appointments receipt into the database representing an appointment accepted by a given tower. - fn store_appointment_receipt( - &mut self, - tower_id: TowerId, - locator: Locator, - available_slots: u32, - receipt: &AppointmentReceipt, - ) -> Result<(), PersisterError> { - let tx = self.get_mut_connection().transaction().unwrap(); - tx.execute( - "INSERT INTO appointment_receipts (locator, tower_id, start_block, user_signature, tower_signature) - VALUES (?1, ?2, ?3, ?4, ?5)", - params![ - locator.to_vec(), - tower_id.to_vec(), - receipt.start_block(), - receipt.user_signature(), - receipt.signature() - ], - )?; - tx.execute( - "UPDATE towers SET available_slots=?1 WHERE tower_id=?2", - params![available_slots, tower_id.to_vec()], - )?; - tx.commit()?; - Ok(()) - } - - /// Loads a given appointment receipt of a given tower from the database. - fn load_appointment_receipt( - &self, - tower_id: TowerId, - locator: Locator, - ) -> Option { - let mut stmt = self - .connection - .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>(0).unwrap(); - let user_sig = row.get::<_, String>(1).unwrap(); - let tower_sig = row.get::<_, String>(2).unwrap(); - - Ok(AppointmentReceipt::with_signature( - user_sig, - start_block, - tower_sig, - )) - }) - .ok() - } - - /// 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. - fn load_appointment_receipts(&self, tower_id: TowerId) -> HashMap { - let mut receipts = HashMap::new(); - let mut stmt = self - .connection - .prepare("SELECT locator, tower_signature FROM appointment_receipts WHERE tower_id = ?") - .unwrap(); - let mut rows = stmt.query([tower_id.to_vec()]).unwrap(); - - while let Ok(Some(row)) = rows.next() { - let locator = Locator::from_slice(&row.get::<_, Vec>(0).unwrap()).unwrap(); - let signature = row.get::<_, String>(1).unwrap(); - - receipts.insert(locator, signature); - } - - receipts - } - - /// Loads a collection of locators from the database entry associated to a given tower. - /// - /// The loaded locators can be loaded either from appointment_receipts, pending_appointments or invalid_appointments - /// depending on `status`. - fn load_appointment_locators( - &self, - tower_id: TowerId, - status: AppointmentStatus, - ) -> HashSet { - let status = match status { - AppointmentStatus::Accepted => "appointment_receipts", - AppointmentStatus::Pending => "pending_appointments", - AppointmentStatus::Invalid => "invalid_appointments", - }; - let mut appointments = HashSet::new(); - // TODO: Can this be prepared instead of formatted (using ?1 seems to fail)? - let mut stmt = self - .connection - .prepare(&format!("SELECT locator FROM {status} WHERE tower_id = ?")) - .unwrap(); - - let mut rows = stmt.query(params![tower_id.to_vec()]).unwrap(); - while let Ok(Some(inner_row)) = rows.next() { - appointments - .insert(Locator::from_slice(&inner_row.get::<_, Vec>(0).unwrap()).unwrap()); - } - - appointments - } - - /// Loads an appointment from the database. - fn load_appointment(&self, locator: Locator) -> Option { - 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)) - }) - .ok() - } - - /// Stores a pending appointment into the database. - /// - /// A pending appointment is an appointment that was sent to a tower when it was unreachable. - /// This data is stored so it can be resent once the tower comes back online. - /// Internally calls [Self::store_appointment]. - fn store_pending_appointment( - &mut self, - tower_id: TowerId, - appointment: &Appointment, - ) -> Result<(), PersisterError> { - let tx = self.get_mut_connection().transaction().unwrap(); - - // If the appointment already exists (because it was added by another tower as either pending or invalid) we simply - // ignore the error. - Self::store_appointment(&tx, appointment).ok(); - tx.execute( - "INSERT INTO pending_appointments (locator, tower_id) VALUES (?1, ?2)", - params![appointment.locator.to_vec(), tower_id.to_vec(),], - )?; - - tx.commit()?; - - Ok(()) - } - - /// Removes a pending appointment from the database. - /// - /// If the pending appointment is the only instance of the appointment, the appointment will also be deleted form the appointments table. - fn delete_pending_appointment( - &mut self, - tower_id: TowerId, - locator: Locator, - ) -> Result<(), PersisterError> { - // We will delete data from pending_appointments or from appointments depending on whether the later has a single reference - // to it or not. If that's the case, deleting the entry from appointments will trigger a cascade deletion of the entry in pending. - // If there are other references, this will be deleted when removing the last one. - let count = { - let mut stmt = self - .connection - .prepare("SELECT COUNT(*) FROM pending_appointments WHERE locator=?") - .unwrap(); - let pending = stmt - .query_row(params![locator.to_vec()], |row| row.get::<_, u32>(0)) - .unwrap(); - - let mut stmt = self - .connection - .prepare("SELECT COUNT(*) FROM invalid_appointments WHERE locator=?") - .unwrap(); - let invalid = stmt - .query_row(params![locator.to_vec()], |row| row.get::<_, u32>(0)) - .unwrap_or(0); - - pending + invalid - }; - - let tx = self.get_mut_connection().transaction().unwrap(); - if count == 1 { - tx.execute( - "DELETE FROM appointments WHERE locator=?", - params![locator.to_vec()], - )?; - } else { - tx.execute( - "DELETE FROM pending_appointments WHERE locator=?1 AND tower_id=?2", - params![locator.to_vec(), tower_id.to_vec()], - )?; - }; - tx.commit()?; - - Ok(()) - } - - /// Stores an invalid appointment into the database. - /// - /// An invalid appointment is an appointment that was rejected by the tower. - /// Storing this data may allow us to see what was the issue and send the data later on. - /// Internally calls [Self::store_appointment]. - fn store_invalid_appointment( - &mut self, - tower_id: TowerId, - appointment: &Appointment, - ) -> Result<(), PersisterError> { - let tx = self.get_mut_connection().transaction().unwrap(); - - // If the appointment already exists (because it was added by another tower as either pending or invalid) we simply - // ignore the error. - Self::store_appointment(&tx, appointment).ok(); - tx.execute( - "INSERT INTO invalid_appointments (locator, tower_id) VALUES (?1, ?2)", - params![appointment.locator.to_vec(), tower_id.to_vec(),], - )?; - - tx.commit()?; - - Ok(()) - } - - /// Loads non finalized appointments from the database for a given tower based on a status flag. - /// - /// This is meant to be used only for pending and invalid appointments, if the method is called for - /// accepted appointment, an empty collection will be returned. - fn load_appointments(&self, tower_id: TowerId, status: AppointmentStatus) -> Vec { - let table = match status { - AppointmentStatus::Accepted => return Vec::new(), - AppointmentStatus::Pending => "pending_appointments", - AppointmentStatus::Invalid => "invalid_appointments", - }; - - 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, {table} as t WHERE a.locator = t.locator AND t.tower_id = ?")) - .unwrap(); - let mut rows = stmt.query([tower_id.to_vec()]).unwrap(); - - while let Ok(Some(row)) = rows.next() { - let locator = Locator::from_slice(&row.get::<_, Vec>(0).unwrap()).unwrap(); - let encrypted_blob = row.get::<_, Vec>(1).unwrap(); - let to_self_delay = row.get::<_, u32>(2).unwrap(); - - appointments.push(Appointment::new(locator, encrypted_blob, to_self_delay)); - } - - appointments - } - - /// Stores a misbehaving proof into the database. - /// - /// A misbehaving proof is proof that the tower has signed an appointment using a key different - /// than the one advertised to the user when they registered. - fn store_misbehaving_proof( - &mut self, - tower_id: TowerId, - proof: &MisbehaviorProof, - ) -> Result<(), PersisterError> { - let tx = self.get_mut_connection().transaction().unwrap(); - tx.execute( - "INSERT INTO appointment_receipts (tower_id, locator, start_block, user_signature, tower_signature) - VALUES (?1, ?2, ?3, ?4, ?5)", - params![ - tower_id.to_vec(), - proof.locator.to_vec(), - proof.appointment_receipt.start_block(), - proof.appointment_receipt.user_signature(), - proof.appointment_receipt.signature() - ], - )?; - tx.execute( - "INSERT INTO misbehaving_proofs (tower_id, locator, recovered_id) VALUES (?1, ?2, ?3)", - params![ - tower_id.to_vec(), - proof.locator.to_vec(), - proof.recovered_id.to_vec() - ], - )?; - - tx.commit()?; - - Ok(()) - } - - fn appointment_exists(&self, locator: Locator) -> bool { - let mut stmt = self - .connection - .prepare("SELECT * FROM appointments WHERE locator=? ") - .unwrap(); - stmt.exists(params![locator.to_vec()]).unwrap() - } - - 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() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use teos_common::test_utils::{ - generate_random_appointment, get_random_registration_receipt, get_random_user_id, - get_registration_receipt_from_previous, - }; - - impl DBM { - pub(crate) fn in_memory() -> Result { - let connection = Connection::open_in_memory()?; - connection.execute("PRAGMA foreign_keys=1;", [])?; - let mut dbm = Self { connection }; - dbm.create_tables(Vec::from_iter(TABLES))?; - - Ok(dbm) - } - } - - #[test] - fn test_create_tables() { - let connection = Connection::open_in_memory().unwrap(); - let mut dbm = DBM { connection }; - dbm.create_tables(Vec::from_iter(TABLES)).unwrap(); - } - - #[test] - fn test_store_load_tower_record() { - let mut dbm = DBM::in_memory().unwrap(); - - // In order to add a tower record we need to associated registration receipt. - let tower_id = get_random_user_id(); - let net_addr = "talaia.watch"; - - let receipt = get_random_registration_receipt(); - let tower_info = TowerInfo::new( - net_addr.to_owned(), - receipt.available_slots(), - receipt.subscription_start(), - receipt.subscription_expiry(), - HashMap::new(), - Vec::new(), - Vec::new(), - ); - - // Check the loaded data matches the in memory data - dbm.store_tower_record(tower_id, net_addr, &receipt) - .unwrap(); - assert_eq!(dbm.load_tower_record(tower_id).unwrap(), tower_info); - } - - #[test] - fn test_load_registration_receipt() { - let mut dbm = DBM::in_memory().unwrap(); - - // Registration receipts are stored alongside tower records when the register command is called - let tower_id = get_random_user_id(); - let net_addr = "talaia.watch"; - let receipt = get_random_registration_receipt(); - - // Check the receipt was stored - dbm.store_tower_record(tower_id, net_addr, &receipt) - .unwrap(); - assert_eq!( - dbm.load_registration_receipt(tower_id, receipt.user_id()) - .unwrap(), - receipt - ); - - // Add another receipt for the same tower with a higher expiry and check this last one is loaded - let middle_receipt = get_registration_receipt_from_previous(&receipt); - let latest_receipt = get_registration_receipt_from_previous(&middle_receipt); - - dbm.store_tower_record(tower_id, net_addr, &latest_receipt) - .unwrap(); - assert_eq!( - dbm.load_registration_receipt(tower_id, latest_receipt.user_id()) - .unwrap(), - latest_receipt - ); - - // Add a final one with a lower expiry and check the last is still loaded - dbm.store_tower_record(tower_id, net_addr, &middle_receipt) - .unwrap(); - assert_eq!( - dbm.load_registration_receipt(tower_id, latest_receipt.user_id()) - .unwrap(), - latest_receipt - ); - } - - #[test] - fn test_load_same_registration_receipt() { - let mut dbm = DBM::in_memory().unwrap(); - - // Registration receipts are stored alongside tower records when the register command is called - let tower_id = get_random_user_id(); - let net_addr = "talaia.watch"; - let receipt = get_random_registration_receipt(); - - // Store it once - dbm.store_tower_record(tower_id, net_addr, &receipt) - .unwrap(); - assert_eq!( - dbm.load_registration_receipt(tower_id, receipt.user_id()) - .unwrap(), - receipt - ); - - // Store the same again, this should fail due to UNIQUE PK constrains. - // Notice store_tower_record is guarded against this by WTClient::add_update_tower though. - assert!(matches!( - dbm.store_tower_record(tower_id, net_addr, &receipt), - Err { .. } - )); - } - - #[test] - fn test_load_nonexistent_tower_record() { - let dbm = DBM::in_memory().unwrap(); - - // If the tower does not exists, `load_tower` will fail. - let tower_id = get_random_user_id(); - assert!(dbm.load_tower_record(tower_id).is_none()); - } - - #[test] - fn test_store_load_towers() { - let mut dbm = DBM::in_memory().unwrap(); - let mut towers = HashMap::new(); - - // In order to add a tower record we need to associated registration receipt. - for _ in 0..10 { - let tower_id = get_random_user_id(); - let net_addr = "talaia.watch"; - let mut receipt = get_random_registration_receipt(); - dbm.store_tower_record(tower_id, net_addr, &receipt) - .unwrap(); - - // Add not only one registration receipt to test if the tower retrieves the one with furthest expiry date. - for _ in 0..10 { - receipt = get_registration_receipt_from_previous(&receipt); - dbm.store_tower_record(tower_id, net_addr, &receipt) - .unwrap(); - } - - towers.insert( - tower_id, - TowerSummary::new( - net_addr.to_owned(), - receipt.available_slots(), - receipt.subscription_start(), - receipt.subscription_expiry(), - ), - ); - } - - assert_eq!(dbm.load_towers(), towers); - } - - #[test] - fn test_load_towers_empty() { - // If there are no towers in the database, `load_towers` should return an empty map. - let dbm = DBM::in_memory().unwrap(); - 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(); - - let tower_id = get_random_user_id(); - let err = dbm.remove_tower_record(tower_id).unwrap_err(); - assert_eq!( - err, - PersisterError::NotFound(format!("tower_id: {tower_id}")) - ); - } - - #[test] - fn test_store_load_appointment_receipts() { - let mut dbm = DBM::in_memory().unwrap(); - - // In order to add a tower record we need to associated registration receipt. - let tower_id = get_random_user_id(); - let net_addr = "talaia.watch"; - - let receipt = get_random_registration_receipt(); - let mut tower_summary = TowerSummary::new( - net_addr.to_owned(), - receipt.available_slots(), - receipt.subscription_start(), - receipt.subscription_expiry(), - ); - dbm.store_tower_record(tower_id, net_addr, &receipt) - .unwrap(); - - // Add some appointment receipts and check they match - let mut receipts = HashMap::new(); - for _ in 0..5 { - let appointment = generate_random_appointment(None); - let user_signature = "user_signature"; - let appointment_receipt = AppointmentReceipt::with_signature( - user_signature.to_owned(), - 42, - "tower_signature".to_owned(), - ); - - tower_summary.available_slots -= 1; - - dbm.store_appointment_receipt( - tower_id, - appointment.locator, - tower_summary.available_slots, - &appointment_receipt, - ) - .unwrap(); - receipts.insert( - appointment.locator, - appointment_receipt.signature().unwrap(), - ); - } - - 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!(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"; - let receipt = get_random_registration_receipt(); - dbm.store_tower_record(tower_id, net_addr, &receipt) - .unwrap(); - - assert!(dbm - .load_appointment_receipt(tower_id, appointment.locator) - .is_none()); - - // Add both - let tower_summary = TowerSummary::new( - net_addr.to_owned(), - receipt.available_slots(), - receipt.subscription_start(), - receipt.subscription_expiry(), - ); - let appointment_receipt = AppointmentReceipt::with_signature( - "user_signature".to_owned(), - 42, - "tower_signature".to_owned(), - ); - 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` - let mut dbm = DBM::in_memory().unwrap(); - - // We first need to add a tower record to the database so we can add some associated data. - let tower_id = get_random_user_id(); - let net_addr = "talaia.watch"; - - let receipt = get_random_registration_receipt(); - let tower_summary = TowerSummary::new( - net_addr.to_owned(), - receipt.available_slots(), - receipt.subscription_start(), - receipt.subscription_expiry(), - ); - dbm.store_tower_record(tower_id, net_addr, &receipt) - .unwrap(); - - // Create all types of appointments and store them in the db. - let user_signature = "user_signature"; - let mut receipts = HashSet::new(); - let mut pending_appointments = HashSet::new(); - let mut invalid_appointments = HashSet::new(); - for _ in 0..5 { - let appointment = generate_random_appointment(None); - let appointment_receipt = AppointmentReceipt::with_signature( - user_signature.to_owned(), - 42, - "tower_signature".to_owned(), - ); - let pending_appointment = generate_random_appointment(None); - let invalid_appointment = generate_random_appointment(None); - - dbm.store_appointment_receipt( - tower_id, - appointment.locator, - tower_summary.available_slots, - &appointment_receipt, - ) - .unwrap(); - dbm.store_pending_appointment(tower_id, &pending_appointment) - .unwrap(); - dbm.store_invalid_appointment(tower_id, &invalid_appointment) - .unwrap(); - - receipts.insert(appointment.locator); - pending_appointments.insert(pending_appointment.locator); - invalid_appointments.insert(invalid_appointment.locator); - } - - // Pull data from the db and check it matches the expected data - assert_eq!( - dbm.load_appointment_locators(tower_id, AppointmentStatus::Accepted), - receipts - ); - assert_eq!( - dbm.load_appointment_locators(tower_id, AppointmentStatus::Pending), - pending_appointments - ); - assert_eq!( - dbm.load_appointment_locators(tower_id, AppointmentStatus::Invalid), - invalid_appointments - ); - } - - #[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!(loaded_appointment.is_none()); - } - - #[test] - fn test_store_pending_appointment() { - let mut dbm = DBM::in_memory().unwrap(); - - // In order to add a tower record we need to associated registration receipt. - let tower_id = get_random_user_id(); - let net_addr = "talaia.watch"; - - let receipt = get_random_registration_receipt(); - let mut tower_summary = TowerSummary::new( - net_addr.to_owned(), - receipt.available_slots(), - receipt.subscription_start(), - receipt.subscription_expiry(), - ) - .with_status(TowerStatus::TemporaryUnreachable); - dbm.store_tower_record(tower_id, net_addr, &receipt) - .unwrap(); - - // Add some pending appointments and check they match - for _ in 0..5 { - let appointment = generate_random_appointment(None); - - tower_summary - .pending_appointments - .insert(appointment.locator); - - dbm.store_pending_appointment(tower_id, &appointment) - .unwrap(); - assert_eq!( - TowerSummary::from(dbm.load_tower_record(tower_id).unwrap()), - tower_summary - ); - } - } - - #[test] - fn test_store_pending_appointment_twice() { - let mut dbm = DBM::in_memory().unwrap(); - - // In order to add a tower record we need to associated registration receipt. - let tower_id_1 = get_random_user_id(); - let tower_id_2 = get_random_user_id(); - let net_addr = "talaia.watch"; - - let receipt = get_random_registration_receipt(); - dbm.store_tower_record(tower_id_1, net_addr, &receipt) - .unwrap(); - dbm.store_tower_record(tower_id_2, net_addr, &receipt) - .unwrap(); - - // If the same appointment is stored twice (by different towers) it should go through - // Since the appointment data will be stored only once and this will create two references - let appointment = generate_random_appointment(None); - dbm.store_pending_appointment(tower_id_1, &appointment) - .unwrap(); - dbm.store_pending_appointment(tower_id_2, &appointment) - .unwrap(); - - // If this is called twice with for the same tower it will fail, since two identical references - // can not exist. This is intended behavior and should not happen - assert!(dbm - .store_pending_appointment(tower_id_2, &appointment) - .is_err()); - } - - #[test] - fn test_delete_pending_appointment() { - let mut dbm = DBM::in_memory().unwrap(); - - // In order to add a tower record we need to associated registration receipt. - 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(); - - // Add a single one, remove it later - let appointment = generate_random_appointment(None); - dbm.store_pending_appointment(tower_id, &appointment) - .unwrap(); - assert!(dbm - .delete_pending_appointment(tower_id, appointment.locator) - .is_ok()); - - // The appointment should be completely gone - assert!(!dbm - .load_appointment_locators(tower_id, AppointmentStatus::Pending) - .contains(&appointment.locator)); - assert!(!dbm.appointment_exists(appointment.locator)); - - // Try again with more than one reference - let another_tower_id = get_random_user_id(); - dbm.store_tower_record(another_tower_id, net_addr, &receipt) - .unwrap(); - - // Add two - dbm.store_pending_appointment(tower_id, &appointment) - .unwrap(); - dbm.store_pending_appointment(another_tower_id, &appointment) - .unwrap(); - // Delete one - assert!(dbm - .delete_pending_appointment(tower_id, appointment.locator) - .is_ok()); - // Check - assert!(!dbm - .load_appointment_locators(tower_id, AppointmentStatus::Pending) - .contains(&appointment.locator)); - assert!(dbm - .load_appointment_locators(another_tower_id, AppointmentStatus::Pending) - .contains(&appointment.locator)); - assert!(dbm.appointment_exists(appointment.locator)); - - // Add an invalid reference and check again - dbm.store_invalid_appointment(tower_id, &appointment) - .unwrap(); - assert!(dbm - .delete_pending_appointment(another_tower_id, appointment.locator) - .is_ok()); - assert!(!dbm - .load_appointment_locators(another_tower_id, AppointmentStatus::Pending) - .contains(&appointment.locator)); - assert!(dbm - .load_appointment_locators(tower_id, AppointmentStatus::Invalid) - .contains(&appointment.locator)); - assert!(dbm.appointment_exists(appointment.locator)); - } - - #[test] - fn test_store_invalid_appointment() { - let mut dbm = DBM::in_memory().unwrap(); - - // In order to add a tower record we need to associated registration receipt. - let tower_id = get_random_user_id(); - let net_addr = "talaia.watch"; - - let receipt = get_random_registration_receipt(); - let mut tower_summary = TowerSummary::new( - net_addr.to_owned(), - receipt.available_slots(), - receipt.subscription_start(), - receipt.subscription_expiry(), - ); - dbm.store_tower_record(tower_id, net_addr, &receipt) - .unwrap(); - - // Add some invalid appointments and check they match - for _ in 0..5 { - let appointment = generate_random_appointment(None); - - tower_summary - .invalid_appointments - .insert(appointment.locator); - - dbm.store_invalid_appointment(tower_id, &appointment) - .unwrap(); - assert_eq!( - TowerSummary::from(dbm.load_tower_record(tower_id).unwrap()), - tower_summary - ); - } - } - - #[test] - fn test_store_invalid_appointment_twice() { - let mut dbm = DBM::in_memory().unwrap(); - - // In order to add a tower record we need to associated registration receipt. - let tower_id_1 = get_random_user_id(); - let tower_id_2 = get_random_user_id(); - let net_addr = "talaia.watch"; - - let receipt = get_random_registration_receipt(); - dbm.store_tower_record(tower_id_1, net_addr, &receipt) - .unwrap(); - dbm.store_tower_record(tower_id_2, net_addr, &receipt) - .unwrap(); - - // Same as with pending appointments. Two references from different towers is allowed - let appointment = generate_random_appointment(None); - dbm.store_invalid_appointment(tower_id_1, &appointment) - .unwrap(); - dbm.store_invalid_appointment(tower_id_2, &appointment) - .unwrap(); - - // Two references from the same tower is not. - assert!(dbm - .store_invalid_appointment(tower_id_2, &appointment) - .is_err()); - } - - #[test] - fn test_store_load_misbehaving_proof() { - let mut dbm = DBM::in_memory().unwrap(); - - // In order to add a tower record we need to associated registration receipt. - let tower_id = get_random_user_id(); - let net_addr = "talaia.watch"; - - let receipt = get_random_registration_receipt(); - let tower_summary = TowerSummary::new( - net_addr.to_owned(), - receipt.available_slots(), - receipt.subscription_start(), - receipt.subscription_expiry(), - ); - dbm.store_tower_record(tower_id, net_addr, &receipt) - .unwrap(); - assert_eq!( - TowerSummary::from(dbm.load_tower_record(tower_id).unwrap()), - tower_summary - ); - - // Store a misbehaving proof and load it back - let appointment = generate_random_appointment(None); - let appointment_receipt = AppointmentReceipt::with_signature( - "user_signature".to_owned(), - 42, - "tower_signature".to_owned(), - ); - - let proof = MisbehaviorProof::new( - appointment.locator, - appointment_receipt, - get_random_user_id(), - ); - - dbm.store_misbehaving_proof(tower_id, &proof).unwrap(); - assert_eq!(dbm.load_misbehaving_proof(tower_id).unwrap(), proof); - } - - #[test] - fn test_store_load_non_existing_misbehaving_proof() { - let dbm = DBM::in_memory().unwrap(); - assert!(dbm.load_misbehaving_proof(get_random_user_id()).is_none()); - } - - #[test] - fn test_store_exists_misbehaving_proof() { - let mut dbm = DBM::in_memory().unwrap(); - - // In order to add a tower record we need to associated registration receipt. - let tower_id = get_random_user_id(); - let net_addr = "talaia.watch"; - - let receipt = get_random_registration_receipt(); - let tower_summary = TowerSummary::new( - net_addr.to_owned(), - receipt.available_slots(), - receipt.subscription_start(), - receipt.subscription_expiry(), - ); - dbm.store_tower_record(tower_id, net_addr, &receipt) - .unwrap(); - assert_eq!( - TowerSummary::from(dbm.load_tower_record(tower_id).unwrap()), - tower_summary - ); - - // // Store a misbehaving proof check - let appointment = generate_random_appointment(None); - let appointment_receipt = AppointmentReceipt::with_signature( - "user_signature".to_owned(), - 42, - "tower_signature".to_owned(), - ); - - let proof = MisbehaviorProof::new( - appointment.locator, - appointment_receipt, - get_random_user_id(), - ); - - dbm.store_misbehaving_proof(tower_id, &proof).unwrap(); - assert!(dbm.exists_misbehaving_proof(tower_id)); - } - - #[test] - fn test_exists_misbehaving_proof_false() { - let dbm = DBM::in_memory().unwrap(); - assert!(!dbm.exists_misbehaving_proof(get_random_user_id())); - } -} diff --git a/teos-ldk-client/src/wt_client.rs b/teos-ldk-client/src/wt_client.rs index b9ba39d5..0ee67190 100644 --- a/teos-ldk-client/src/wt_client.rs +++ b/teos-ldk-client/src/wt_client.rs @@ -277,8 +277,8 @@ impl WTClient { mod tests { use super::*; + use crate::storage::memory_store::MemoryStore; use crate::storage::{create_storage, StorageConfig}; - use tempdir::TempDir; use teos_common::cryptography; use tokio::sync::mpsc::unbounded_channel; @@ -291,10 +291,11 @@ mod tests { #[tokio::test] async fn test_add_update_load_tower() { let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); + let storage = create_storage(StorageConfig::KV { + kv_store: MemoryStore::new().into_dyn_store(), + sk: vec![0; 32], + }) + .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -389,10 +390,11 @@ mod tests { #[tokio::test] async fn test_get_tower_status() { let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); + let storage = create_storage(StorageConfig::KV { + kv_store: MemoryStore::new().into_dyn_store(), + sk: vec![0; 32], + }) + .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -416,10 +418,11 @@ mod tests { #[tokio::test] async fn test_set_tower_status() { let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); + let storage = create_storage(StorageConfig::KV { + kv_store: MemoryStore::new().into_dyn_store(), + sk: vec![0; 32], + }) + .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -450,10 +453,11 @@ mod tests { #[tokio::test] async fn test_add_appointment_receipt() { let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); + let storage = create_storage(StorageConfig::KV { + kv_store: MemoryStore::new().into_dyn_store(), + sk: vec![0; 32], + }) + .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -504,10 +508,11 @@ mod tests { #[tokio::test] async fn test_add_pending_appointment() { let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); + let storage = create_storage(StorageConfig::KV { + kv_store: MemoryStore::new().into_dyn_store(), + sk: vec![0; 32], + }) + .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -551,10 +556,11 @@ mod tests { #[tokio::test] async fn test_remove_pending_appointment() { let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); + let storage = create_storage(StorageConfig::KV { + kv_store: MemoryStore::new().into_dyn_store(), + sk: vec![0; 32], + }) + .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -586,10 +592,11 @@ mod tests { #[tokio::test] async fn test_add_invalid_appointment() { let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); + let storage = create_storage(StorageConfig::KV { + kv_store: MemoryStore::new().into_dyn_store(), + sk: vec![0; 32], + }) + .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -629,10 +636,11 @@ mod tests { #[tokio::test] async fn test_move_pending_appointment_to_invalid() { let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); + let storage = create_storage(StorageConfig::KV { + kv_store: MemoryStore::new().into_dyn_store(), + sk: vec![0; 32], + }) + .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -677,10 +685,11 @@ mod tests { async fn test_move_pending_appointment_to_invalid_multiple_towers() { // Check that moving an appointment from pending to invalid can be done even if multiple towers have a reference to it let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); + let storage = create_storage(StorageConfig::KV { + kv_store: MemoryStore::new().into_dyn_store(), + sk: vec![0; 32], + }) + .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -755,10 +764,11 @@ mod tests { #[tokio::test] async fn test_flag_misbehaving_tower() { let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); + let storage = create_storage(StorageConfig::KV { + kv_store: MemoryStore::new().into_dyn_store(), + sk: vec![0; 32], + }) + .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -794,10 +804,11 @@ mod tests { #[tokio::test] async fn test_remove_tower() { let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); + let storage = create_storage(StorageConfig::KV { + kv_store: MemoryStore::new().into_dyn_store(), + sk: vec![0; 32], + }) + .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -863,10 +874,11 @@ mod tests { // 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 keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); + let storage = create_storage(StorageConfig::KV { + kv_store: MemoryStore::new().into_dyn_store(), + sk: vec![0; 32], + }) + .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -924,10 +936,11 @@ mod tests { #[tokio::test] async fn test_remove_inexistent_tower() { let keypair = cryptography::get_random_keypair(); - let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &keypair.0)); - let tmp_path = TempDir::new(&format!("watchtower_{}", user_id)).unwrap(); - let db_path = tmp_path.path().join("watchtower.db"); - let storage = create_storage(StorageConfig::SQL { db_path }).unwrap(); + let storage = create_storage(StorageConfig::KV { + kv_store: MemoryStore::new().into_dyn_store(), + sk: vec![0; 32], + }) + .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; From 4ccd76b92a7d46e5c5de752702c3ee4f6047be8e Mon Sep 17 00:00:00 2001 From: dzdidi Date: Thu, 20 Feb 2025 13:02:38 +0100 Subject: [PATCH 66/85] fix: store appointment with misbehaving proof Signed-off-by: dzdidi --- teos-ldk-client/src/retrier.rs | 4 +--- teos-ldk-client/src/storage/kv.rs | 18 ++++++++++++++++-- teos-ldk-client/src/wt_client.rs | 2 +- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/teos-ldk-client/src/retrier.rs b/teos-ldk-client/src/retrier.rs index 19c85fea..953dd32e 100644 --- a/teos-ldk-client/src/retrier.rs +++ b/teos-ldk-client/src/retrier.rs @@ -576,9 +576,8 @@ mod tests { use crate::storage::{create_storage, StorageConfig}; - use serde_json::json; - + use tokio::sync::mpsc::unbounded_channel; use teos_common::errors; @@ -589,7 +588,6 @@ mod tests { generate_random_appointment, get_random_registration_receipt, get_registration_receipt_from_previous, }; - use crate::net::http::ApiError; use crate::storage::memory_store::MemoryStore; diff --git a/teos-ldk-client/src/storage/kv.rs b/teos-ldk-client/src/storage/kv.rs index 5123b02b..c51e8215 100644 --- a/teos-ldk-client/src/storage/kv.rs +++ b/teos-ldk-client/src/storage/kv.rs @@ -632,10 +632,24 @@ impl Persister for KVStorage { proof: &MisbehaviorProof, ) -> Result<(), PersisterError> { let key = make_key(&[&tower_id.to_string()]); - let proof = bincode::serialize(proof).unwrap(); self.store - .write(PRIMARY_NAMESPACE, NS_MISBEHAVIOR_PROOFS, &key, &proof) + .write( + PRIMARY_NAMESPACE, + NS_APPOINTMENT_RECEIPTS, + &format!("{}:{}", tower_id, proof.locator), + &bincode::serialize(&proof.appointment_receipt).unwrap(), + ) + .map_err(|e| PersisterError::StoreError(e.to_string())) + .unwrap(); + + self.store + .write( + PRIMARY_NAMESPACE, + NS_MISBEHAVIOR_PROOFS, + &key, + &bincode::serialize(proof).unwrap(), + ) .map_err(|e| PersisterError::StoreError(e.to_string())) } diff --git a/teos-ldk-client/src/wt_client.rs b/teos-ldk-client/src/wt_client.rs index 0ee67190..2235fec6 100644 --- a/teos-ldk-client/src/wt_client.rs +++ b/teos-ldk-client/src/wt_client.rs @@ -782,7 +782,7 @@ mod tests { wt_client.flag_misbehaving_tower(tower_id, proof.clone()); assert!(!wt_client.towers.contains_key(&tower_id)); - // // Add the tower to the state and try again + // Add the tower to the state and try again let registration_receipt = get_random_registration_receipt(); wt_client .add_update_tower(tower_id, "talaia.watch", ®istration_receipt) From 3dcf29425bd6ec91792d515ef7097b8020b0faea Mon Sep 17 00:00:00 2001 From: dzdidi Date: Thu, 20 Feb 2025 16:42:23 +0100 Subject: [PATCH 67/85] fix: cascade delete tower related data upon tower deletion Signed-off-by: dzdidi --- teos-ldk-client/src/storage/kv.rs | 71 ++++++++++++++++++++++++++++++- teos-ldk-client/src/wt_client.rs | 3 +- 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/teos-ldk-client/src/storage/kv.rs b/teos-ldk-client/src/storage/kv.rs index c51e8215..21c944db 100644 --- a/teos-ldk-client/src/storage/kv.rs +++ b/teos-ldk-client/src/storage/kv.rs @@ -42,6 +42,7 @@ const NS_APPOINTMENTS: &str = "appointments"; const NS_PENDING_APPOINTMENTS: &str = "appointments_pending"; const NS_INVALID_APPOINTMENTS: &str = "appointments_invalid"; const NS_MISBEHAVIOR_PROOFS: &str = "misbehavior_proofs"; +const NS_AVAILABLE_SLOTS: &str = "available_slots"; pub type DynStore = dyn KVStore + Sync + Send; @@ -220,6 +221,74 @@ impl Persister for KVStorage { /// reference to them. fn remove_tower_record(&self, tower_id: TowerId) -> Result<(), PersisterError> { let key = make_key(&[&tower_id.to_string()]); + + let associated_pending_appointments = self + .store + .list(PRIMARY_NAMESPACE, NS_PENDING_APPOINTMENTS) + .unwrap(); + + let associated_pending_appointments = associated_pending_appointments + .iter() + .filter(|l| l.starts_with(&tower_id.to_string())) + .collect::>(); + for key in associated_pending_appointments { + self.store + .remove(PRIMARY_NAMESPACE, NS_PENDING_APPOINTMENTS, key, true) + .map_err(|_e| PersisterError::NotFound(format!("tower_id: {tower_id}")))?; + } + + let associated_invalid_appointments = self + .store + .list(PRIMARY_NAMESPACE, NS_INVALID_APPOINTMENTS) + .unwrap(); + let associated_invalid_appointments = associated_invalid_appointments + .iter() + .filter(|l| l.starts_with(&tower_id.to_string())) + .collect::>(); + for key in associated_invalid_appointments { + self.store + .remove(PRIMARY_NAMESPACE, NS_INVALID_APPOINTMENTS, key, true) + .map_err(|_e| PersisterError::NotFound(format!("tower_id: {tower_id}")))?; + } + + let associated_registration_receipts = self + .store + .list(PRIMARY_NAMESPACE, &format!("{NS_REGISTRATION_RECEIPTS}:{tower_id}")) + .unwrap(); + for key in associated_registration_receipts { + self.store + .remove(PRIMARY_NAMESPACE, &format!("{NS_REGISTRATION_RECEIPTS}:{tower_id}"), &key, true) + .map_err(|_e| PersisterError::NotFound(format!("tower_id: {tower_id}")))?; + } + + let associated_appointment_receipts = self + .store + .list(PRIMARY_NAMESPACE, NS_APPOINTMENT_RECEIPTS) + .unwrap(); + let associated_appointment_receipts = associated_appointment_receipts + .iter() + .filter(|l| l.starts_with(&tower_id.to_string())) + .collect::>(); + for key in associated_appointment_receipts { + self.store + .remove(PRIMARY_NAMESPACE, NS_APPOINTMENT_RECEIPTS, key, true) + .map_err(|_e| PersisterError::NotFound(format!("tower_id: {tower_id}")))?; + } + + let associated_misbehaving_proofs = self + .store + .list(PRIMARY_NAMESPACE, NS_MISBEHAVIOR_PROOFS) + .unwrap(); + let associated_misbehaving_proofs = associated_misbehaving_proofs + .iter() + .filter(|l| l.starts_with(&tower_id.to_string())) + .collect::>(); + for key in associated_misbehaving_proofs { + self.store + .remove(PRIMARY_NAMESPACE, NS_MISBEHAVIOR_PROOFS, key, true) + .map_err(|_e| PersisterError::NotFound(format!("tower_id: {tower_id}")))?; + } + self.store .remove(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key, true) .map_err(|e| PersisterError::NotFound(format!("tower_id: {tower_id}"))) @@ -317,7 +386,7 @@ impl Persister for KVStorage { self.store .write( PRIMARY_NAMESPACE, - &tower_id.to_string(), + NS_AVAILABLE_SLOTS, &tower_id.to_string(), &available_slots.to_be_bytes(), ) diff --git a/teos-ldk-client/src/wt_client.rs b/teos-ldk-client/src/wt_client.rs index 2235fec6..673e60e7 100644 --- a/teos-ldk-client/src/wt_client.rs +++ b/teos-ldk-client/src/wt_client.rs @@ -379,7 +379,7 @@ mod tests { wt_client.add_appointment_receipt( tower_id, locator, - 0, + 0, // FIXME: the problem is that this reset does not work &get_random_appointment_receipt(tower_sk), ); wt_client @@ -862,7 +862,6 @@ mod tests { wt_client.remove_tower(tower_id).unwrap(); assert!(wt_client.load_tower_info(tower_id).is_none()); assert!(!wt_client.towers.contains_key(&tower_id)); - // FIXME assert!(!wt_client .storage .appointment_receipt_exists(locator, tower_id)); From b43e6ece0ddd2caf7599744f66a44a65e010bb4e Mon Sep 17 00:00:00 2001 From: dzdidi Date: Mon, 24 Feb 2025 10:49:08 +0100 Subject: [PATCH 68/85] store available slots Signed-off-by: dzdidi --- teos-ldk-client/src/storage/kv.rs | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/teos-ldk-client/src/storage/kv.rs b/teos-ldk-client/src/storage/kv.rs index 21c944db..60a85a15 100644 --- a/teos-ldk-client/src/storage/kv.rs +++ b/teos-ldk-client/src/storage/kv.rs @@ -157,6 +157,16 @@ impl Persister for KVStorage { ®istration_receipt, ) .map_err(|e| PersisterError::StoreError(e.to_string())) + .unwrap(); + + self.store + .write( + PRIMARY_NAMESPACE, + NS_AVAILABLE_SLOTS, + &tower_id.to_string(), + &receipt.available_slots().to_be_bytes(), + ) + .map_err(|e| PersisterError::StoreError(e.to_string())) } /// Loads a tower record from the database. @@ -212,6 +222,15 @@ impl Persister for KVStorage { tower_info.status = TowerStatus::TemporaryUnreachable; } + let available_slots = self + .store + .read(PRIMARY_NAMESPACE, NS_AVAILABLE_SLOTS, &tower_id.to_string()) + .map_err(|_e| PersisterError::NotFound(format!("tower_id: {tower_id}"))) + .unwrap(); + + tower_info.available_slots = + u32::from_be_bytes(available_slots.as_slice().try_into().unwrap()); + Some(tower_info) } @@ -253,11 +272,19 @@ impl Persister for KVStorage { let associated_registration_receipts = self .store - .list(PRIMARY_NAMESPACE, &format!("{NS_REGISTRATION_RECEIPTS}:{tower_id}")) + .list( + PRIMARY_NAMESPACE, + &format!("{NS_REGISTRATION_RECEIPTS}:{tower_id}"), + ) .unwrap(); for key in associated_registration_receipts { self.store - .remove(PRIMARY_NAMESPACE, &format!("{NS_REGISTRATION_RECEIPTS}:{tower_id}"), &key, true) + .remove( + PRIMARY_NAMESPACE, + &format!("{NS_REGISTRATION_RECEIPTS}:{tower_id}"), + &key, + true, + ) .map_err(|_e| PersisterError::NotFound(format!("tower_id: {tower_id}")))?; } From d4a5b9e9379db27756d0fafcee30392d2f110b5c Mon Sep 17 00:00:00 2001 From: dzdidi Date: Mon, 24 Feb 2025 12:51:44 +0100 Subject: [PATCH 69/85] small clean up Signed-off-by: dzdidi --- teos-ldk-client/src/storage/kv.rs | 43 +++++++++---------------------- 1 file changed, 12 insertions(+), 31 deletions(-) diff --git a/teos-ldk-client/src/storage/kv.rs b/teos-ldk-client/src/storage/kv.rs index 60a85a15..233c0c49 100644 --- a/teos-ldk-client/src/storage/kv.rs +++ b/teos-ldk-client/src/storage/kv.rs @@ -59,10 +59,6 @@ pub struct KVStorage { } /// Creates a composite key from multiple components -fn make_key(components: &[&str]) -> String { - components.join(":") -} - /// Gets the appropriate namespace based on appointment status fn get_appointment_namespace(status: AppointmentStatus) -> &'static str { match status { @@ -92,11 +88,9 @@ impl KVStorage { } fn load_misbehaving_proof(&self, tower_id: TowerId) -> Option { - let key = make_key(&[&tower_id.to_string()]); - match self .store - .read(PRIMARY_NAMESPACE, NS_MISBEHAVIOR_PROOFS, &key) + .read(PRIMARY_NAMESPACE, NS_MISBEHAVIOR_PROOFS, &tower_id.to_string()) { Ok(value) => { let decrypted = decrypt(&value, &self.sk).unwrap(); @@ -107,10 +101,8 @@ impl KVStorage { } fn exists_misbehaving_proof(&self, tower_id: TowerId) -> bool { - let key = make_key(&[&tower_id.to_string()]); - self.store - .read(PRIMARY_NAMESPACE, NS_MISBEHAVIOR_PROOFS, &key) + .read(PRIMARY_NAMESPACE, NS_MISBEHAVIOR_PROOFS, &tower_id.to_string()) .is_ok() } } @@ -126,8 +118,6 @@ impl Persister for KVStorage { net_addr: &str, receipt: &RegistrationReceipt, ) -> Result<(), PersisterError> { - let key = make_key(&[&tower_id.to_string()]); - let tower_info = TowerInfo::new( net_addr.to_string(), receipt.available_slots(), @@ -142,7 +132,7 @@ impl Persister for KVStorage { .map_err(|e| PersisterError::Other(format!("Serialization error: {}", e)))?; self.store - .write(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key, &tower_info) + .write(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &tower_id.to_string(), &tower_info) .map_err(|e| PersisterError::StoreError(e.to_string())) .unwrap(); @@ -175,8 +165,7 @@ impl Persister for KVStorage { /// 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. fn load_tower_record(&self, tower_id: TowerId) -> Option { - let key = make_key(&[&tower_id.to_string()]); - let value = match self.store.read(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key) { + let value = match self.store.read(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &tower_id.to_string()) { Ok(v) => v, Err(_) => return None, }; @@ -239,8 +228,6 @@ impl Persister for KVStorage { /// 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. fn remove_tower_record(&self, tower_id: TowerId) -> Result<(), PersisterError> { - let key = make_key(&[&tower_id.to_string()]); - let associated_pending_appointments = self .store .list(PRIMARY_NAMESPACE, NS_PENDING_APPOINTMENTS) @@ -252,7 +239,7 @@ impl Persister for KVStorage { .collect::>(); for key in associated_pending_appointments { self.store - .remove(PRIMARY_NAMESPACE, NS_PENDING_APPOINTMENTS, key, true) + .remove(PRIMARY_NAMESPACE, NS_PENDING_APPOINTMENTS, &tower_id.to_string(), true) .map_err(|_e| PersisterError::NotFound(format!("tower_id: {tower_id}")))?; } @@ -317,7 +304,7 @@ impl Persister for KVStorage { } self.store - .remove(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key, true) + .remove(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &tower_id.to_string(), true) .map_err(|e| PersisterError::NotFound(format!("tower_id: {tower_id}"))) } @@ -396,8 +383,6 @@ impl Persister for KVStorage { available_slots: u32, receipt: &AppointmentReceipt, ) -> Result<(), PersisterError> { - let key = locator.to_string(); - // store appointment self.store .write( @@ -442,7 +427,7 @@ impl Persister for KVStorage { .map_err(|e| PersisterError::Other(format!("Serialization error: {}", e)))?; self.store - .write(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &key, &tower_info) + .write(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &tower_id.to_string(), &tower_info) .map_err(|e| PersisterError::StoreError(e.to_string())) .unwrap(); @@ -538,9 +523,7 @@ impl Persister for KVStorage { /// Loads an appointment from the database. fn load_appointment(&self, locator: Locator) -> Option { - let key = make_key(&[&locator.to_string()]); - - match self.store.read(PRIMARY_NAMESPACE, NS_APPOINTMENTS, &key) { + match self.store.read(PRIMARY_NAMESPACE, NS_APPOINTMENTS, &locator.to_string()) { Ok(value) => { let decrypted = decrypt(&value, &self.sk).unwrap(); Some(bincode::deserialize(&decrypted).unwrap()) @@ -727,8 +710,6 @@ impl Persister for KVStorage { tower_id: TowerId, proof: &MisbehaviorProof, ) -> Result<(), PersisterError> { - let key = make_key(&[&tower_id.to_string()]); - self.store .write( PRIMARY_NAMESPACE, @@ -743,16 +724,14 @@ impl Persister for KVStorage { .write( PRIMARY_NAMESPACE, NS_MISBEHAVIOR_PROOFS, - &key, + &tower_id.to_string(), &bincode::serialize(proof).unwrap(), ) .map_err(|e| PersisterError::StoreError(e.to_string())) } fn appointment_exists(&self, locator: Locator) -> bool { - let key = make_key(&[&locator.to_string()]); - - let res = self.store.read(PRIMARY_NAMESPACE, NS_APPOINTMENTS, &key); + let res = self.store.read(PRIMARY_NAMESPACE, NS_APPOINTMENTS, &locator.to_string()); res.is_ok() } @@ -776,6 +755,7 @@ impl Persister for KVStorage { /// /// The message to be encrypted is expected to be the penalty transaction. fn encrypt(message: &Vec, secret: &Vec) -> Result, chacha20poly1305::aead::Error> { + // todo!(); // // Defaults is [0; 12] // let nonce = Nonce::default(); // let k = sha256::Hash::hash(secret); @@ -798,6 +778,7 @@ fn decrypt( encrypted_blob: &[u8], secret: &Vec, ) -> Result, chacha20poly1305::aead::Error> { + // todo!(); // // Defaults is [0; 12] // let nonce = Nonce::default(); // let k = sha256::Hash::hash(secret); From 81ab11610f1c4f473d0226e3f4326e68b3ba60a0 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Mon, 24 Feb 2025 12:59:32 +0100 Subject: [PATCH 70/85] small clean up Signed-off-by: dzdidi --- teos-ldk-client/src/storage/kv.rs | 76 +++++++++++++++++++++++-------- 1 file changed, 56 insertions(+), 20 deletions(-) diff --git a/teos-ldk-client/src/storage/kv.rs b/teos-ldk-client/src/storage/kv.rs index 233c0c49..f6c30382 100644 --- a/teos-ldk-client/src/storage/kv.rs +++ b/teos-ldk-client/src/storage/kv.rs @@ -88,10 +88,11 @@ impl KVStorage { } fn load_misbehaving_proof(&self, tower_id: TowerId) -> Option { - match self - .store - .read(PRIMARY_NAMESPACE, NS_MISBEHAVIOR_PROOFS, &tower_id.to_string()) - { + match self.store.read( + PRIMARY_NAMESPACE, + NS_MISBEHAVIOR_PROOFS, + &tower_id.to_string(), + ) { Ok(value) => { let decrypted = decrypt(&value, &self.sk).unwrap(); Some(bincode::deserialize(&decrypted).unwrap()) @@ -99,12 +100,6 @@ impl KVStorage { Err(_) => None, } } - - fn exists_misbehaving_proof(&self, tower_id: TowerId) -> bool { - self.store - .read(PRIMARY_NAMESPACE, NS_MISBEHAVIOR_PROOFS, &tower_id.to_string()) - .is_ok() - } } impl Persister for KVStorage { @@ -132,7 +127,12 @@ impl Persister for KVStorage { .map_err(|e| PersisterError::Other(format!("Serialization error: {}", e)))?; self.store - .write(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &tower_id.to_string(), &tower_info) + .write( + PRIMARY_NAMESPACE, + NS_TOWER_RECORDS, + &tower_id.to_string(), + &tower_info, + ) .map_err(|e| PersisterError::StoreError(e.to_string())) .unwrap(); @@ -165,10 +165,14 @@ impl Persister for KVStorage { /// 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. fn load_tower_record(&self, tower_id: TowerId) -> Option { - let value = match self.store.read(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &tower_id.to_string()) { - Ok(v) => v, - Err(_) => return None, - }; + let value = + match self + .store + .read(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &tower_id.to_string()) + { + Ok(v) => v, + Err(_) => return None, + }; let mut tower_info: TowerInfo = bincode::deserialize(&value).ok()?; @@ -239,7 +243,12 @@ impl Persister for KVStorage { .collect::>(); for key in associated_pending_appointments { self.store - .remove(PRIMARY_NAMESPACE, NS_PENDING_APPOINTMENTS, &tower_id.to_string(), true) + .remove( + PRIMARY_NAMESPACE, + NS_PENDING_APPOINTMENTS, + &tower_id.to_string(), + true, + ) .map_err(|_e| PersisterError::NotFound(format!("tower_id: {tower_id}")))?; } @@ -304,7 +313,12 @@ impl Persister for KVStorage { } self.store - .remove(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &tower_id.to_string(), true) + .remove( + PRIMARY_NAMESPACE, + NS_TOWER_RECORDS, + &tower_id.to_string(), + true, + ) .map_err(|e| PersisterError::NotFound(format!("tower_id: {tower_id}"))) } @@ -427,7 +441,12 @@ impl Persister for KVStorage { .map_err(|e| PersisterError::Other(format!("Serialization error: {}", e)))?; self.store - .write(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &tower_id.to_string(), &tower_info) + .write( + PRIMARY_NAMESPACE, + NS_TOWER_RECORDS, + &tower_id.to_string(), + &tower_info, + ) .map_err(|e| PersisterError::StoreError(e.to_string())) .unwrap(); @@ -523,7 +542,10 @@ impl Persister for KVStorage { /// Loads an appointment from the database. fn load_appointment(&self, locator: Locator) -> Option { - match self.store.read(PRIMARY_NAMESPACE, NS_APPOINTMENTS, &locator.to_string()) { + match self + .store + .read(PRIMARY_NAMESPACE, NS_APPOINTMENTS, &locator.to_string()) + { Ok(value) => { let decrypted = decrypt(&value, &self.sk).unwrap(); Some(bincode::deserialize(&decrypted).unwrap()) @@ -731,7 +753,9 @@ impl Persister for KVStorage { } fn appointment_exists(&self, locator: Locator) -> bool { - let res = self.store.read(PRIMARY_NAMESPACE, NS_APPOINTMENTS, &locator.to_string()); + let res = self + .store + .read(PRIMARY_NAMESPACE, NS_APPOINTMENTS, &locator.to_string()); res.is_ok() } @@ -800,6 +824,18 @@ mod tests { get_registration_receipt_from_previous, }; + impl KVStorage { + fn exists_misbehaving_proof(&self, tower_id: TowerId) -> bool { + self.store + .read( + PRIMARY_NAMESPACE, + NS_MISBEHAVIOR_PROOFS, + &tower_id.to_string(), + ) + .is_ok() + } + } + #[test] fn test_store_load_tower_record() { let mut storage = create_test_kv_storage(); From aaebf986f7fc5636fe715bab12969756e95ff6e3 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Mon, 24 Feb 2025 14:12:13 +0100 Subject: [PATCH 71/85] key space manager draft Signed-off-by: dzdidi --- teos-ldk-client/src/storage/kv.rs | 117 +++++++++++++++++++++++++----- 1 file changed, 100 insertions(+), 17 deletions(-) diff --git a/teos-ldk-client/src/storage/kv.rs b/teos-ldk-client/src/storage/kv.rs index f6c30382..fcf786e4 100644 --- a/teos-ldk-client/src/storage/kv.rs +++ b/teos-ldk-client/src/storage/kv.rs @@ -68,38 +68,118 @@ fn get_appointment_namespace(status: AppointmentStatus) -> &'static str { } } -// Implement methods to convert TowerInfo to vec and vice versa +struct KeySpace { + primary_namespace: String, + secondary_namespace: String, + key: String, +} + +impl KeySpace { + fn tower(tower_id: TowerId) -> Self { + return KeySpace { + primary_namespace: PRIMARY_NAMESPACE.to_string(), + secondary_namespace: NS_TOWER_RECORDS.to_string(), + key: tower_id.to_string(), + }; + } + + fn appointment(locator: Locator) -> Self { + return KeySpace { + primary_namespace: PRIMARY_NAMESPACE.to_string(), + secondary_namespace: NS_APPOINTMENTS.to_string(), + key: locator.to_string(), + }; + } + + fn misbehaving_proof(tower_id: TowerId) -> Self { + return KeySpace { + primary_namespace: PRIMARY_NAMESPACE.to_string(), + secondary_namespace: NS_MISBEHAVIOR_PROOFS.to_string(), + key: tower_id.to_string(), + }; + } + + fn registration_receipt(tower_id: TowerId, subscription_expiry: u32) -> Self { + return KeySpace { + primary_namespace: PRIMARY_NAMESPACE.to_string(), + secondary_namespace: format!("{NS_REGISTRATION_RECEIPTS}:{tower_id}"), + key: subscription_expiry.to_string(), + }; + } + + fn appointment_receipt(tower_id: TowerId, locator: Locator) -> Self { + return KeySpace { + primary_namespace: PRIMARY_NAMESPACE.to_string(), + secondary_namespace: NS_APPOINTMENT_RECEIPTS.to_string(), + key: format!("{}:{}", tower_id, locator), + }; + } + + fn pending_appointment(tower_id: TowerId, locator: Locator) -> Self { + return KeySpace { + primary_namespace: PRIMARY_NAMESPACE.to_string(), + secondary_namespace: NS_PENDING_APPOINTMENTS.to_string(), + key: format!("{}:{}", tower_id, locator), + }; + } + + fn invalid_appointment(tower_id: TowerId, locator: Locator) -> Self { + return KeySpace { + primary_namespace: PRIMARY_NAMESPACE.to_string(), + secondary_namespace: NS_INVALID_APPOINTMENTS.to_string(), + key: format!("{}:{}", tower_id, locator), + }; + } + + fn available_slots(tower_id: TowerId) -> Self { + return KeySpace { + primary_namespace: PRIMARY_NAMESPACE.to_string(), + secondary_namespace: NS_AVAILABLE_SLOTS.to_string(), + key: tower_id.to_string(), + }; + } +} impl KVStorage { pub fn new(store: Arc, sk: Vec) -> Result { Ok(KVStorage { store, sk }) } - fn store_appointment(&mut self, appointment: &Appointment) -> Result<(), PersisterError> { - let value = bincode::serialize(appointment).unwrap(); + fn store_item(&mut self, key_space: KeySpace, value: &T, encrypted: bool) -> Result<(), PersisterError> { + let value = bincode::serialize(value).unwrap(); + let value = if encrypted { + encrypt(&value, &self.sk).unwrap() + } else { + value + }; + self.store - .write( - PRIMARY_NAMESPACE, - NS_APPOINTMENTS, - &appointment.locator.to_string(), - &value, - ) + .write(&key_space.primary_namespace, &key_space.secondary_namespace, &key_space.key, &value) .map_err(|e| PersisterError::StoreError(e.to_string())) } - fn load_misbehaving_proof(&self, tower_id: TowerId) -> Option { - match self.store.read( - PRIMARY_NAMESPACE, - NS_MISBEHAVIOR_PROOFS, - &tower_id.to_string(), - ) { + fn load_item(&self, key_space: KeySpace, encrypted: bool) -> Option { + match self.store.read(&key_space.primary_namespace, &key_space.secondary_namespace, &key_space.key) { Ok(value) => { - let decrypted = decrypt(&value, &self.sk).unwrap(); - Some(bincode::deserialize(&decrypted).unwrap()) + let value = if encrypted { + decrypt(&value, &self.sk).unwrap() + } else { + value + }; + + Some(bincode::deserialize(&value).unwrap()) } Err(_) => None, } } + + fn store_appointment(&mut self, appointment: &Appointment) -> Result<(), PersisterError> { + self.store_item(KeySpace::appointment(appointment.locator), appointment, false) + } + + fn load_misbehaving_proof(&self, tower_id: TowerId) -> Option { + self.load_item(KeySpace::misbehaving_proof(tower_id), true) + } } impl Persister for KVStorage { @@ -126,6 +206,7 @@ impl Persister for KVStorage { let tower_info = bincode::serialize(&tower_info) .map_err(|e| PersisterError::Other(format!("Serialization error: {}", e)))?; + // Store the tower record self.store .write( PRIMARY_NAMESPACE, @@ -139,6 +220,7 @@ impl Persister for KVStorage { let registration_receipt = bincode::serialize(&receipt) .map_err(|e| PersisterError::Other(format!("Serialization error: {}", e)))?; + // Store the registration receipt self.store .write( PRIMARY_NAMESPACE, @@ -149,6 +231,7 @@ impl Persister for KVStorage { .map_err(|e| PersisterError::StoreError(e.to_string())) .unwrap(); + // Store the available slots self.store .write( PRIMARY_NAMESPACE, From 7f92e93a35c37d73e0bbd127354a45ad021b953b Mon Sep 17 00:00:00 2001 From: dzdidi Date: Mon, 24 Feb 2025 15:17:04 +0100 Subject: [PATCH 72/85] bincode serde for available slots Signed-off-by: dzdidi --- teos-ldk-client/src/storage/kv.rs | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/teos-ldk-client/src/storage/kv.rs b/teos-ldk-client/src/storage/kv.rs index fcf786e4..b2ac54cb 100644 --- a/teos-ldk-client/src/storage/kv.rs +++ b/teos-ldk-client/src/storage/kv.rs @@ -1,27 +1,15 @@ use std::collections::{HashMap, HashSet}; -// use chacha20poly1305::aead::{Aead}; use std::sync::Arc; use crate::TowerStatus; use crate::storage::persister::{Persister, PersisterError}; -// use bitcoin::secp256k1::SecretKey; use lightning::io::Error as DBError; use lightning::util::persist::KVStore; use teos_common::appointment::{Appointment, Locator}; use teos_common::receipts::{AppointmentReceipt, RegistrationReceipt}; use teos_common::{TowerId, UserId}; -// use teos_common::cryptography; - -// use chacha20poly1305::aead::{Aead, NewAead}; -// use rand::distributions::Uniform; -// use rand::Rng; - -// use bitcoin::consensus; -// use bitcoin::secp256k1::{Error, PublicKey, Secp256k1, SecretKey}; -// use bitcoin::{Transaction, Txid}; -// use lightning::util::message_signing; impl From for PersisterError { fn from(error: DBError) -> Self { @@ -237,7 +225,7 @@ impl Persister for KVStorage { PRIMARY_NAMESPACE, NS_AVAILABLE_SLOTS, &tower_id.to_string(), - &receipt.available_slots().to_be_bytes(), + &bincode::serialize(&receipt.available_slots()).unwrap(), ) .map_err(|e| PersisterError::StoreError(e.to_string())) } @@ -304,8 +292,8 @@ impl Persister for KVStorage { .map_err(|_e| PersisterError::NotFound(format!("tower_id: {tower_id}"))) .unwrap(); - tower_info.available_slots = - u32::from_be_bytes(available_slots.as_slice().try_into().unwrap()); + tower_info.available_slots = bincode::deserialize(&available_slots).unwrap(); + // u32::from_be_bytes(available_slots.as_slice().try_into().unwrap()); Some(tower_info) } @@ -497,7 +485,7 @@ impl Persister for KVStorage { PRIMARY_NAMESPACE, NS_AVAILABLE_SLOTS, &tower_id.to_string(), - &available_slots.to_be_bytes(), + &bincode::serialize(&available_slots).unwrap(), ) .map_err(|e| PersisterError::StoreError(e.to_string())) .unwrap(); From b155a57765439bdf6d707e1d632e0734855790ae Mon Sep 17 00:00:00 2001 From: dzdidi Date: Mon, 24 Feb 2025 15:46:38 +0100 Subject: [PATCH 73/85] small cleanups Signed-off-by: dzdidi --- teos-ldk-client/src/storage/kv.rs | 251 +++++++++--------------------- 1 file changed, 72 insertions(+), 179 deletions(-) diff --git a/teos-ldk-client/src/storage/kv.rs b/teos-ldk-client/src/storage/kv.rs index b2ac54cb..b82d3d34 100644 --- a/teos-ldk-client/src/storage/kv.rs +++ b/teos-ldk-client/src/storage/kv.rs @@ -181,6 +181,7 @@ impl Persister for KVStorage { net_addr: &str, receipt: &RegistrationReceipt, ) -> Result<(), PersisterError> { + // Create tower info let tower_info = TowerInfo::new( net_addr.to_string(), receipt.available_slots(), @@ -191,43 +192,18 @@ impl Persister for KVStorage { Vec::new(), ); - let tower_info = bincode::serialize(&tower_info) - .map_err(|e| PersisterError::Other(format!("Serialization error: {}", e)))?; - // Store the tower record - self.store - .write( - PRIMARY_NAMESPACE, - NS_TOWER_RECORDS, - &tower_id.to_string(), - &tower_info, - ) - .map_err(|e| PersisterError::StoreError(e.to_string())) - .unwrap(); - - let registration_receipt = bincode::serialize(&receipt) - .map_err(|e| PersisterError::Other(format!("Serialization error: {}", e)))?; + self.store_item(KeySpace::tower(tower_id), &tower_info, false)?; // Store the registration receipt - self.store - .write( - PRIMARY_NAMESPACE, - &format!("{NS_REGISTRATION_RECEIPTS}:{tower_id}"), - &receipt.subscription_expiry().to_string(), - ®istration_receipt, - ) - .map_err(|e| PersisterError::StoreError(e.to_string())) - .unwrap(); + self.store_item( + KeySpace::registration_receipt(tower_id, receipt.subscription_expiry()), + receipt, + false, + )?; // Store the available slots - self.store - .write( - PRIMARY_NAMESPACE, - NS_AVAILABLE_SLOTS, - &tower_id.to_string(), - &bincode::serialize(&receipt.available_slots()).unwrap(), - ) - .map_err(|e| PersisterError::StoreError(e.to_string())) + self.store_item(KeySpace::available_slots(tower_id), &receipt.available_slots(), false) } /// Loads a tower record from the database. @@ -236,24 +212,18 @@ impl Persister for KVStorage { /// 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. fn load_tower_record(&self, tower_id: TowerId) -> Option { - let value = - match self - .store - .read(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &tower_id.to_string()) - { - Ok(v) => v, - Err(_) => return None, - }; - - let mut tower_info: TowerInfo = bincode::deserialize(&value).ok()?; + // Load base tower info + let mut tower_info: TowerInfo = self.load_item(KeySpace::tower(tower_id), false)?; + // Load all associated appointments tower_info.appointments = self.load_appointment_receipts(tower_id); - tower_info.pending_appointments = + tower_info.pending_appointments = self.load_appointments(tower_id, AppointmentStatus::Pending); - tower_info.invalid_appointments = + tower_info.invalid_appointments = self.load_appointments(tower_id, AppointmentStatus::Invalid); - let subsciption_expiries = self + // Get the latest registration receipt expiry + let subscription_expiries = self .store .list( PRIMARY_NAMESPACE, @@ -265,20 +235,17 @@ impl Persister for KVStorage { .max() .unwrap(); - let registration_receipt = self - .store - .read( - PRIMARY_NAMESPACE, - &format!("{NS_REGISTRATION_RECEIPTS}:{tower_id}"), - &subsciption_expiries.to_string(), - ) - .unwrap(); - let registration_receipt: RegistrationReceipt = - bincode::deserialize(®istration_receipt).unwrap(); + // Load the registration receipt + let registration_receipt: RegistrationReceipt = self.load_item( + KeySpace::registration_receipt(tower_id, subscription_expiries), + false, + )?; + // Update subscription info tower_info.subscription_start = registration_receipt.subscription_start(); tower_info.subscription_expiry = registration_receipt.subscription_expiry(); + // Check for misbehavior and update status if let Some(proof) = self.load_misbehaving_proof(tower_id) { tower_info.status = TowerStatus::Misbehaving; tower_info.set_misbehaving_proof(proof); @@ -286,14 +253,8 @@ impl Persister for KVStorage { tower_info.status = TowerStatus::TemporaryUnreachable; } - let available_slots = self - .store - .read(PRIMARY_NAMESPACE, NS_AVAILABLE_SLOTS, &tower_id.to_string()) - .map_err(|_e| PersisterError::NotFound(format!("tower_id: {tower_id}"))) - .unwrap(); - - tower_info.available_slots = bincode::deserialize(&available_slots).unwrap(); - // u32::from_be_bytes(available_slots.as_slice().try_into().unwrap()); + // Load available slots + tower_info.available_slots = self.load_item(KeySpace::available_slots(tower_id), false)?; Some(tower_info) } @@ -399,6 +360,7 @@ impl Persister for KVStorage { fn load_towers(&self) -> HashMap { let mut towers = HashMap::new(); + // List all tower IDs from the tower records namespace let tower_ids = self .store .list(PRIMARY_NAMESPACE, NS_TOWER_RECORDS) @@ -407,9 +369,11 @@ impl Persister for KVStorage { .map(|key| key.parse().unwrap()) .collect::>(); + // Load each tower record and convert to summary for tower_id in tower_ids { - let tower_info = self.load_tower_record(tower_id).unwrap(); - towers.insert(tower_id, TowerSummary::from(tower_info)); + if let Some(tower_info) = self.load_tower_record(tower_id) { + towers.insert(tower_id, TowerSummary::from(tower_info)); + } } towers @@ -468,38 +432,17 @@ impl Persister for KVStorage { available_slots: u32, receipt: &AppointmentReceipt, ) -> Result<(), PersisterError> { - // store appointment - self.store - .write( - PRIMARY_NAMESPACE, - NS_APPOINTMENT_RECEIPTS, - &format!("{}:{}", tower_id, locator), - &bincode::serialize(receipt).unwrap(), - ) - .map_err(|e| PersisterError::StoreError(e.to_string())) - .unwrap(); - - // Update the tower's available_slots - self.store - .write( - PRIMARY_NAMESPACE, - NS_AVAILABLE_SLOTS, - &tower_id.to_string(), - &bincode::serialize(&available_slots).unwrap(), - ) - .map_err(|e| PersisterError::StoreError(e.to_string())) - .unwrap(); + // Store appointment receipt + self.store_item(KeySpace::appointment_receipt(tower_id, locator), receipt, false)?; - let tower = self - .store - .read(PRIMARY_NAMESPACE, NS_TOWER_RECORDS, &format!("{tower_id}")) - .map_err(|e| PersisterError::StoreError(e.to_string())) - .unwrap(); + // Update the tower's available slots + self.store_item(KeySpace::available_slots(tower_id), &available_slots, false)?; - let tower: TowerInfo = bincode::deserialize(&tower).unwrap(); + // Load and update tower info + let tower: TowerInfo = self.load_item(KeySpace::tower(tower_id), false).unwrap(); let tower_info = TowerInfo::new( - tower.net_addr.to_string(), + tower.net_addr, available_slots, tower.subscription_start, tower.subscription_expiry, @@ -508,18 +451,8 @@ impl Persister for KVStorage { Vec::new(), ); - let tower_info = bincode::serialize(&tower_info) - .map_err(|e| PersisterError::Other(format!("Serialization error: {}", e)))?; - - self.store - .write( - PRIMARY_NAMESPACE, - NS_TOWER_RECORDS, - &tower_id.to_string(), - &tower_info, - ) - .map_err(|e| PersisterError::StoreError(e.to_string())) - .unwrap(); + // Store updated tower info + self.store_item(KeySpace::tower(tower_id), &tower_info, false)?; Ok(()) } @@ -530,14 +463,7 @@ impl Persister for KVStorage { tower_id: TowerId, locator: Locator, ) -> Option { - match self.store.read( - PRIMARY_NAMESPACE, - NS_APPOINTMENT_RECEIPTS, - &format!("{}:{}", tower_id, locator), - ) { - Ok(value) => Some(bincode::deserialize(&value).unwrap()), - Err(_) => None, - } + self.load_item(KeySpace::appointment_receipt(tower_id, locator), false) } /// Loads the appointment receipts associated to a given tower. @@ -613,16 +539,7 @@ impl Persister for KVStorage { /// Loads an appointment from the database. fn load_appointment(&self, locator: Locator) -> Option { - match self - .store - .read(PRIMARY_NAMESPACE, NS_APPOINTMENTS, &locator.to_string()) - { - Ok(value) => { - let decrypted = decrypt(&value, &self.sk).unwrap(); - Some(bincode::deserialize(&decrypted).unwrap()) - } - Err(_) => None, - } + self.load_item(KeySpace::appointment(locator), true) } /// Stores a pending appointment into the database. @@ -635,32 +552,25 @@ impl Persister for KVStorage { tower_id: TowerId, appointment: &Appointment, ) -> Result<(), PersisterError> { - if self - .store - .read( - PRIMARY_NAMESPACE, - NS_PENDING_APPOINTMENTS, - &format!("{}:{}", tower_id, appointment.locator), - ) - .is_ok() - { + // Check if pending appointment already exists + let key_space = KeySpace::pending_appointment(tower_id, appointment.locator); + // FIXME + if self.store.read( + &key_space.primary_namespace, + &key_space.secondary_namespace, + &key_space.key, + ).is_ok() { return Err(PersisterError::Other(format!( "{}:{}", tower_id, appointment.locator ))); } - self.store - .write( - PRIMARY_NAMESPACE, - NS_PENDING_APPOINTMENTS, - &format!("{}:{}", tower_id, appointment.locator), - &bincode::serialize(appointment).unwrap(), - ) - .map_err(|e| PersisterError::StoreError(e.to_string())) - .unwrap(); + // Store the pending appointment + self.store_item(key_space, appointment, false)?; - self.store_appointment(appointment).unwrap(); + // Store the appointment itself + self.store_appointment(appointment)?; Ok(()) } @@ -723,32 +633,24 @@ impl Persister for KVStorage { tower_id: TowerId, appointment: &Appointment, ) -> Result<(), PersisterError> { - if self - .store - .read( - PRIMARY_NAMESPACE, - NS_INVALID_APPOINTMENTS, - &format!("{}:{}", tower_id, appointment.locator), - ) - .is_ok() - { + // Check if invalid appointment already exists + let key_space = KeySpace::invalid_appointment(tower_id, appointment.locator); + if self.store.read( + &key_space.primary_namespace, + &key_space.secondary_namespace, + &key_space.key, + ).is_ok() { return Err(PersisterError::Other(format!( "{}:{}", tower_id, appointment.locator ))); } - self.store - .write( - PRIMARY_NAMESPACE, - NS_INVALID_APPOINTMENTS, - &format!("{}:{}", tower_id, appointment.locator), - &bincode::serialize(appointment).unwrap(), - ) - .map_err(|e| PersisterError::StoreError(e.to_string())) - .unwrap(); + // Store the invalid appointment + self.store_item(key_space, appointment, false)?; - self.store_appointment(appointment).unwrap(); + // Store the appointment itself + self.store_appointment(appointment)?; Ok(()) } @@ -803,24 +705,15 @@ impl Persister for KVStorage { tower_id: TowerId, proof: &MisbehaviorProof, ) -> Result<(), PersisterError> { - self.store - .write( - PRIMARY_NAMESPACE, - NS_APPOINTMENT_RECEIPTS, - &format!("{}:{}", tower_id, proof.locator), - &bincode::serialize(&proof.appointment_receipt).unwrap(), - ) - .map_err(|e| PersisterError::StoreError(e.to_string())) - .unwrap(); - - self.store - .write( - PRIMARY_NAMESPACE, - NS_MISBEHAVIOR_PROOFS, - &tower_id.to_string(), - &bincode::serialize(proof).unwrap(), - ) - .map_err(|e| PersisterError::StoreError(e.to_string())) + // Store the appointment receipt + self.store_item( + KeySpace::appointment_receipt(tower_id, proof.locator), + &proof.appointment_receipt, + false, + )?; + + // Store the misbehavior proof + self.store_item(KeySpace::misbehaving_proof(tower_id), proof, false) } fn appointment_exists(&self, locator: Locator) -> bool { From 82044b3aa5ef54f725a907fd55ab1393590b699f Mon Sep 17 00:00:00 2001 From: dzdidi Date: Mon, 24 Feb 2025 16:55:12 +0100 Subject: [PATCH 74/85] small cleanups Signed-off-by: dzdidi --- teos-ldk-client/src/storage/kv.rs | 248 +++++++++++++----------------- 1 file changed, 104 insertions(+), 144 deletions(-) diff --git a/teos-ldk-client/src/storage/kv.rs b/teos-ldk-client/src/storage/kv.rs index b82d3d34..83858ad8 100644 --- a/teos-ldk-client/src/storage/kv.rs +++ b/teos-ldk-client/src/storage/kv.rs @@ -161,6 +161,12 @@ impl KVStorage { } } + fn remove_item(&self, key_space: KeySpace) -> Result<(), PersisterError> { + self.store + .remove(&key_space.primary_namespace, &key_space.secondary_namespace, &key_space.key, false) + .map_err(|_| PersisterError::StoreError(format!("removing: {}", key_space.key))) + } + fn store_appointment(&mut self, appointment: &Appointment) -> Result<(), PersisterError> { self.store_item(KeySpace::appointment(appointment.locator), appointment, false) } @@ -264,94 +270,82 @@ impl Persister for KVStorage { /// 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. fn remove_tower_record(&self, tower_id: TowerId) -> Result<(), PersisterError> { - let associated_pending_appointments = self + // Remove pending appointments + let pending_appointments = self .store .list(PRIMARY_NAMESPACE, NS_PENDING_APPOINTMENTS) - .unwrap(); - - let associated_pending_appointments = associated_pending_appointments + .unwrap() .iter() .filter(|l| l.starts_with(&tower_id.to_string())) - .collect::>(); - for key in associated_pending_appointments { - self.store - .remove( - PRIMARY_NAMESPACE, - NS_PENDING_APPOINTMENTS, - &tower_id.to_string(), - true, - ) - .map_err(|_e| PersisterError::NotFound(format!("tower_id: {tower_id}")))?; + .map(|key| { + let parts: Vec<&str> = key.split(':').collect(); + let locator = Locator::from_slice(&hex::decode(parts[1]).unwrap()).unwrap(); + KeySpace::pending_appointment(tower_id, locator) + }) + .collect::>(); + for key_space in pending_appointments { + self.remove_item(key_space)?; } - let associated_invalid_appointments = self + // Remove invalid appointments + let invalid_appointments = self .store .list(PRIMARY_NAMESPACE, NS_INVALID_APPOINTMENTS) - .unwrap(); - let associated_invalid_appointments = associated_invalid_appointments + .unwrap() .iter() .filter(|l| l.starts_with(&tower_id.to_string())) - .collect::>(); - for key in associated_invalid_appointments { - self.store - .remove(PRIMARY_NAMESPACE, NS_INVALID_APPOINTMENTS, key, true) - .map_err(|_e| PersisterError::NotFound(format!("tower_id: {tower_id}")))?; + .map(|key| { + let parts: Vec<&str> = key.split(':').collect(); + let locator = Locator::from_slice(&hex::decode(parts[1]).unwrap()).unwrap(); + KeySpace::invalid_appointment(tower_id, locator) + }) + .collect::>(); + for key_space in invalid_appointments { + self.remove_item(key_space)?; } - let associated_registration_receipts = self + // Remove registration receipts + let registration_receipts = self .store .list( PRIMARY_NAMESPACE, &format!("{NS_REGISTRATION_RECEIPTS}:{tower_id}"), ) - .unwrap(); - for key in associated_registration_receipts { - self.store - .remove( - PRIMARY_NAMESPACE, - &format!("{NS_REGISTRATION_RECEIPTS}:{tower_id}"), - &key, - true, - ) - .map_err(|_e| PersisterError::NotFound(format!("tower_id: {tower_id}")))?; + .unwrap() + .iter() + .map(|key| { + let expiry = key.parse::().unwrap(); + KeySpace::registration_receipt(tower_id, expiry) + }) + .collect::>(); + for key_space in registration_receipts { + self.remove_item(key_space)?; } - let associated_appointment_receipts = self + // Remove appointment receipts + let appointment_receipts = self .store .list(PRIMARY_NAMESPACE, NS_APPOINTMENT_RECEIPTS) - .unwrap(); - let associated_appointment_receipts = associated_appointment_receipts + .unwrap() .iter() .filter(|l| l.starts_with(&tower_id.to_string())) - .collect::>(); - for key in associated_appointment_receipts { - self.store - .remove(PRIMARY_NAMESPACE, NS_APPOINTMENT_RECEIPTS, key, true) - .map_err(|_e| PersisterError::NotFound(format!("tower_id: {tower_id}")))?; + .map(|key| { + let parts: Vec<&str> = key.split(':').collect(); + let locator = Locator::from_slice(&hex::decode(parts[1]).unwrap()).unwrap(); + KeySpace::appointment_receipt(tower_id, locator) + }) + .collect::>(); + for key_space in appointment_receipts { + self.remove_item(key_space)?; } - let associated_misbehaving_proofs = self - .store - .list(PRIMARY_NAMESPACE, NS_MISBEHAVIOR_PROOFS) - .unwrap(); - let associated_misbehaving_proofs = associated_misbehaving_proofs - .iter() - .filter(|l| l.starts_with(&tower_id.to_string())) - .collect::>(); - for key in associated_misbehaving_proofs { - self.store - .remove(PRIMARY_NAMESPACE, NS_MISBEHAVIOR_PROOFS, key, true) - .map_err(|_e| PersisterError::NotFound(format!("tower_id: {tower_id}")))?; + // Remove misbehaving proofs + if let Some(proof) = self.load_misbehaving_proof(tower_id) { + self.remove_item(KeySpace::misbehaving_proof(tower_id))?; } - self.store - .remove( - PRIMARY_NAMESPACE, - NS_TOWER_RECORDS, - &tower_id.to_string(), - true, - ) - .map_err(|e| PersisterError::NotFound(format!("tower_id: {tower_id}"))) + // Finally remove the tower record itself + self.remove_item(KeySpace::tower(tower_id)) } /// Loads all tower records from the database. @@ -387,7 +381,8 @@ impl Persister for KVStorage { tower_id: TowerId, user_id: UserId, ) -> Option { - let subscription_expiries = self + // Find the latest subscription expiry + let latest_expiry = self .store .list( PRIMARY_NAMESPACE, @@ -396,31 +391,21 @@ impl Persister for KVStorage { .unwrap() .iter() .map(|s_e| s_e.parse::().unwrap()) - .max(); + .max()?; - let subscription_expiries = match subscription_expiries { - Some(subscription_expiries) => subscription_expiries, - None => return None, - }; - - let registration_receipt = self - .store - .read( - PRIMARY_NAMESPACE, - &format!("{NS_REGISTRATION_RECEIPTS}:{tower_id}"), - &subscription_expiries.to_string(), - ) - .unwrap(); - - let registration_receipt: RegistrationReceipt = - bincode::deserialize(®istration_receipt).unwrap(); + // Load the registration receipt using KeySpace + let receipt: RegistrationReceipt = self.load_item( + KeySpace::registration_receipt(tower_id, latest_expiry), + false, + )?; + // Create new receipt with the provided user_id Some(RegistrationReceipt::with_signature( user_id, - registration_receipt.available_slots(), - registration_receipt.subscription_start(), - registration_receipt.subscription_expiry(), - registration_receipt.signature().unwrap(), + receipt.available_slots(), + receipt.subscription_start(), + receipt.subscription_expiry(), + receipt.signature().unwrap(), )) } @@ -488,13 +473,7 @@ impl Persister for KVStorage { for key in keys { // Key is just the locator string let hex_encoded_string = key; - let locator = - match Locator::from_slice(hex::decode(hex_encoded_string).unwrap().as_slice()) { - Ok(l) => l, - Err(s) => { - panic!("Error deserializing locator: {}", s); - } - }; + let locator = Locator::from_slice(hex::decode(hex_encoded_string).unwrap().as_slice()).unwrap(); // Try to read and decrypt the receipt let receipt = self.load_appointment_receipt(tower_id, locator).unwrap(); if let Some(signature) = receipt.signature() { @@ -583,44 +562,36 @@ impl Persister for KVStorage { tower_id: TowerId, locator: Locator, ) -> Result<(), PersisterError> { - // for all towers and given locator - let invalid_appointments_count = self - .store - .list(PRIMARY_NAMESPACE, NS_INVALID_APPOINTMENTS) - .unwrap() - .iter() - .filter(|l| l.ends_with(&locator.to_string())) - .count(); - - // for all towers and given locator - let pending_appointments_count = self - .store - .list(PRIMARY_NAMESPACE, NS_PENDING_APPOINTMENTS) - .unwrap() - .iter() - .filter(|l| l.ends_with(&locator.to_string())) - .count(); - - if invalid_appointments_count + pending_appointments_count == 1 { - self.store - .remove( - PRIMARY_NAMESPACE, - NS_APPOINTMENTS, - &locator.to_string(), - false, - ) - .map_err(|e| PersisterError::StoreError(e.to_string())) - .unwrap(); + // Count total references to this appointment + let total_references = { + // Count invalid appointments + let invalid_count = self + .store + .list(PRIMARY_NAMESPACE, NS_INVALID_APPOINTMENTS) + .unwrap() + .iter() + .filter(|l| l.ends_with(&locator.to_string())) + .count(); + + // Count pending appointments + let pending_count = self + .store + .list(PRIMARY_NAMESPACE, NS_PENDING_APPOINTMENTS) + .unwrap() + .iter() + .filter(|l| l.ends_with(&locator.to_string())) + .count(); + + invalid_count + pending_count }; - self.store - .remove( - PRIMARY_NAMESPACE, - NS_PENDING_APPOINTMENTS, - &format!("{}:{}", tower_id, locator), - false, - ) - .map_err(|e| PersisterError::StoreError(e.to_string())) + // If this is the last reference, remove the appointment itself + if total_references == 1 { + self.remove_item(KeySpace::appointment(locator))?; + } + + // Remove the pending appointment reference + self.remove_item(KeySpace::pending_appointment(tower_id, locator)) } /// Stores an invalid appointment into the database. @@ -680,12 +651,7 @@ impl Persister for KVStorage { .collect::>(); for locator in locators { - let locator = match Locator::from_slice(hex::decode(locator).unwrap().as_slice()) { - Ok(l) => l, - Err(s) => { - panic!("Error deserializing locator: {}", s); - } - }; + let locator = Locator::from_slice(hex::decode(locator).unwrap().as_slice()).unwrap(); match self.load_appointment(locator) { None => continue, @@ -717,21 +683,15 @@ impl Persister for KVStorage { } fn appointment_exists(&self, locator: Locator) -> bool { - let res = self - .store - .read(PRIMARY_NAMESPACE, NS_APPOINTMENTS, &locator.to_string()); + let key_space = KeySpace::appointment(locator); - res.is_ok() + self.load_item::(key_space, false).is_some() } fn appointment_receipt_exists(&self, locator: Locator, tower_id: TowerId) -> bool { - self.store - .read( - PRIMARY_NAMESPACE, - NS_APPOINTMENT_RECEIPTS, - &format!("{}:{}", tower_id, locator), - ) - .is_ok() + let key_space = KeySpace::appointment_receipt(tower_id, locator); + + self.load_item::(key_space, false).is_some() } } @@ -987,7 +947,7 @@ mod tests { let err = storage.remove_tower_record(tower_id).unwrap_err(); assert_eq!( err, - PersisterError::NotFound(format!("tower_id: {tower_id}")) + PersisterError::StoreError(format!("removing: {}", tower_id)) ); } From a15b899784efa19461b1368a81f5b7854e98d111 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Mon, 24 Feb 2025 17:35:19 +0100 Subject: [PATCH 75/85] key space, name space and kv helpers Signed-off-by: dzdidi --- teos-ldk-client/src/storage/kv.rs | 399 +++++++++++++++--------------- 1 file changed, 204 insertions(+), 195 deletions(-) diff --git a/teos-ldk-client/src/storage/kv.rs b/teos-ldk-client/src/storage/kv.rs index 83858ad8..c897bc7d 100644 --- a/teos-ldk-client/src/storage/kv.rs +++ b/teos-ldk-client/src/storage/kv.rs @@ -46,13 +46,54 @@ pub struct KVStorage { sk: Vec, } -/// Creates a composite key from multiple components /// Gets the appropriate namespace based on appointment status -fn get_appointment_namespace(status: AppointmentStatus) -> &'static str { +fn get_appointment_namespace(status: AppointmentStatus) -> NameSpace { match status { - AppointmentStatus::Accepted => NS_APPOINTMENT_RECEIPTS, - AppointmentStatus::Pending => NS_PENDING_APPOINTMENTS, - AppointmentStatus::Invalid => NS_INVALID_APPOINTMENTS, + AppointmentStatus::Accepted => NameSpace::appointment_receipts(), + AppointmentStatus::Pending => NameSpace::pending_appointments(), + AppointmentStatus::Invalid => NameSpace::invalid_appointments(), + } +} + +struct NameSpace { + primary_namespace: String, + secondary_namespace: String, +} + +impl NameSpace { + fn registration_receipts(tower_id: TowerId) -> Self { + NameSpace { + primary_namespace: PRIMARY_NAMESPACE.to_string(), + secondary_namespace: format!("{NS_REGISTRATION_RECEIPTS}:{tower_id}"), + } + } + + fn pending_appointments() -> Self { + NameSpace { + primary_namespace: PRIMARY_NAMESPACE.to_string(), + secondary_namespace: NS_PENDING_APPOINTMENTS.to_string(), + } + } + + fn invalid_appointments() -> Self { + NameSpace { + primary_namespace: PRIMARY_NAMESPACE.to_string(), + secondary_namespace: NS_INVALID_APPOINTMENTS.to_string(), + } + } + + fn appointment_receipts() -> Self { + NameSpace { + primary_namespace: PRIMARY_NAMESPACE.to_string(), + secondary_namespace: NS_APPOINTMENT_RECEIPTS.to_string(), + } + } + + fn tower_records() -> Self { + NameSpace { + primary_namespace: PRIMARY_NAMESPACE.to_string(), + secondary_namespace: NS_TOWER_RECORDS.to_string(), + } } } @@ -64,67 +105,67 @@ struct KeySpace { impl KeySpace { fn tower(tower_id: TowerId) -> Self { - return KeySpace { + KeySpace { primary_namespace: PRIMARY_NAMESPACE.to_string(), secondary_namespace: NS_TOWER_RECORDS.to_string(), key: tower_id.to_string(), - }; + } } fn appointment(locator: Locator) -> Self { - return KeySpace { + KeySpace { primary_namespace: PRIMARY_NAMESPACE.to_string(), secondary_namespace: NS_APPOINTMENTS.to_string(), key: locator.to_string(), - }; + } } fn misbehaving_proof(tower_id: TowerId) -> Self { - return KeySpace { + KeySpace { primary_namespace: PRIMARY_NAMESPACE.to_string(), secondary_namespace: NS_MISBEHAVIOR_PROOFS.to_string(), key: tower_id.to_string(), - }; + } } fn registration_receipt(tower_id: TowerId, subscription_expiry: u32) -> Self { - return KeySpace { + KeySpace { primary_namespace: PRIMARY_NAMESPACE.to_string(), secondary_namespace: format!("{NS_REGISTRATION_RECEIPTS}:{tower_id}"), key: subscription_expiry.to_string(), - }; + } } fn appointment_receipt(tower_id: TowerId, locator: Locator) -> Self { - return KeySpace { + KeySpace { primary_namespace: PRIMARY_NAMESPACE.to_string(), secondary_namespace: NS_APPOINTMENT_RECEIPTS.to_string(), key: format!("{}:{}", tower_id, locator), - }; + } } fn pending_appointment(tower_id: TowerId, locator: Locator) -> Self { - return KeySpace { + KeySpace { primary_namespace: PRIMARY_NAMESPACE.to_string(), secondary_namespace: NS_PENDING_APPOINTMENTS.to_string(), key: format!("{}:{}", tower_id, locator), - }; + } } fn invalid_appointment(tower_id: TowerId, locator: Locator) -> Self { - return KeySpace { + KeySpace { primary_namespace: PRIMARY_NAMESPACE.to_string(), secondary_namespace: NS_INVALID_APPOINTMENTS.to_string(), key: format!("{}:{}", tower_id, locator), - }; + } } fn available_slots(tower_id: TowerId) -> Self { - return KeySpace { + KeySpace { primary_namespace: PRIMARY_NAMESPACE.to_string(), secondary_namespace: NS_AVAILABLE_SLOTS.to_string(), key: tower_id.to_string(), - }; + } } } @@ -133,7 +174,12 @@ impl KVStorage { Ok(KVStorage { store, sk }) } - fn store_item(&mut self, key_space: KeySpace, value: &T, encrypted: bool) -> Result<(), PersisterError> { + fn store_item( + &mut self, + key_space: KeySpace, + value: &T, + encrypted: bool, + ) -> Result<(), PersisterError> { let value = bincode::serialize(value).unwrap(); let value = if encrypted { encrypt(&value, &self.sk).unwrap() @@ -142,12 +188,25 @@ impl KVStorage { }; self.store - .write(&key_space.primary_namespace, &key_space.secondary_namespace, &key_space.key, &value) + .write( + &key_space.primary_namespace, + &key_space.secondary_namespace, + &key_space.key, + &value, + ) .map_err(|e| PersisterError::StoreError(e.to_string())) } - fn load_item(&self, key_space: KeySpace, encrypted: bool) -> Option { - match self.store.read(&key_space.primary_namespace, &key_space.secondary_namespace, &key_space.key) { + fn load_item( + &self, + key_space: &KeySpace, + encrypted: bool, + ) -> Option { + match self.store.read( + &key_space.primary_namespace, + &key_space.secondary_namespace, + &key_space.key, + ) { Ok(value) => { let value = if encrypted { decrypt(&value, &self.sk).unwrap() @@ -161,18 +220,36 @@ impl KVStorage { } } + fn list_keys(&self, name_space: NameSpace) -> Vec { + self.store + .list( + &name_space.primary_namespace, + &name_space.secondary_namespace, + ) + .unwrap() + } + fn remove_item(&self, key_space: KeySpace) -> Result<(), PersisterError> { self.store - .remove(&key_space.primary_namespace, &key_space.secondary_namespace, &key_space.key, false) + .remove( + &key_space.primary_namespace, + &key_space.secondary_namespace, + &key_space.key, + false, + ) .map_err(|_| PersisterError::StoreError(format!("removing: {}", key_space.key))) } fn store_appointment(&mut self, appointment: &Appointment) -> Result<(), PersisterError> { - self.store_item(KeySpace::appointment(appointment.locator), appointment, false) + self.store_item( + KeySpace::appointment(appointment.locator), + appointment, + false, + ) } fn load_misbehaving_proof(&self, tower_id: TowerId) -> Option { - self.load_item(KeySpace::misbehaving_proof(tower_id), true) + self.load_item(&KeySpace::misbehaving_proof(tower_id), true) } } @@ -209,7 +286,11 @@ impl Persister for KVStorage { )?; // Store the available slots - self.store_item(KeySpace::available_slots(tower_id), &receipt.available_slots(), false) + self.store_item( + KeySpace::available_slots(tower_id), + &receipt.available_slots(), + false, + ) } /// Loads a tower record from the database. @@ -219,39 +300,28 @@ impl Persister for KVStorage { /// In the case that the tower has misbehaved, then a misbehaving proof is also attached to the record. fn load_tower_record(&self, tower_id: TowerId) -> Option { // Load base tower info - let mut tower_info: TowerInfo = self.load_item(KeySpace::tower(tower_id), false)?; + let mut tower_info: TowerInfo = self.load_item(&KeySpace::tower(tower_id), false)?; - // Load all associated appointments + // Load all appointments data tower_info.appointments = self.load_appointment_receipts(tower_id); - tower_info.pending_appointments = - self.load_appointments(tower_id, AppointmentStatus::Pending); - tower_info.invalid_appointments = - self.load_appointments(tower_id, AppointmentStatus::Invalid); + tower_info.pending_appointments = self.load_appointments(tower_id, AppointmentStatus::Pending); + tower_info.invalid_appointments = self.load_appointments(tower_id, AppointmentStatus::Invalid); - // Get the latest registration receipt expiry - let subscription_expiries = self - .store - .list( - PRIMARY_NAMESPACE, - &format!("{NS_REGISTRATION_RECEIPTS}:{tower_id}"), - ) - .unwrap() + // Load and update subscription info from latest registration receipt + let latest_expiry = self.list_keys(NameSpace::registration_receipts(tower_id)) .iter() - .map(|s_e| s_e.parse::().unwrap()) - .max() - .unwrap(); + .filter_map(|s_e| s_e.parse::().ok()) + .max()?; - // Load the registration receipt let registration_receipt: RegistrationReceipt = self.load_item( - KeySpace::registration_receipt(tower_id, subscription_expiries), + &KeySpace::registration_receipt(tower_id, latest_expiry), false, )?; - // Update subscription info tower_info.subscription_start = registration_receipt.subscription_start(); tower_info.subscription_expiry = registration_receipt.subscription_expiry(); - // Check for misbehavior and update status + // Update tower status based on misbehavior and pending appointments if let Some(proof) = self.load_misbehaving_proof(tower_id) { tower_info.status = TowerStatus::Misbehaving; tower_info.set_misbehaving_proof(proof); @@ -260,7 +330,7 @@ impl Persister for KVStorage { } // Load available slots - tower_info.available_slots = self.load_item(KeySpace::available_slots(tower_id), false)?; + tower_info.available_slots = self.load_item(&KeySpace::available_slots(tower_id), false)?; Some(tower_info) } @@ -271,10 +341,8 @@ impl Persister for KVStorage { /// reference to them. fn remove_tower_record(&self, tower_id: TowerId) -> Result<(), PersisterError> { // Remove pending appointments - let pending_appointments = self - .store - .list(PRIMARY_NAMESPACE, NS_PENDING_APPOINTMENTS) - .unwrap() + let pending_keys = self + .list_keys(NameSpace::pending_appointments()) .iter() .filter(|l| l.starts_with(&tower_id.to_string())) .map(|key| { @@ -283,15 +351,13 @@ impl Persister for KVStorage { KeySpace::pending_appointment(tower_id, locator) }) .collect::>(); - for key_space in pending_appointments { + for key_space in pending_keys { self.remove_item(key_space)?; } // Remove invalid appointments - let invalid_appointments = self - .store - .list(PRIMARY_NAMESPACE, NS_INVALID_APPOINTMENTS) - .unwrap() + let invalid_keys = self + .list_keys(NameSpace::invalid_appointments()) .iter() .filter(|l| l.starts_with(&tower_id.to_string())) .map(|key| { @@ -300,33 +366,26 @@ impl Persister for KVStorage { KeySpace::invalid_appointment(tower_id, locator) }) .collect::>(); - for key_space in invalid_appointments { + for key_space in invalid_keys { self.remove_item(key_space)?; } // Remove registration receipts - let registration_receipts = self - .store - .list( - PRIMARY_NAMESPACE, - &format!("{NS_REGISTRATION_RECEIPTS}:{tower_id}"), - ) - .unwrap() + let registration_keys = self + .list_keys(NameSpace::registration_receipts(tower_id)) .iter() .map(|key| { let expiry = key.parse::().unwrap(); KeySpace::registration_receipt(tower_id, expiry) }) .collect::>(); - for key_space in registration_receipts { + for key_space in registration_keys { self.remove_item(key_space)?; } // Remove appointment receipts - let appointment_receipts = self - .store - .list(PRIMARY_NAMESPACE, NS_APPOINTMENT_RECEIPTS) - .unwrap() + let receipt_keys = self + .list_keys(NameSpace::appointment_receipts()) .iter() .filter(|l| l.starts_with(&tower_id.to_string())) .map(|key| { @@ -335,11 +394,11 @@ impl Persister for KVStorage { KeySpace::appointment_receipt(tower_id, locator) }) .collect::>(); - for key_space in appointment_receipts { + for key_space in receipt_keys { self.remove_item(key_space)?; } - // Remove misbehaving proofs + // Remove misbehaving proofs if they exist if let Some(proof) = self.load_misbehaving_proof(tower_id) { self.remove_item(KeySpace::misbehaving_proof(tower_id))?; } @@ -352,25 +411,14 @@ impl Persister for KVStorage { /// /// Returns a key value pair with the tower id as key and the tower summary as value. fn load_towers(&self) -> HashMap { - let mut towers = HashMap::new(); - - // List all tower IDs from the tower records namespace - let tower_ids = self - .store - .list(PRIMARY_NAMESPACE, NS_TOWER_RECORDS) - .unwrap() + self.list_keys(NameSpace::tower_records()) .iter() - .map(|key| key.parse().unwrap()) - .collect::>(); - - // Load each tower record and convert to summary - for tower_id in tower_ids { - if let Some(tower_info) = self.load_tower_record(tower_id) { - towers.insert(tower_id, TowerSummary::from(tower_info)); - } - } - - towers + .filter_map(|key| { + let tower_id = key.parse().unwrap(); + self.load_tower_record(tower_id) + .map(|info| (tower_id, TowerSummary::from(info))) + }) + .collect() } /// Loads the latest registration receipt for a given tower. @@ -383,19 +431,14 @@ impl Persister for KVStorage { ) -> Option { // Find the latest subscription expiry let latest_expiry = self - .store - .list( - PRIMARY_NAMESPACE, - &format!("{NS_REGISTRATION_RECEIPTS}:{tower_id}"), - ) - .unwrap() + .list_keys(NameSpace::registration_receipts(tower_id)) .iter() .map(|s_e| s_e.parse::().unwrap()) .max()?; // Load the registration receipt using KeySpace let receipt: RegistrationReceipt = self.load_item( - KeySpace::registration_receipt(tower_id, latest_expiry), + &KeySpace::registration_receipt(tower_id, latest_expiry), false, )?; @@ -418,13 +461,17 @@ impl Persister for KVStorage { receipt: &AppointmentReceipt, ) -> Result<(), PersisterError> { // Store appointment receipt - self.store_item(KeySpace::appointment_receipt(tower_id, locator), receipt, false)?; + self.store_item( + KeySpace::appointment_receipt(tower_id, locator), + receipt, + false, + )?; // Update the tower's available slots self.store_item(KeySpace::available_slots(tower_id), &available_slots, false)?; // Load and update tower info - let tower: TowerInfo = self.load_item(KeySpace::tower(tower_id), false).unwrap(); + let tower: TowerInfo = self.load_item(&KeySpace::tower(tower_id), false).unwrap(); let tower_info = TowerInfo::new( tower.net_addr, @@ -448,7 +495,7 @@ impl Persister for KVStorage { tower_id: TowerId, locator: Locator, ) -> Option { - self.load_item(KeySpace::appointment_receipt(tower_id, locator), false) + self.load_item(&KeySpace::appointment_receipt(tower_id, locator), false) } /// Loads the appointment receipts associated to a given tower. @@ -456,32 +503,20 @@ impl Persister for KVStorage { /// 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. fn load_appointment_receipts(&self, tower_id: TowerId) -> HashMap { - let mut receipts = HashMap::new(); - - let keys = self - .store - .list(PRIMARY_NAMESPACE, NS_APPOINTMENT_RECEIPTS) - .unwrap(); - - let keys = keys + self.list_keys(NameSpace::appointment_receipts()) .iter() - .filter(|l| l.starts_with(&tower_id.to_string())) - .map(|l| l.split(":").collect::>()[1]) - .collect::>(); - - // Get all keys in the tower-specific namespace - for key in keys { - // Key is just the locator string - let hex_encoded_string = key; - let locator = Locator::from_slice(hex::decode(hex_encoded_string).unwrap().as_slice()).unwrap(); - // Try to read and decrypt the receipt - let receipt = self.load_appointment_receipt(tower_id, locator).unwrap(); - if let Some(signature) = receipt.signature() { - receipts.insert(locator, signature); - } - } - - receipts + .filter(|key| key.starts_with(&tower_id.to_string())) + .filter_map(|key| { + // Extract locator from key + let locator_hex = key.split(':').nth(1)?; + let locator = Locator::from_slice(&hex::decode(locator_hex).ok()?).ok()?; + + // Load and get signature from receipt + self.load_appointment_receipt(tower_id, locator) + .and_then(|receipt| receipt.signature()) + .map(|signature| (locator, signature)) + }) + .collect() } /// Loads a collection of locators from the database entry associated to a given tower. @@ -493,32 +528,21 @@ impl Persister for KVStorage { tower_id: TowerId, status: AppointmentStatus, ) -> HashSet { - let mut result = HashSet::new(); - - let appointment_namespace = get_appointment_namespace(status); - - let locators = self - .store - .list(PRIMARY_NAMESPACE, appointment_namespace) - .map_err(|e| PersisterError::StoreError(e.to_string())) - .unwrap(); - - let locators = locators + self.list_keys(get_appointment_namespace(status)) .iter() - .filter(|l| l.starts_with(&tower_id.to_string())) - .map(|l| l.split(":").collect::>()[1]) - .collect::>(); - - for locator in locators { - result.insert(Locator::from_slice(hex::decode(locator).unwrap().as_slice()).unwrap()); - } - - result + .filter(|key| key.starts_with(&tower_id.to_string())) + .filter_map(|key| { + key.split(':') + .nth(1) + .and_then(|hex_str| hex::decode(hex_str).ok()) + .and_then(|bytes| Locator::from_slice(&bytes).ok()) + }) + .collect() } /// Loads an appointment from the database. fn load_appointment(&self, locator: Locator) -> Option { - self.load_item(KeySpace::appointment(locator), true) + self.load_item(&KeySpace::appointment(locator), true) } /// Stores a pending appointment into the database. @@ -533,18 +557,14 @@ impl Persister for KVStorage { ) -> Result<(), PersisterError> { // Check if pending appointment already exists let key_space = KeySpace::pending_appointment(tower_id, appointment.locator); - // FIXME - if self.store.read( - &key_space.primary_namespace, - &key_space.secondary_namespace, - &key_space.key, - ).is_ok() { + if self.load_item::(&key_space, false).is_some(){ return Err(PersisterError::Other(format!( "{}:{}", tower_id, appointment.locator ))); } + let key_space = KeySpace::pending_appointment(tower_id, appointment.locator); // Store the pending appointment self.store_item(key_space, appointment, false)?; @@ -566,18 +586,14 @@ impl Persister for KVStorage { let total_references = { // Count invalid appointments let invalid_count = self - .store - .list(PRIMARY_NAMESPACE, NS_INVALID_APPOINTMENTS) - .unwrap() + .list_keys(NameSpace::invalid_appointments()) .iter() .filter(|l| l.ends_with(&locator.to_string())) .count(); // Count pending appointments let pending_count = self - .store - .list(PRIMARY_NAMESPACE, NS_PENDING_APPOINTMENTS) - .unwrap() + .list_keys(NameSpace::pending_appointments()) .iter() .filter(|l| l.ends_with(&locator.to_string())) .count(); @@ -606,11 +622,15 @@ impl Persister for KVStorage { ) -> Result<(), PersisterError> { // Check if invalid appointment already exists let key_space = KeySpace::invalid_appointment(tower_id, appointment.locator); - if self.store.read( - &key_space.primary_namespace, - &key_space.secondary_namespace, - &key_space.key, - ).is_ok() { + if self + .store + .read( + &key_space.primary_namespace, + &key_space.secondary_namespace, + &key_space.key, + ) + .is_ok() + { return Err(PersisterError::Other(format!( "{}:{}", tower_id, appointment.locator @@ -631,35 +651,23 @@ impl Persister for KVStorage { /// This is meant to be used only for pending and invalid appointments, if the method is called for /// accepted appointment, an empty collection will be returned. fn load_appointments(&self, tower_id: TowerId, status: AppointmentStatus) -> Vec { - let mut appointments = Vec::new(); - - let namespace = match status { - AppointmentStatus::Accepted => return Vec::new(), - _ => get_appointment_namespace(status), - }; - - let locators = self - .store - .list(PRIMARY_NAMESPACE, namespace) - .map_err(|e| PersisterError::StoreError(e.to_string())) - .unwrap(); - - let locators = locators - .iter() - .filter(|l| l.starts_with(&tower_id.to_string())) - .map(|l| l.split(":").collect::>()[1]) - .collect::>(); - - for locator in locators { - let locator = Locator::from_slice(hex::decode(locator).unwrap().as_slice()).unwrap(); - - match self.load_appointment(locator) { - None => continue, - Some(appointment) => appointments.push(appointment), - } + // Return early for accepted appointments + if matches!(status, AppointmentStatus::Accepted) { + return Vec::new(); } - appointments + self.list_keys(get_appointment_namespace(status)) + .iter() + .filter(|key| key.starts_with(&tower_id.to_string())) + .filter_map(|key| { + // Extract and parse locator from key + key.split(':') + .nth(1) + .and_then(|hex_str| hex::decode(hex_str).ok()) + .and_then(|bytes| Locator::from_slice(&bytes).ok()) + .and_then(|locator| self.load_appointment(locator)) + }) + .collect() } /// Stores a misbehaving proof into the database. @@ -685,13 +693,14 @@ impl Persister for KVStorage { fn appointment_exists(&self, locator: Locator) -> bool { let key_space = KeySpace::appointment(locator); - self.load_item::(key_space, false).is_some() + self.load_item::(&key_space, false).is_some() } fn appointment_receipt_exists(&self, locator: Locator, tower_id: TowerId) -> bool { let key_space = KeySpace::appointment_receipt(tower_id, locator); - self.load_item::(key_space, false).is_some() + self.load_item::(&key_space, false) + .is_some() } } From 079869b3f8b399b48b8767b29161b4583a9bbbf6 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Tue, 25 Feb 2025 08:15:18 +0100 Subject: [PATCH 76/85] small cleanups Signed-off-by: dzdidi --- teos-ldk-client/src/storage/kv.rs | 153 ++++++++++++++++++------------ 1 file changed, 90 insertions(+), 63 deletions(-) diff --git a/teos-ldk-client/src/storage/kv.rs b/teos-ldk-client/src/storage/kv.rs index c897bc7d..fdbc7b1b 100644 --- a/teos-ldk-client/src/storage/kv.rs +++ b/teos-ldk-client/src/storage/kv.rs @@ -251,6 +251,84 @@ impl KVStorage { fn load_misbehaving_proof(&self, tower_id: TowerId) -> Option { self.load_item(&KeySpace::misbehaving_proof(tower_id), true) } + + fn remove_pending_appointments(&self, tower_id: TowerId) -> Result<(), PersisterError> { + let pending_keys = self + .list_keys(NameSpace::pending_appointments()) + .iter() + .filter(|l| l.starts_with(&tower_id.to_string())) + .map(|key| { + let parts: Vec<&str> = key.split(':').collect(); + let locator = Locator::from_slice(&hex::decode(parts[1]).unwrap()).unwrap(); + KeySpace::pending_appointment(tower_id, locator) + }) + .collect::>(); + for key_space in pending_keys { + self.remove_item(key_space)?; + } + + Ok(()) + } + + fn remove_invalid_appointments(&self, tower_id: TowerId) -> Result<(), PersisterError> { + let invalid_keys = self + .list_keys(NameSpace::invalid_appointments()) + .iter() + .filter(|l| l.starts_with(&tower_id.to_string())) + .map(|key| { + let parts: Vec<&str> = key.split(':').collect(); + let locator = Locator::from_slice(&hex::decode(parts[1]).unwrap()).unwrap(); + KeySpace::invalid_appointment(tower_id, locator) + }) + .collect::>(); + for key_space in invalid_keys { + self.remove_item(key_space)?; + } + + Ok(()) + } + + fn remove_registration_receipts(&self, tower_id: TowerId) -> Result<(), PersisterError> { + let registration_keys = self + .list_keys(NameSpace::registration_receipts(tower_id)) + .iter() + .map(|key| { + let expiry = key.parse::().unwrap(); + KeySpace::registration_receipt(tower_id, expiry) + }) + .collect::>(); + for key_space in registration_keys { + self.remove_item(key_space)?; + } + + Ok(()) + } + + fn remove_appointment_receipts(&self, tower_id: TowerId) -> Result<(), PersisterError> { + let receipt_keys = self + .list_keys(NameSpace::appointment_receipts()) + .iter() + .filter(|l| l.starts_with(&tower_id.to_string())) + .map(|key| { + let parts: Vec<&str> = key.split(':').collect(); + let locator = Locator::from_slice(&hex::decode(parts[1]).unwrap()).unwrap(); + KeySpace::appointment_receipt(tower_id, locator) + }) + .collect::>(); + for key_space in receipt_keys { + self.remove_item(key_space)?; + } + + Ok(()) + } + + fn remove_misbehaving_proofs(&self, tower_id: TowerId) -> Result<(), PersisterError> { + if let Some(proof) = self.load_misbehaving_proof(tower_id) { + self.remove_item(KeySpace::misbehaving_proof(tower_id))?; + } + + Ok(()) + } } impl Persister for KVStorage { @@ -304,11 +382,14 @@ impl Persister for KVStorage { // Load all appointments data tower_info.appointments = self.load_appointment_receipts(tower_id); - tower_info.pending_appointments = self.load_appointments(tower_id, AppointmentStatus::Pending); - tower_info.invalid_appointments = self.load_appointments(tower_id, AppointmentStatus::Invalid); + tower_info.pending_appointments = + self.load_appointments(tower_id, AppointmentStatus::Pending); + tower_info.invalid_appointments = + self.load_appointments(tower_id, AppointmentStatus::Invalid); // Load and update subscription info from latest registration receipt - let latest_expiry = self.list_keys(NameSpace::registration_receipts(tower_id)) + let latest_expiry = self + .list_keys(NameSpace::registration_receipts(tower_id)) .iter() .filter_map(|s_e| s_e.parse::().ok()) .max()?; @@ -340,70 +421,16 @@ impl Persister for KVStorage { /// 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. fn remove_tower_record(&self, tower_id: TowerId) -> Result<(), PersisterError> { - // Remove pending appointments - let pending_keys = self - .list_keys(NameSpace::pending_appointments()) - .iter() - .filter(|l| l.starts_with(&tower_id.to_string())) - .map(|key| { - let parts: Vec<&str> = key.split(':').collect(); - let locator = Locator::from_slice(&hex::decode(parts[1]).unwrap()).unwrap(); - KeySpace::pending_appointment(tower_id, locator) - }) - .collect::>(); - for key_space in pending_keys { - self.remove_item(key_space)?; - } + self.remove_pending_appointments(tower_id)?; - // Remove invalid appointments - let invalid_keys = self - .list_keys(NameSpace::invalid_appointments()) - .iter() - .filter(|l| l.starts_with(&tower_id.to_string())) - .map(|key| { - let parts: Vec<&str> = key.split(':').collect(); - let locator = Locator::from_slice(&hex::decode(parts[1]).unwrap()).unwrap(); - KeySpace::invalid_appointment(tower_id, locator) - }) - .collect::>(); - for key_space in invalid_keys { - self.remove_item(key_space)?; - } + self.remove_invalid_appointments(tower_id)?; - // Remove registration receipts - let registration_keys = self - .list_keys(NameSpace::registration_receipts(tower_id)) - .iter() - .map(|key| { - let expiry = key.parse::().unwrap(); - KeySpace::registration_receipt(tower_id, expiry) - }) - .collect::>(); - for key_space in registration_keys { - self.remove_item(key_space)?; - } + self.remove_registration_receipts(tower_id)?; - // Remove appointment receipts - let receipt_keys = self - .list_keys(NameSpace::appointment_receipts()) - .iter() - .filter(|l| l.starts_with(&tower_id.to_string())) - .map(|key| { - let parts: Vec<&str> = key.split(':').collect(); - let locator = Locator::from_slice(&hex::decode(parts[1]).unwrap()).unwrap(); - KeySpace::appointment_receipt(tower_id, locator) - }) - .collect::>(); - for key_space in receipt_keys { - self.remove_item(key_space)?; - } + self.remove_appointment_receipts(tower_id)?; - // Remove misbehaving proofs if they exist - if let Some(proof) = self.load_misbehaving_proof(tower_id) { - self.remove_item(KeySpace::misbehaving_proof(tower_id))?; - } + self.remove_misbehaving_proofs(tower_id)?; - // Finally remove the tower record itself self.remove_item(KeySpace::tower(tower_id)) } @@ -557,7 +584,7 @@ impl Persister for KVStorage { ) -> Result<(), PersisterError> { // Check if pending appointment already exists let key_space = KeySpace::pending_appointment(tower_id, appointment.locator); - if self.load_item::(&key_space, false).is_some(){ + if self.load_item::(&key_space, false).is_some() { return Err(PersisterError::Other(format!( "{}:{}", tower_id, appointment.locator From 246e29700cb39b1d49248f04a5cb82cee437d513 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Tue, 25 Feb 2025 10:04:58 +0100 Subject: [PATCH 77/85] kv refactoring, encrypt everything Signed-off-by: dzdidi --- Cargo.lock | 4 +- teos-ldk-client/src/storage/encryption.rs | 51 ++++ teos-ldk-client/src/storage/kv.rs | 280 ++++------------------ teos-ldk-client/src/storage/mod.rs | 3 + teos-ldk-client/src/storage/namespace.rs | 164 +++++++++++++ 5 files changed, 260 insertions(+), 242 deletions(-) create mode 100644 teos-ldk-client/src/storage/encryption.rs create mode 100644 teos-ldk-client/src/storage/namespace.rs diff --git a/Cargo.lock b/Cargo.lock index 3f1fd387..38be52fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1489,9 +1489,9 @@ dependencies = [ [[package]] name = "inout" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ "generic-array", ] diff --git a/teos-ldk-client/src/storage/encryption.rs b/teos-ldk-client/src/storage/encryption.rs new file mode 100644 index 00000000..ec8a6d93 --- /dev/null +++ b/teos-ldk-client/src/storage/encryption.rs @@ -0,0 +1,51 @@ +use chacha20poly1305::{ + aead::{Aead, KeyInit}, + ChaCha20Poly1305, Key, Nonce, +}; + +use bitcoin::hashes::{sha256, Hash}; + +/// Encrypts a given message under a given secret using `chacha20poly1305`. +/// +/// The key material used is: +/// - The dispute txid as encryption key. +/// - `[0; 12]` as IV. +/// +/// The message to be encrypted is expected to be the penalty transaction. +pub(crate) fn encrypt( + message: &[u8], + secret: &[u8], +) -> Result, chacha20poly1305::aead::Error> { + // Create nonce [0; 12] + let nonce = Nonce::default(); + + // Hash the secret to create the encryption key + let key_hash = sha256::Hash::hash(secret); + let key = Key::from_slice(key_hash.as_byte_array()); + + // Create cipher instance + let cipher = ChaCha20Poly1305::new(key); + // Encrypt the message + cipher.encrypt(&nonce, message) +} + +/// Decrypts an encrypted blob of data using `chacha20poly1305` and a given secret. +/// +/// The key material used is: +/// - The dispute txid as decryption key. +/// - `[0; 12]` as IV. +/// +/// The result is expected to be a penalty transaction. +pub(crate) fn decrypt( + encrypted_blob: &[u8], + secret: &Vec, +) -> Result, chacha20poly1305::aead::Error> { + // Defaults is [0; 12] + let nonce = Nonce::default(); + let k = sha256::Hash::hash(secret); + let key = Key::from_slice(k.as_byte_array()); + + let cypher = ChaCha20Poly1305::new(key); + + cypher.decrypt(&nonce, encrypted_blob.as_ref()) +} diff --git a/teos-ldk-client/src/storage/kv.rs b/teos-ldk-client/src/storage/kv.rs index fdbc7b1b..211e9232 100644 --- a/teos-ldk-client/src/storage/kv.rs +++ b/teos-ldk-client/src/storage/kv.rs @@ -11,6 +11,9 @@ use teos_common::appointment::{Appointment, Locator}; use teos_common::receipts::{AppointmentReceipt, RegistrationReceipt}; use teos_common::{TowerId, UserId}; +use crate::storage::encryption::{decrypt, encrypt}; +use crate::storage::namespace::{get_appointment_namespace, KeySpace, NameSpace}; + impl From for PersisterError { fn from(error: DBError) -> Self { PersisterError::Other(error.to_string()) @@ -18,157 +21,13 @@ impl From for PersisterError { } use crate::{AppointmentStatus, MisbehaviorProof, TowerInfo, TowerSummary}; - -// Primary namespace for all watchtower-related data -const PRIMARY_NAMESPACE: &str = "watchtower"; - -// Secondary namespaces and their prefixes for different data types -const NS_TOWER_RECORDS: &str = "tower_records"; -const NS_REGISTRATION_RECEIPTS: &str = "registration_receipts"; -const NS_APPOINTMENT_RECEIPTS: &str = "appointment_receipts"; -const NS_APPOINTMENTS: &str = "appointments"; -const NS_PENDING_APPOINTMENTS: &str = "appointments_pending"; -const NS_INVALID_APPOINTMENTS: &str = "appointments_invalid"; -const NS_MISBEHAVIOR_PROOFS: &str = "misbehavior_proofs"; -const NS_AVAILABLE_SLOTS: &str = "available_slots"; - pub type DynStore = dyn KVStore + Sync + Send; -/// Enum representing the possible errors when decrypting an encrypted blob. -#[derive(Debug)] -pub enum DecryptingError { - AED(chacha20poly1305::aead::Error), - Encode(bitcoin::consensus::encode::Error), -} - pub struct KVStorage { store: Arc, sk: Vec, } -/// Gets the appropriate namespace based on appointment status -fn get_appointment_namespace(status: AppointmentStatus) -> NameSpace { - match status { - AppointmentStatus::Accepted => NameSpace::appointment_receipts(), - AppointmentStatus::Pending => NameSpace::pending_appointments(), - AppointmentStatus::Invalid => NameSpace::invalid_appointments(), - } -} - -struct NameSpace { - primary_namespace: String, - secondary_namespace: String, -} - -impl NameSpace { - fn registration_receipts(tower_id: TowerId) -> Self { - NameSpace { - primary_namespace: PRIMARY_NAMESPACE.to_string(), - secondary_namespace: format!("{NS_REGISTRATION_RECEIPTS}:{tower_id}"), - } - } - - fn pending_appointments() -> Self { - NameSpace { - primary_namespace: PRIMARY_NAMESPACE.to_string(), - secondary_namespace: NS_PENDING_APPOINTMENTS.to_string(), - } - } - - fn invalid_appointments() -> Self { - NameSpace { - primary_namespace: PRIMARY_NAMESPACE.to_string(), - secondary_namespace: NS_INVALID_APPOINTMENTS.to_string(), - } - } - - fn appointment_receipts() -> Self { - NameSpace { - primary_namespace: PRIMARY_NAMESPACE.to_string(), - secondary_namespace: NS_APPOINTMENT_RECEIPTS.to_string(), - } - } - - fn tower_records() -> Self { - NameSpace { - primary_namespace: PRIMARY_NAMESPACE.to_string(), - secondary_namespace: NS_TOWER_RECORDS.to_string(), - } - } -} - -struct KeySpace { - primary_namespace: String, - secondary_namespace: String, - key: String, -} - -impl KeySpace { - fn tower(tower_id: TowerId) -> Self { - KeySpace { - primary_namespace: PRIMARY_NAMESPACE.to_string(), - secondary_namespace: NS_TOWER_RECORDS.to_string(), - key: tower_id.to_string(), - } - } - - fn appointment(locator: Locator) -> Self { - KeySpace { - primary_namespace: PRIMARY_NAMESPACE.to_string(), - secondary_namespace: NS_APPOINTMENTS.to_string(), - key: locator.to_string(), - } - } - - fn misbehaving_proof(tower_id: TowerId) -> Self { - KeySpace { - primary_namespace: PRIMARY_NAMESPACE.to_string(), - secondary_namespace: NS_MISBEHAVIOR_PROOFS.to_string(), - key: tower_id.to_string(), - } - } - - fn registration_receipt(tower_id: TowerId, subscription_expiry: u32) -> Self { - KeySpace { - primary_namespace: PRIMARY_NAMESPACE.to_string(), - secondary_namespace: format!("{NS_REGISTRATION_RECEIPTS}:{tower_id}"), - key: subscription_expiry.to_string(), - } - } - - fn appointment_receipt(tower_id: TowerId, locator: Locator) -> Self { - KeySpace { - primary_namespace: PRIMARY_NAMESPACE.to_string(), - secondary_namespace: NS_APPOINTMENT_RECEIPTS.to_string(), - key: format!("{}:{}", tower_id, locator), - } - } - - fn pending_appointment(tower_id: TowerId, locator: Locator) -> Self { - KeySpace { - primary_namespace: PRIMARY_NAMESPACE.to_string(), - secondary_namespace: NS_PENDING_APPOINTMENTS.to_string(), - key: format!("{}:{}", tower_id, locator), - } - } - - fn invalid_appointment(tower_id: TowerId, locator: Locator) -> Self { - KeySpace { - primary_namespace: PRIMARY_NAMESPACE.to_string(), - secondary_namespace: NS_INVALID_APPOINTMENTS.to_string(), - key: format!("{}:{}", tower_id, locator), - } - } - - fn available_slots(tower_id: TowerId) -> Self { - KeySpace { - primary_namespace: PRIMARY_NAMESPACE.to_string(), - secondary_namespace: NS_AVAILABLE_SLOTS.to_string(), - key: tower_id.to_string(), - } - } -} - impl KVStorage { pub fn new(store: Arc, sk: Vec) -> Result { Ok(KVStorage { store, sk }) @@ -189,9 +48,9 @@ impl KVStorage { self.store .write( - &key_space.primary_namespace, - &key_space.secondary_namespace, - &key_space.key, + key_space.namespace().primary(), + key_space.namespace().secondary(), + key_space.key(), &value, ) .map_err(|e| PersisterError::StoreError(e.to_string())) @@ -203,9 +62,9 @@ impl KVStorage { encrypted: bool, ) -> Option { match self.store.read( - &key_space.primary_namespace, - &key_space.secondary_namespace, - &key_space.key, + key_space.namespace().primary(), + key_space.namespace().secondary(), + key_space.key(), ) { Ok(value) => { let value = if encrypted { @@ -222,29 +81,26 @@ impl KVStorage { fn list_keys(&self, name_space: NameSpace) -> Vec { self.store - .list( - &name_space.primary_namespace, - &name_space.secondary_namespace, - ) + .list(name_space.primary(), name_space.secondary()) .unwrap() } fn remove_item(&self, key_space: KeySpace) -> Result<(), PersisterError> { self.store .remove( - &key_space.primary_namespace, - &key_space.secondary_namespace, - &key_space.key, + key_space.namespace().primary(), + key_space.namespace().secondary(), + key_space.key(), false, ) - .map_err(|_| PersisterError::StoreError(format!("removing: {}", key_space.key))) + .map_err(|_| PersisterError::StoreError(format!("removing: {}", key_space.key()))) } fn store_appointment(&mut self, appointment: &Appointment) -> Result<(), PersisterError> { self.store_item( KeySpace::appointment(appointment.locator), appointment, - false, + true, ) } @@ -323,7 +179,7 @@ impl KVStorage { } fn remove_misbehaving_proofs(&self, tower_id: TowerId) -> Result<(), PersisterError> { - if let Some(proof) = self.load_misbehaving_proof(tower_id) { + if self.load_misbehaving_proof(tower_id).is_some() { self.remove_item(KeySpace::misbehaving_proof(tower_id))?; } @@ -354,20 +210,20 @@ impl Persister for KVStorage { ); // Store the tower record - self.store_item(KeySpace::tower(tower_id), &tower_info, false)?; + self.store_item(KeySpace::tower(tower_id), &tower_info, true)?; // Store the registration receipt self.store_item( KeySpace::registration_receipt(tower_id, receipt.subscription_expiry()), receipt, - false, + true, )?; // Store the available slots self.store_item( KeySpace::available_slots(tower_id), &receipt.available_slots(), - false, + true, ) } @@ -378,7 +234,7 @@ impl Persister for KVStorage { /// In the case that the tower has misbehaved, then a misbehaving proof is also attached to the record. fn load_tower_record(&self, tower_id: TowerId) -> Option { // Load base tower info - let mut tower_info: TowerInfo = self.load_item(&KeySpace::tower(tower_id), false)?; + let mut tower_info: TowerInfo = self.load_item(&KeySpace::tower(tower_id), true)?; // Load all appointments data tower_info.appointments = self.load_appointment_receipts(tower_id); @@ -396,7 +252,7 @@ impl Persister for KVStorage { let registration_receipt: RegistrationReceipt = self.load_item( &KeySpace::registration_receipt(tower_id, latest_expiry), - false, + true, )?; tower_info.subscription_start = registration_receipt.subscription_start(); @@ -411,7 +267,7 @@ impl Persister for KVStorage { } // Load available slots - tower_info.available_slots = self.load_item(&KeySpace::available_slots(tower_id), false)?; + tower_info.available_slots = self.load_item(&KeySpace::available_slots(tower_id), true)?; Some(tower_info) } @@ -466,7 +322,7 @@ impl Persister for KVStorage { // Load the registration receipt using KeySpace let receipt: RegistrationReceipt = self.load_item( &KeySpace::registration_receipt(tower_id, latest_expiry), - false, + true, )?; // Create new receipt with the provided user_id @@ -491,14 +347,14 @@ impl Persister for KVStorage { self.store_item( KeySpace::appointment_receipt(tower_id, locator), receipt, - false, + true, )?; // Update the tower's available slots - self.store_item(KeySpace::available_slots(tower_id), &available_slots, false)?; + self.store_item(KeySpace::available_slots(tower_id), &available_slots, true)?; // Load and update tower info - let tower: TowerInfo = self.load_item(&KeySpace::tower(tower_id), false).unwrap(); + let tower: TowerInfo = self.load_item(&KeySpace::tower(tower_id), true).unwrap(); let tower_info = TowerInfo::new( tower.net_addr, @@ -511,7 +367,7 @@ impl Persister for KVStorage { ); // Store updated tower info - self.store_item(KeySpace::tower(tower_id), &tower_info, false)?; + self.store_item(KeySpace::tower(tower_id), &tower_info, true)?; Ok(()) } @@ -522,7 +378,7 @@ impl Persister for KVStorage { tower_id: TowerId, locator: Locator, ) -> Option { - self.load_item(&KeySpace::appointment_receipt(tower_id, locator), false) + self.load_item(&KeySpace::appointment_receipt(tower_id, locator), true) } /// Loads the appointment receipts associated to a given tower. @@ -584,7 +440,7 @@ impl Persister for KVStorage { ) -> Result<(), PersisterError> { // Check if pending appointment already exists let key_space = KeySpace::pending_appointment(tower_id, appointment.locator); - if self.load_item::(&key_space, false).is_some() { + if self.load_item::(&key_space, true).is_some() { return Err(PersisterError::Other(format!( "{}:{}", tower_id, appointment.locator @@ -593,7 +449,7 @@ impl Persister for KVStorage { let key_space = KeySpace::pending_appointment(tower_id, appointment.locator); // Store the pending appointment - self.store_item(key_space, appointment, false)?; + self.store_item(key_space, appointment, true)?; // Store the appointment itself self.store_appointment(appointment)?; @@ -647,17 +503,10 @@ impl Persister for KVStorage { tower_id: TowerId, appointment: &Appointment, ) -> Result<(), PersisterError> { - // Check if invalid appointment already exists let key_space = KeySpace::invalid_appointment(tower_id, appointment.locator); - if self - .store - .read( - &key_space.primary_namespace, - &key_space.secondary_namespace, - &key_space.key, - ) - .is_ok() - { + + // Check if invalid appointment already exists using load_item + if self.load_item::(&key_space, true).is_some() { return Err(PersisterError::Other(format!( "{}:{}", tower_id, appointment.locator @@ -665,7 +514,7 @@ impl Persister for KVStorage { } // Store the invalid appointment - self.store_item(key_space, appointment, false)?; + self.store_item(key_space, appointment, true)?; // Store the appointment itself self.store_appointment(appointment)?; @@ -710,70 +559,27 @@ impl Persister for KVStorage { self.store_item( KeySpace::appointment_receipt(tower_id, proof.locator), &proof.appointment_receipt, - false, + true, )?; // Store the misbehavior proof - self.store_item(KeySpace::misbehaving_proof(tower_id), proof, false) + self.store_item(KeySpace::misbehaving_proof(tower_id), proof, true) } fn appointment_exists(&self, locator: Locator) -> bool { let key_space = KeySpace::appointment(locator); - self.load_item::(&key_space, false).is_some() + self.load_item::(&key_space, true).is_some() } fn appointment_receipt_exists(&self, locator: Locator, tower_id: TowerId) -> bool { let key_space = KeySpace::appointment_receipt(tower_id, locator); - self.load_item::(&key_space, false) + self.load_item::(&key_space, true) .is_some() } } -/// Encrypts a given message under a given secret using `chacha20poly1305`. -/// -/// The key material used is: -/// - The dispute txid as encryption key. -/// - `[0; 12]` as IV. -/// -/// The message to be encrypted is expected to be the penalty transaction. -fn encrypt(message: &Vec, secret: &Vec) -> Result, chacha20poly1305::aead::Error> { - // todo!(); - // // Defaults is [0; 12] - // let nonce = Nonce::default(); - // let k = sha256::Hash::hash(secret); - // let key = Key::from_slice(k.as_byte_array()); - // - // let cypher = ChaCha20Poly1305::new(key); - // - // cypher.encrypt(&nonce, message.to_vec().as_ref()) - Ok(message.clone()) -} - -/// Decrypts an encrypted blob of data using `chacha20poly1305` and a given secret. -/// -/// The key material used is: -/// - The dispute txid as decryption key. -/// - `[0; 12]` as IV. -/// -/// The result is expected to be a penalty transaction. -fn decrypt( - encrypted_blob: &[u8], - secret: &Vec, -) -> Result, chacha20poly1305::aead::Error> { - // todo!(); - // // Defaults is [0; 12] - // let nonce = Nonce::default(); - // let k = sha256::Hash::hash(secret); - // let key = Key::from_slice(k.as_byte_array()); - // - // let cypher = ChaCha20Poly1305::new(key); - // - // cypher.decrypt(&nonce, encrypted_blob.as_ref()) - Ok(encrypted_blob.to_vec()) -} - #[cfg(test)] mod tests { use super::*; @@ -786,13 +592,8 @@ mod tests { impl KVStorage { fn exists_misbehaving_proof(&self, tower_id: TowerId) -> bool { - self.store - .read( - PRIMARY_NAMESPACE, - NS_MISBEHAVIOR_PROOFS, - &tower_id.to_string(), - ) - .is_ok() + self.load_item::(&KeySpace::misbehaving_proof(tower_id), true) + .is_some() } } @@ -1162,7 +963,6 @@ mod tests { fn test_store_load_appointment() { let mut storage = create_test_kv_storage(); - let tower_id = get_random_user_id(); let appointment = generate_random_appointment(None); storage.store_appointment(&appointment).unwrap(); diff --git a/teos-ldk-client/src/storage/mod.rs b/teos-ldk-client/src/storage/mod.rs index 8f83e395..242d3522 100644 --- a/teos-ldk-client/src/storage/mod.rs +++ b/teos-ldk-client/src/storage/mod.rs @@ -7,6 +7,9 @@ pub use crate::storage::persister::{Persister, PersisterError}; mod kv; use kv::{DynStore, KVStorage}; +mod encryption; +mod namespace; + #[cfg(test)] pub mod memory_store; diff --git a/teos-ldk-client/src/storage/namespace.rs b/teos-ldk-client/src/storage/namespace.rs new file mode 100644 index 00000000..1bbaf08b --- /dev/null +++ b/teos-ldk-client/src/storage/namespace.rs @@ -0,0 +1,164 @@ +use teos_common::appointment::Locator; +use teos_common::TowerId; + +use crate::AppointmentStatus; + +/// Namespace constants for the storage system +pub mod constants { + /// Primary namespace for all watchtower-related data + pub const PRIMARY: &str = "watchtower"; + + /// Secondary namespace constants + pub mod secondary { + pub const TOWER_RECORDS: &str = "tower_records"; + pub const REGISTRATION_RECEIPTS: &str = "registration_receipts"; + pub const APPOINTMENT_RECEIPTS: &str = "appointment_receipts"; + pub const APPOINTMENTS: &str = "appointments"; + pub const PENDING_APPOINTMENTS: &str = "appointments_pending"; + pub const INVALID_APPOINTMENTS: &str = "appointments_invalid"; + pub const MISBEHAVIOR_PROOFS: &str = "misbehavior_proofs"; + pub const AVAILABLE_SLOTS: &str = "available_slots"; + } +} + +use constants::secondary::*; +use constants::*; + +/// Gets the appropriate namespace based on appointment status +pub(crate) fn get_appointment_namespace(status: AppointmentStatus) -> NameSpace { + match status { + AppointmentStatus::Accepted => NameSpace::appointment_receipts(), + AppointmentStatus::Pending => NameSpace::pending_appointments(), + AppointmentStatus::Invalid => NameSpace::invalid_appointments(), + } +} + +/// Represents a namespace in the storage system +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct NameSpace { + primary_namespace: String, + secondary_namespace: String, +} + +impl NameSpace { + /// Returns the primary namespace + pub fn primary(&self) -> &str { + &self.primary_namespace + } + + /// Returns the secondary namespace + pub fn secondary(&self) -> &str { + &self.secondary_namespace + } +} + +impl NameSpace { + /// Creates a KeySpace from this namespace + pub fn with_key(&self, key: impl Into) -> KeySpace { + KeySpace::new(self.clone(), key) + } + + /// Creates a new NameSpace instance + fn new(secondary_namespace: impl Into) -> Self { + Self { + primary_namespace: PRIMARY.to_string(), + secondary_namespace: secondary_namespace.into(), + } + } + + /// Creates a new NameSpace instance with a formatted secondary namespace + fn new_formatted( + secondary_namespace: impl std::fmt::Display, + id: impl std::fmt::Display, + ) -> Self { + Self::new(format!("{}:{}", secondary_namespace, id)) + } + + pub fn registration_receipts(tower_id: TowerId) -> Self { + Self::new_formatted(REGISTRATION_RECEIPTS, tower_id) + } + + pub fn pending_appointments() -> Self { + Self::new(PENDING_APPOINTMENTS) + } + + pub fn invalid_appointments() -> Self { + Self::new(INVALID_APPOINTMENTS) + } + + pub fn appointment_receipts() -> Self { + Self::new(APPOINTMENT_RECEIPTS) + } + + pub fn tower_records() -> Self { + Self::new(TOWER_RECORDS) + } +} + +/// Represents a complete key space in the storage system +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct KeySpace { + namespace: NameSpace, + key: String, +} + +impl KeySpace { + /// Returns a reference to the namespace + pub fn namespace(&self) -> &NameSpace { + &self.namespace + } + + /// Returns a reference to the key + pub fn key(&self) -> &str { + &self.key + } + + /// Creates a new KeySpace instance + fn new(namespace: NameSpace, key: impl Into) -> Self { + Self { + namespace, + key: key.into(), + } + } + + /// Creates a new KeySpace instance with a formatted key + fn new_with_formatted_key( + secondary_namespace: impl Into, + id1: impl std::fmt::Display, + id2: impl std::fmt::Display, + ) -> Self { + NameSpace::new(secondary_namespace).with_key(format!("{}:{}", id1, id2)) + } + + pub fn tower(tower_id: TowerId) -> Self { + Self::new(NameSpace::tower_records(), tower_id.to_string()) + } + + pub fn appointment(locator: Locator) -> Self { + Self::new(NameSpace::new(APPOINTMENTS), locator.to_string()) + } + + pub fn misbehaving_proof(tower_id: TowerId) -> Self { + NameSpace::new(MISBEHAVIOR_PROOFS).with_key(tower_id.to_string()) + } + + pub fn registration_receipt(tower_id: TowerId, subscription_expiry: u32) -> Self { + NameSpace::registration_receipts(tower_id).with_key(subscription_expiry.to_string()) + } + + pub fn appointment_receipt(tower_id: TowerId, locator: Locator) -> Self { + Self::new_with_formatted_key(APPOINTMENT_RECEIPTS, tower_id, locator) + } + + pub fn pending_appointment(tower_id: TowerId, locator: Locator) -> Self { + Self::new_with_formatted_key(PENDING_APPOINTMENTS, tower_id, locator) + } + + pub fn invalid_appointment(tower_id: TowerId, locator: Locator) -> Self { + Self::new_with_formatted_key(INVALID_APPOINTMENTS, tower_id, locator) + } + + pub fn available_slots(tower_id: TowerId) -> Self { + NameSpace::new(AVAILABLE_SLOTS).with_key(tower_id.to_string()) + } +} From 05604a4e2ba10baf85e054dbb920757ea63fbcc1 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Tue, 25 Feb 2025 10:19:56 +0100 Subject: [PATCH 78/85] clippy fix Signed-off-by: dzdidi --- teos-ldk-client/src/ser.rs | 39 +-------------------- teos-ldk-client/src/storage/encryption.rs | 2 +- teos-ldk-client/src/storage/memory_store.rs | 9 ++++- 3 files changed, 10 insertions(+), 40 deletions(-) diff --git a/teos-ldk-client/src/ser.rs b/teos-ldk-client/src/ser.rs index d0c2e9ba..50a9ef8f 100644 --- a/teos-ldk-client/src/ser.rs +++ b/teos-ldk-client/src/ser.rs @@ -1,11 +1,8 @@ use bitcoin::consensus::encode; use bitcoin::Transaction; -use teos_common::appointment::{Appointment, Locator}; - use hex::FromHex; -use serde::{de, ser::SerializeMap, Deserializer, Serialize, Serializer}; -use std::collections::HashMap; +use serde::{de, Deserializer}; pub fn deserialize_tx<'de, D>(deserializer: D) -> Result where @@ -34,37 +31,3 @@ where deserializer.deserialize_any(TransactionVisitor) } - -pub fn serialize_receipts(hm: &HashMap, s: S) -> Result -where - S: Serializer, -{ - let mut map = s.serialize_map(Some(hm.len()))?; - for (locator, sig) in hm { - map.serialize_entry(&hex::encode(locator), sig)?; - } - map.end() -} - -#[derive(Serialize)] -struct AppointmentInners { - encrypted_blob: String, - to_self_delay: u32, -} - -pub fn serialize_appointments(v: &Vec, s: S) -> Result -where - S: Serializer, -{ - let mut map = s.serialize_map(Some(v.len()))?; - for a in v { - map.serialize_entry( - &hex::encode(a.locator), - &AppointmentInners { - encrypted_blob: hex::encode(&a.encrypted_blob), - to_self_delay: a.to_self_delay, - }, - )?; - } - map.end() -} diff --git a/teos-ldk-client/src/storage/encryption.rs b/teos-ldk-client/src/storage/encryption.rs index ec8a6d93..44b5e9a1 100644 --- a/teos-ldk-client/src/storage/encryption.rs +++ b/teos-ldk-client/src/storage/encryption.rs @@ -38,7 +38,7 @@ pub(crate) fn encrypt( /// The result is expected to be a penalty transaction. pub(crate) fn decrypt( encrypted_blob: &[u8], - secret: &Vec, + secret: &[u8], ) -> Result, chacha20poly1305::aead::Error> { // Defaults is [0; 12] let nonce = Nonce::default(); diff --git a/teos-ldk-client/src/storage/memory_store.rs b/teos-ldk-client/src/storage/memory_store.rs index cc8766e9..123a85c7 100644 --- a/teos-ldk-client/src/storage/memory_store.rs +++ b/teos-ldk-client/src/storage/memory_store.rs @@ -5,10 +5,17 @@ use std::sync::{Arc, Mutex}; pub(crate) type DynStore = dyn KVStore + Sync + Send; +/// Type alias for the namespace to key-value mapping +type NamespaceMap = HashMap>; +/// Type alias for the storage data structure +type StorageMap = HashMap; +/// Type alias for thread-safe storage +type ThreadSafeStorage = Arc>; + /// In-memory key-value store implementation for testing #[derive(Clone, Debug)] pub struct MemoryStore { - data: Arc>>>>, + data: ThreadSafeStorage, } impl MemoryStore { From 1c6998fc41c1e9987e3d8d4507b7cd409ef5a8e7 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Tue, 25 Feb 2025 10:52:49 +0100 Subject: [PATCH 79/85] rename memory_store to mock_kv Signed-off-by: dzdidi --- teos-ldk-client/src/retrier.rs | 2 +- teos-ldk-client/src/storage/{memory_store.rs => mock_kv.rs} | 0 teos-ldk-client/src/storage/mod.rs | 4 ++-- teos-ldk-client/src/wt_client.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename teos-ldk-client/src/storage/{memory_store.rs => mock_kv.rs} (100%) diff --git a/teos-ldk-client/src/retrier.rs b/teos-ldk-client/src/retrier.rs index 953dd32e..44cacb65 100644 --- a/teos-ldk-client/src/retrier.rs +++ b/teos-ldk-client/src/retrier.rs @@ -590,7 +590,7 @@ mod tests { }; use crate::net::http::ApiError; - use crate::storage::memory_store::MemoryStore; + use crate::storage::mock_kv::MemoryStore; use crate::test_utils::get_dummy_add_appointment_response; const LONG_AUTO_RETRY_DELAY: u32 = 60; diff --git a/teos-ldk-client/src/storage/memory_store.rs b/teos-ldk-client/src/storage/mock_kv.rs similarity index 100% rename from teos-ldk-client/src/storage/memory_store.rs rename to teos-ldk-client/src/storage/mock_kv.rs diff --git a/teos-ldk-client/src/storage/mod.rs b/teos-ldk-client/src/storage/mod.rs index 242d3522..6097bb54 100644 --- a/teos-ldk-client/src/storage/mod.rs +++ b/teos-ldk-client/src/storage/mod.rs @@ -11,10 +11,10 @@ mod encryption; mod namespace; #[cfg(test)] -pub mod memory_store; +pub mod mock_kv; #[cfg(test)] -pub use memory_store::MemoryStore; +pub use mock_kv::MemoryStore; pub fn create_storage( config: StorageConfig, diff --git a/teos-ldk-client/src/wt_client.rs b/teos-ldk-client/src/wt_client.rs index 673e60e7..92063d8d 100644 --- a/teos-ldk-client/src/wt_client.rs +++ b/teos-ldk-client/src/wt_client.rs @@ -277,7 +277,7 @@ impl WTClient { mod tests { use super::*; - use crate::storage::memory_store::MemoryStore; + use crate::storage::mock_kv::MemoryStore; use crate::storage::{create_storage, StorageConfig}; use teos_common::cryptography; use tokio::sync::mpsc::unbounded_channel; From 33e57a583bd095971bd714c2c3c3f3cd09eaf62f Mon Sep 17 00:00:00 2001 From: dzdidi Date: Tue, 25 Feb 2025 11:08:55 +0100 Subject: [PATCH 80/85] kv cleanup Signed-off-by: dzdidi --- teos-ldk-client/src/retrier.rs | 114 ++++++++++++++--------------- teos-ldk-client/src/storage/kv.rs | 8 +- teos-ldk-client/src/storage/mod.rs | 38 ++++------ teos-ldk-client/src/wt_client.rs | 106 +++++++++++++-------------- 4 files changed, 130 insertions(+), 136 deletions(-) diff --git a/teos-ldk-client/src/retrier.rs b/teos-ldk-client/src/retrier.rs index 44cacb65..c9b9db42 100644 --- a/teos-ldk-client/src/retrier.rs +++ b/teos-ldk-client/src/retrier.rs @@ -574,7 +574,7 @@ impl Retrier { mod tests { use super::*; - use crate::storage::{create_storage, StorageConfig}; + use crate::storage::create_storage; use serde_json::json; @@ -628,10 +628,10 @@ mod tests { async fn test_manage_retry_reachable() { let (tx, rx) = unbounded_channel(); let keypair = cryptography::get_random_keypair(); - let storage = create_storage(StorageConfig::KV { - kv_store: MemoryStore::new().into_dyn_store(), - sk: vec![0; 32], - }) + let storage = create_storage( + MemoryStore::new().into_dyn_store(), + keypair.0.secret_bytes().to_vec(), + ) .unwrap(); let wt_client = Arc::new(Mutex::new( @@ -727,10 +727,10 @@ mod tests { async fn test_manage_retry_unreachable() { let (tx, rx) = unbounded_channel(); let keypair = cryptography::get_random_keypair(); - let storage = create_storage(StorageConfig::KV { - kv_store: MemoryStore::new().into_dyn_store(), - sk: vec![0; 32], - }) + let storage = create_storage( + MemoryStore::new().into_dyn_store(), + keypair.0.secret_bytes().to_vec(), + ) .unwrap(); let wt_client = Arc::new(Mutex::new( @@ -859,10 +859,10 @@ mod tests { async fn test_manage_retry_rejected() { let (tx, rx) = unbounded_channel(); let keypair = cryptography::get_random_keypair(); - let storage = create_storage(StorageConfig::KV { - kv_store: MemoryStore::new().into_dyn_store(), - sk: vec![0; 32], - }) + let storage = create_storage( + MemoryStore::new().into_dyn_store(), + keypair.0.secret_bytes().to_vec(), + ) .unwrap(); let wt_client = Arc::new(Mutex::new( @@ -968,10 +968,10 @@ mod tests { async fn test_manage_retry_misbehaving() { let (tx, rx) = unbounded_channel(); let keypair = cryptography::get_random_keypair(); - let storage = create_storage(StorageConfig::KV { - kv_store: MemoryStore::new().into_dyn_store(), - sk: vec![0; 32], - }) + let storage = create_storage( + MemoryStore::new().into_dyn_store(), + keypair.0.secret_bytes().to_vec(), + ) .unwrap(); let wt_client = Arc::new(Mutex::new( @@ -1065,10 +1065,10 @@ mod tests { async fn test_manage_retry_abandoned() { let keypair = cryptography::get_random_keypair(); let (tx, rx) = unbounded_channel(); - let storage = create_storage(StorageConfig::KV { - kv_store: MemoryStore::new().into_dyn_store(), - sk: vec![0; 32], - }) + let storage = create_storage( + MemoryStore::new().into_dyn_store(), + keypair.0.secret_bytes().to_vec(), + ) .unwrap(); let wt_client = Arc::new(Mutex::new( @@ -1113,10 +1113,10 @@ mod tests { async fn test_manage_retry_subscription_error() { let (tx, rx) = unbounded_channel(); let keypair = cryptography::get_random_keypair(); - let storage = create_storage(StorageConfig::KV { - kv_store: MemoryStore::new().into_dyn_store(), - sk: vec![0; 32], - }) + let storage = create_storage( + MemoryStore::new().into_dyn_store(), + keypair.0.secret_bytes().to_vec(), + ) .unwrap(); let wt_client = Arc::new(Mutex::new( @@ -1239,10 +1239,10 @@ mod tests { // 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 storage = create_storage(StorageConfig::KV { - kv_store: MemoryStore::new().into_dyn_store(), - sk: vec![0; 32], - }) + let mut storage = create_storage( + MemoryStore::new().into_dyn_store(), + keypair.0.secret_bytes().to_vec(), + ) .unwrap(); let receipt = get_random_registration_receipt(); storage @@ -1392,10 +1392,10 @@ mod tests { let (tower_sk, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); let keypair = cryptography::get_random_keypair(); - let storage = create_storage(StorageConfig::KV { - kv_store: MemoryStore::new().into_dyn_store(), - sk: vec![0; 32], - }) + let storage = create_storage( + MemoryStore::new().into_dyn_store(), + keypair.0.secret_bytes().to_vec(), + ) .unwrap(); let wt_client = Arc::new(Mutex::new( @@ -1447,10 +1447,10 @@ mod tests { let (_, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); let keypair = cryptography::get_random_keypair(); - let storage = create_storage(StorageConfig::KV { - kv_store: MemoryStore::new().into_dyn_store(), - sk: vec![0; 32], - }) + let storage = create_storage( + MemoryStore::new().into_dyn_store(), + keypair.0.secret_bytes().to_vec(), + ) .unwrap(); let wt_client = Arc::new(Mutex::new( @@ -1476,10 +1476,10 @@ mod tests { let (_, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); let keypair = cryptography::get_random_keypair(); - let storage = create_storage(StorageConfig::KV { - kv_store: MemoryStore::new().into_dyn_store(), - sk: vec![0; 32], - }) + let storage = create_storage( + MemoryStore::new().into_dyn_store(), + keypair.0.secret_bytes().to_vec(), + ) .unwrap(); let wt_client = Arc::new(Mutex::new( @@ -1533,10 +1533,10 @@ mod tests { let (_, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); let keypair = cryptography::get_random_keypair(); - let storage = create_storage(StorageConfig::KV { - kv_store: MemoryStore::new().into_dyn_store(), - sk: vec![0; 32], - }) + let storage = create_storage( + MemoryStore::new().into_dyn_store(), + keypair.0.secret_bytes().to_vec(), + ) .unwrap(); let wt_client = Arc::new(Mutex::new( @@ -1570,10 +1570,10 @@ mod tests { let (_, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); let keypair = cryptography::get_random_keypair(); - let storage = create_storage(StorageConfig::KV { - kv_store: MemoryStore::new().into_dyn_store(), - sk: vec![0; 32], - }) + let storage = create_storage( + MemoryStore::new().into_dyn_store(), + keypair.0.secret_bytes().to_vec(), + ) .unwrap(); let wt_client = Arc::new(Mutex::new( @@ -1630,10 +1630,10 @@ mod tests { let (_, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); let keypair = cryptography::get_random_keypair(); - let storage = create_storage(StorageConfig::KV { - kv_store: MemoryStore::new().into_dyn_store(), - sk: vec![0; 32], - }) + let storage = create_storage( + MemoryStore::new().into_dyn_store(), + keypair.0.secret_bytes().to_vec(), + ) .unwrap(); let wt_client = Arc::new(Mutex::new( @@ -1696,10 +1696,10 @@ mod tests { let (_, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); let keypair = cryptography::get_random_keypair(); - let storage = create_storage(StorageConfig::KV { - kv_store: MemoryStore::new().into_dyn_store(), - sk: vec![0; 32], - }) + let storage = create_storage( + MemoryStore::new().into_dyn_store(), + keypair.0.secret_bytes().to_vec(), + ) .unwrap(); let wt_client = Arc::new(Mutex::new( diff --git a/teos-ldk-client/src/storage/kv.rs b/teos-ldk-client/src/storage/kv.rs index 211e9232..c1b327ee 100644 --- a/teos-ldk-client/src/storage/kv.rs +++ b/teos-ldk-client/src/storage/kv.rs @@ -583,7 +583,7 @@ impl Persister for KVStorage { #[cfg(test)] mod tests { use super::*; - use crate::storage::create_test_kv_storage; + use crate::storage::mock_kv::MemoryStore; use teos_common::test_utils::{ generate_random_appointment, get_random_registration_receipt, get_random_user_id, @@ -597,6 +597,12 @@ mod tests { } } + fn create_test_kv_storage() -> KVStorage { + let store = MemoryStore::new().into_dyn_store(); + let sk = vec![0u8; 32]; // Test secret key + KVStorage::new(store, sk).unwrap() + } + #[test] fn test_store_load_tower_record() { let mut storage = create_test_kv_storage(); diff --git a/teos-ldk-client/src/storage/mod.rs b/teos-ldk-client/src/storage/mod.rs index 6097bb54..1cb0de4a 100644 --- a/teos-ldk-client/src/storage/mod.rs +++ b/teos-ldk-client/src/storage/mod.rs @@ -1,13 +1,9 @@ pub mod persister; -use std::sync::Arc; - pub use crate::storage::persister::{Persister, PersisterError}; -mod kv; -use kv::{DynStore, KVStorage}; - mod encryption; +mod kv; mod namespace; #[cfg(test)] @@ -16,30 +12,22 @@ pub mod mock_kv; #[cfg(test)] pub use mock_kv::MemoryStore; +#[cfg(test)] pub fn create_storage( - config: StorageConfig, + kv_store: Arc, + sk: Vec, ) -> Result, PersisterError> { - match config { - StorageConfig::KV { kv_store, sk } => match KVStorage::new(kv_store, sk) { - Ok(storage) => Ok(Box::new(storage)), - Err(e) => Err(PersisterError::Other(format!( - "Error creating storage: {}", - e - ))), - }, + match KVStorage::new(kv_store, sk) { + Ok(storage) => Ok(Box::new(storage)), + Err(e) => Err(PersisterError::Other(format!( + "Error creating storage: {}", + e + ))), } } -pub enum StorageConfig { - KV { - kv_store: Arc, - sk: Vec, - }, -} +#[cfg(test)] +use std::sync::Arc; #[cfg(test)] -fn create_test_kv_storage() -> KVStorage { - let store = MemoryStore::new().into_dyn_store(); - let sk = vec![0u8; 32]; // Test secret key - KVStorage::new(store, sk).unwrap() -} +use kv::{DynStore, KVStorage}; diff --git a/teos-ldk-client/src/wt_client.rs b/teos-ldk-client/src/wt_client.rs index 92063d8d..07f5aadd 100644 --- a/teos-ldk-client/src/wt_client.rs +++ b/teos-ldk-client/src/wt_client.rs @@ -277,8 +277,8 @@ impl WTClient { mod tests { use super::*; + use crate::storage::create_storage; use crate::storage::mock_kv::MemoryStore; - use crate::storage::{create_storage, StorageConfig}; use teos_common::cryptography; use tokio::sync::mpsc::unbounded_channel; @@ -291,10 +291,10 @@ mod tests { #[tokio::test] async fn test_add_update_load_tower() { let keypair = cryptography::get_random_keypair(); - let storage = create_storage(StorageConfig::KV { - kv_store: MemoryStore::new().into_dyn_store(), - sk: vec![0; 32], - }) + let storage = create_storage( + MemoryStore::new().into_dyn_store(), + keypair.0.secret_bytes().to_vec(), + ) .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -390,10 +390,10 @@ mod tests { #[tokio::test] async fn test_get_tower_status() { let keypair = cryptography::get_random_keypair(); - let storage = create_storage(StorageConfig::KV { - kv_store: MemoryStore::new().into_dyn_store(), - sk: vec![0; 32], - }) + let storage = create_storage( + MemoryStore::new().into_dyn_store(), + keypair.0.secret_bytes().to_vec(), + ) .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -418,10 +418,10 @@ mod tests { #[tokio::test] async fn test_set_tower_status() { let keypair = cryptography::get_random_keypair(); - let storage = create_storage(StorageConfig::KV { - kv_store: MemoryStore::new().into_dyn_store(), - sk: vec![0; 32], - }) + let storage = create_storage( + MemoryStore::new().into_dyn_store(), + keypair.0.secret_bytes().to_vec(), + ) .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -453,10 +453,10 @@ mod tests { #[tokio::test] async fn test_add_appointment_receipt() { let keypair = cryptography::get_random_keypair(); - let storage = create_storage(StorageConfig::KV { - kv_store: MemoryStore::new().into_dyn_store(), - sk: vec![0; 32], - }) + let storage = create_storage( + MemoryStore::new().into_dyn_store(), + keypair.0.secret_bytes().to_vec(), + ) .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -508,10 +508,10 @@ mod tests { #[tokio::test] async fn test_add_pending_appointment() { let keypair = cryptography::get_random_keypair(); - let storage = create_storage(StorageConfig::KV { - kv_store: MemoryStore::new().into_dyn_store(), - sk: vec![0; 32], - }) + let storage = create_storage( + MemoryStore::new().into_dyn_store(), + keypair.0.secret_bytes().to_vec(), + ) .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -556,10 +556,10 @@ mod tests { #[tokio::test] async fn test_remove_pending_appointment() { let keypair = cryptography::get_random_keypair(); - let storage = create_storage(StorageConfig::KV { - kv_store: MemoryStore::new().into_dyn_store(), - sk: vec![0; 32], - }) + let storage = create_storage( + MemoryStore::new().into_dyn_store(), + keypair.0.secret_bytes().to_vec(), + ) .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -592,10 +592,10 @@ mod tests { #[tokio::test] async fn test_add_invalid_appointment() { let keypair = cryptography::get_random_keypair(); - let storage = create_storage(StorageConfig::KV { - kv_store: MemoryStore::new().into_dyn_store(), - sk: vec![0; 32], - }) + let storage = create_storage( + MemoryStore::new().into_dyn_store(), + keypair.0.secret_bytes().to_vec(), + ) .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -636,10 +636,10 @@ mod tests { #[tokio::test] async fn test_move_pending_appointment_to_invalid() { let keypair = cryptography::get_random_keypair(); - let storage = create_storage(StorageConfig::KV { - kv_store: MemoryStore::new().into_dyn_store(), - sk: vec![0; 32], - }) + let storage = create_storage( + MemoryStore::new().into_dyn_store(), + keypair.0.secret_bytes().to_vec(), + ) .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -685,10 +685,10 @@ mod tests { async fn test_move_pending_appointment_to_invalid_multiple_towers() { // Check that moving an appointment from pending to invalid can be done even if multiple towers have a reference to it let keypair = cryptography::get_random_keypair(); - let storage = create_storage(StorageConfig::KV { - kv_store: MemoryStore::new().into_dyn_store(), - sk: vec![0; 32], - }) + let storage = create_storage( + MemoryStore::new().into_dyn_store(), + keypair.0.secret_bytes().to_vec(), + ) .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -764,10 +764,10 @@ mod tests { #[tokio::test] async fn test_flag_misbehaving_tower() { let keypair = cryptography::get_random_keypair(); - let storage = create_storage(StorageConfig::KV { - kv_store: MemoryStore::new().into_dyn_store(), - sk: vec![0; 32], - }) + let storage = create_storage( + MemoryStore::new().into_dyn_store(), + keypair.0.secret_bytes().to_vec(), + ) .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -804,10 +804,10 @@ mod tests { #[tokio::test] async fn test_remove_tower() { let keypair = cryptography::get_random_keypair(); - let storage = create_storage(StorageConfig::KV { - kv_store: MemoryStore::new().into_dyn_store(), - sk: vec![0; 32], - }) + let storage = create_storage( + MemoryStore::new().into_dyn_store(), + keypair.0.secret_bytes().to_vec(), + ) .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -873,10 +873,10 @@ mod tests { // 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 keypair = cryptography::get_random_keypair(); - let storage = create_storage(StorageConfig::KV { - kv_store: MemoryStore::new().into_dyn_store(), - sk: vec![0; 32], - }) + let storage = create_storage( + MemoryStore::new().into_dyn_store(), + keypair.0.secret_bytes().to_vec(), + ) .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; @@ -935,10 +935,10 @@ mod tests { #[tokio::test] async fn test_remove_inexistent_tower() { let keypair = cryptography::get_random_keypair(); - let storage = create_storage(StorageConfig::KV { - kv_store: MemoryStore::new().into_dyn_store(), - sk: vec![0; 32], - }) + let storage = create_storage( + MemoryStore::new().into_dyn_store(), + keypair.0.secret_bytes().to_vec(), + ) .unwrap(); let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; From 3afce12730a4964c499e12ceb995cba5cf2393de Mon Sep 17 00:00:00 2001 From: dzdidi Date: Tue, 25 Feb 2025 12:48:29 +0100 Subject: [PATCH 81/85] create strorage inside of wt client Signed-off-by: dzdidi --- teos-ldk-client/src/retrier.rs | 193 ++++++++++++++--------------- teos-ldk-client/src/storage/kv.rs | 1 + teos-ldk-client/src/storage/mod.rs | 2 +- teos-ldk-client/src/wt_client.rs | 128 +++++++++---------- 4 files changed, 150 insertions(+), 174 deletions(-) diff --git a/teos-ldk-client/src/retrier.rs b/teos-ldk-client/src/retrier.rs index c9b9db42..6d7120c4 100644 --- a/teos-ldk-client/src/retrier.rs +++ b/teos-ldk-client/src/retrier.rs @@ -574,8 +574,6 @@ impl Retrier { mod tests { use super::*; - use crate::storage::create_storage; - use serde_json::json; use tokio::sync::mpsc::unbounded_channel; @@ -590,7 +588,9 @@ mod tests { }; use crate::net::http::ApiError; + use crate::storage::kv::KVStorage; use crate::storage::mock_kv::MemoryStore; + use crate::storage::persister::Persister; use crate::test_utils::get_dummy_add_appointment_response; const LONG_AUTO_RETRY_DELAY: u32 = 60; @@ -628,14 +628,13 @@ mod tests { async fn test_manage_retry_reachable() { let (tx, rx) = unbounded_channel(); let keypair = cryptography::get_random_keypair(); - let storage = create_storage( - MemoryStore::new().into_dyn_store(), - keypair.0.secret_bytes().to_vec(), - ) - .unwrap(); - let wt_client = Arc::new(Mutex::new( - WTClient::new(storage, keypair.0, unbounded_channel().0).await, + WTClient::new( + MemoryStore::new().into_dyn_store(), + keypair.0, + unbounded_channel().0, + ) + .await, )); let mut server = mockito::Server::new_async().await; @@ -727,14 +726,13 @@ mod tests { async fn test_manage_retry_unreachable() { let (tx, rx) = unbounded_channel(); let keypair = cryptography::get_random_keypair(); - let storage = create_storage( - MemoryStore::new().into_dyn_store(), - keypair.0.secret_bytes().to_vec(), - ) - .unwrap(); - let wt_client = Arc::new(Mutex::new( - WTClient::new(storage, keypair.0, unbounded_channel().0).await, + WTClient::new( + MemoryStore::new().into_dyn_store(), + keypair.0, + unbounded_channel().0, + ) + .await, )); // Add a tower with pending appointments @@ -859,14 +857,13 @@ mod tests { async fn test_manage_retry_rejected() { let (tx, rx) = unbounded_channel(); let keypair = cryptography::get_random_keypair(); - let storage = create_storage( - MemoryStore::new().into_dyn_store(), - keypair.0.secret_bytes().to_vec(), - ) - .unwrap(); - let wt_client = Arc::new(Mutex::new( - WTClient::new(storage, keypair.0, unbounded_channel().0).await, + WTClient::new( + MemoryStore::new().into_dyn_store(), + keypair.0, + unbounded_channel().0, + ) + .await, )); let mut server = mockito::Server::new_async().await; @@ -968,14 +965,13 @@ mod tests { async fn test_manage_retry_misbehaving() { let (tx, rx) = unbounded_channel(); let keypair = cryptography::get_random_keypair(); - let storage = create_storage( - MemoryStore::new().into_dyn_store(), - keypair.0.secret_bytes().to_vec(), - ) - .unwrap(); - let wt_client = Arc::new(Mutex::new( - WTClient::new(storage, keypair.0, unbounded_channel().0).await, + WTClient::new( + MemoryStore::new().into_dyn_store(), + keypair.0, + unbounded_channel().0, + ) + .await, )); let mut server = mockito::Server::new_async().await; @@ -1065,14 +1061,13 @@ mod tests { async fn test_manage_retry_abandoned() { let keypair = cryptography::get_random_keypair(); let (tx, rx) = unbounded_channel(); - let storage = create_storage( - MemoryStore::new().into_dyn_store(), - keypair.0.secret_bytes().to_vec(), - ) - .unwrap(); - let wt_client = Arc::new(Mutex::new( - WTClient::new(storage, keypair.0, unbounded_channel().0).await, + WTClient::new( + MemoryStore::new().into_dyn_store(), + keypair.0, + unbounded_channel().0, + ) + .await, )); let server = mockito::Server::new_async().await; @@ -1113,14 +1108,13 @@ mod tests { async fn test_manage_retry_subscription_error() { let (tx, rx) = unbounded_channel(); let keypair = cryptography::get_random_keypair(); - let storage = create_storage( - MemoryStore::new().into_dyn_store(), - keypair.0.secret_bytes().to_vec(), - ) - .unwrap(); - let wt_client = Arc::new(Mutex::new( - WTClient::new(storage, keypair.0, unbounded_channel().0).await, + WTClient::new( + MemoryStore::new().into_dyn_store(), + keypair.0, + unbounded_channel().0, + ) + .await, )); let mut server = mockito::Server::new_async().await; @@ -1239,23 +1233,23 @@ mod tests { // 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 storage = create_storage( - MemoryStore::new().into_dyn_store(), - keypair.0.secret_bytes().to_vec(), - ) - .unwrap(); - let receipt = get_random_registration_receipt(); - storage - .store_tower_record(tower_id, "http://unreachable.tower", &receipt) - .unwrap(); + + let store = MemoryStore::new().into_dyn_store(); + let mut storage = KVStorage::new(store.clone(), keypair.0.secret_bytes().to_vec()).unwrap(); let appointment = generate_random_appointment(None); storage .store_pending_appointment(tower_id, &appointment) .unwrap(); + + let receipt = get_random_registration_receipt(); + storage + .store_tower_record(tower_id, "http://unreachable.tower", &receipt) + .unwrap(); + // Now we can create the WTClient and check that the data is pending let wt_client = Arc::new(Mutex::new( - WTClient::new(storage, keypair.0, tx.clone()).await, + WTClient::new(store, keypair.0, tx.clone()).await, )); // Also create the retrier thread so retries can be managed @@ -1392,14 +1386,13 @@ mod tests { let (tower_sk, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); let keypair = cryptography::get_random_keypair(); - let storage = create_storage( - MemoryStore::new().into_dyn_store(), - keypair.0.secret_bytes().to_vec(), - ) - .unwrap(); - let wt_client = Arc::new(Mutex::new( - WTClient::new(storage, keypair.0, unbounded_channel().0).await, + WTClient::new( + MemoryStore::new().into_dyn_store(), + keypair.0, + unbounded_channel().0, + ) + .await, )); let mut server = mockito::Server::new_async().await; @@ -1447,14 +1440,13 @@ mod tests { let (_, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); let keypair = cryptography::get_random_keypair(); - let storage = create_storage( - MemoryStore::new().into_dyn_store(), - keypair.0.secret_bytes().to_vec(), - ) - .unwrap(); - let wt_client = Arc::new(Mutex::new( - WTClient::new(storage, keypair.0, unbounded_channel().0).await, + WTClient::new( + MemoryStore::new().into_dyn_store(), + keypair.0, + unbounded_channel().0, + ) + .await, )); let server = mockito::Server::new_async().await; @@ -1476,14 +1468,13 @@ mod tests { let (_, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); let keypair = cryptography::get_random_keypair(); - let storage = create_storage( - MemoryStore::new().into_dyn_store(), - keypair.0.secret_bytes().to_vec(), - ) - .unwrap(); - let wt_client = Arc::new(Mutex::new( - WTClient::new(storage, keypair.0, unbounded_channel().0).await, + WTClient::new( + MemoryStore::new().into_dyn_store(), + keypair.0, + unbounded_channel().0, + ) + .await, )); let mut server = mockito::Server::new_async().await; @@ -1533,14 +1524,13 @@ mod tests { let (_, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); let keypair = cryptography::get_random_keypair(); - let storage = create_storage( - MemoryStore::new().into_dyn_store(), - keypair.0.secret_bytes().to_vec(), - ) - .unwrap(); - let wt_client = Arc::new(Mutex::new( - WTClient::new(storage, keypair.0, unbounded_channel().0).await, + WTClient::new( + MemoryStore::new().into_dyn_store(), + keypair.0, + unbounded_channel().0, + ) + .await, )); // The tower we'd like to retry sending appointments to has to exist within the plugin @@ -1570,14 +1560,13 @@ mod tests { let (_, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); let keypair = cryptography::get_random_keypair(); - let storage = create_storage( - MemoryStore::new().into_dyn_store(), - keypair.0.secret_bytes().to_vec(), - ) - .unwrap(); - let wt_client = Arc::new(Mutex::new( - WTClient::new(storage, keypair.0, unbounded_channel().0).await, + WTClient::new( + MemoryStore::new().into_dyn_store(), + keypair.0, + unbounded_channel().0, + ) + .await, )); let mut server = mockito::Server::new_async().await; @@ -1630,14 +1619,13 @@ mod tests { let (_, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); let keypair = cryptography::get_random_keypair(); - let storage = create_storage( - MemoryStore::new().into_dyn_store(), - keypair.0.secret_bytes().to_vec(), - ) - .unwrap(); - let wt_client = Arc::new(Mutex::new( - WTClient::new(storage, keypair.0, unbounded_channel().0).await, + WTClient::new( + MemoryStore::new().into_dyn_store(), + keypair.0, + unbounded_channel().0, + ) + .await, )); let mut server = mockito::Server::new_async().await; @@ -1696,14 +1684,13 @@ mod tests { let (_, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); let keypair = cryptography::get_random_keypair(); - let storage = create_storage( - MemoryStore::new().into_dyn_store(), - keypair.0.secret_bytes().to_vec(), - ) - .unwrap(); - let wt_client = Arc::new(Mutex::new( - WTClient::new(storage, keypair.0, unbounded_channel().0).await, + WTClient::new( + MemoryStore::new().into_dyn_store(), + keypair.0, + unbounded_channel().0, + ) + .await, )); // The tower we'd like to retry sending appointments to has to exist within the plugin diff --git a/teos-ldk-client/src/storage/kv.rs b/teos-ldk-client/src/storage/kv.rs index c1b327ee..f22083a7 100644 --- a/teos-ldk-client/src/storage/kv.rs +++ b/teos-ldk-client/src/storage/kv.rs @@ -21,6 +21,7 @@ impl From for PersisterError { } use crate::{AppointmentStatus, MisbehaviorProof, TowerInfo, TowerSummary}; +// XXX: this is taken from LDK and should be imported after it is made public there pub type DynStore = dyn KVStore + Sync + Send; pub struct KVStorage { diff --git a/teos-ldk-client/src/storage/mod.rs b/teos-ldk-client/src/storage/mod.rs index 1cb0de4a..e4e212f1 100644 --- a/teos-ldk-client/src/storage/mod.rs +++ b/teos-ldk-client/src/storage/mod.rs @@ -3,7 +3,7 @@ pub mod persister; pub use crate::storage::persister::{Persister, PersisterError}; mod encryption; -mod kv; +pub(crate) mod kv; mod namespace; #[cfg(test)] diff --git a/teos-ldk-client/src/wt_client.rs b/teos-ldk-client/src/wt_client.rs index 07f5aadd..d16fde76 100644 --- a/teos-ldk-client/src/wt_client.rs +++ b/teos-ldk-client/src/wt_client.rs @@ -1,6 +1,7 @@ use crate::storage::persister::{Persister, PersisterError}; use std::collections::{HashMap, HashSet}; use std::iter::FromIterator; +use std::sync::Arc; use tokio::sync::mpsc::UnboundedSender; @@ -13,6 +14,8 @@ use teos_common::{TowerId, UserId}; use crate::retrier::RetrierStatus; use crate::{MisbehaviorProof, SubscriptionError, TowerInfo, TowerStatus, TowerSummary}; +use crate::storage::kv::{DynStore, KVStorage}; + #[derive(Eq, PartialEq)] pub enum RevocationData { Fresh(Locator), @@ -70,10 +73,11 @@ pub struct WTClient { impl WTClient { pub async fn new( - storage: Box, + store: Arc, user_sk: SecretKey, unreachable_towers: UnboundedSender<(TowerId, RevocationData)>, ) -> Self { + let storage = Box::new(KVStorage::new(store, user_sk.secret_bytes().to_vec()).unwrap()); let user_id = UserId(PublicKey::from_secret_key(&Secp256k1::new(), &user_sk)); let towers = storage.load_towers(); @@ -276,8 +280,6 @@ impl WTClient { #[cfg(test)] mod tests { use super::*; - - use crate::storage::create_storage; use crate::storage::mock_kv::MemoryStore; use teos_common::cryptography; use tokio::sync::mpsc::unbounded_channel; @@ -291,13 +293,12 @@ mod tests { #[tokio::test] async fn test_add_update_load_tower() { let keypair = cryptography::get_random_keypair(); - let storage = create_storage( + let mut wt_client = WTClient::new( MemoryStore::new().into_dyn_store(), - keypair.0.secret_bytes().to_vec(), + keypair.0, + unbounded_channel().0, ) - .unwrap(); - - let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; + .await; // Adding a new tower will add a summary to towers and the full data to the let mut receipt = get_random_registration_receipt(); @@ -379,7 +380,7 @@ mod tests { wt_client.add_appointment_receipt( tower_id, locator, - 0, // FIXME: the problem is that this reset does not work + 0, &get_random_appointment_receipt(tower_sk), ); wt_client @@ -390,13 +391,12 @@ mod tests { #[tokio::test] async fn test_get_tower_status() { let keypair = cryptography::get_random_keypair(); - let storage = create_storage( + let mut wt_client = WTClient::new( MemoryStore::new().into_dyn_store(), - keypair.0.secret_bytes().to_vec(), + keypair.0, + unbounded_channel().0, ) - .unwrap(); - - let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; + .await; // If the tower is unknown, get_tower_status returns None let tower_id = get_random_user_id(); @@ -418,13 +418,12 @@ mod tests { #[tokio::test] async fn test_set_tower_status() { let keypair = cryptography::get_random_keypair(); - let storage = create_storage( + let mut wt_client = WTClient::new( MemoryStore::new().into_dyn_store(), - keypair.0.secret_bytes().to_vec(), + keypair.0, + unbounded_channel().0, ) - .unwrap(); - - let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; + .await; // If the tower is unknown nothing will happen let unknown_tower = get_random_user_id(); @@ -453,13 +452,12 @@ mod tests { #[tokio::test] async fn test_add_appointment_receipt() { let keypair = cryptography::get_random_keypair(); - let storage = create_storage( + let mut wt_client = WTClient::new( MemoryStore::new().into_dyn_store(), - keypair.0.secret_bytes().to_vec(), + keypair.0, + unbounded_channel().0, ) - .unwrap(); - - let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; + .await; let (tower_sk, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); @@ -508,13 +506,12 @@ mod tests { #[tokio::test] async fn test_add_pending_appointment() { let keypair = cryptography::get_random_keypair(); - let storage = create_storage( + let mut wt_client = WTClient::new( MemoryStore::new().into_dyn_store(), - keypair.0.secret_bytes().to_vec(), + keypair.0, + unbounded_channel().0, ) - .unwrap(); - - let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; + .await; let tower_id = get_random_user_id(); @@ -556,13 +553,12 @@ mod tests { #[tokio::test] async fn test_remove_pending_appointment() { let keypair = cryptography::get_random_keypair(); - let storage = create_storage( + let mut wt_client = WTClient::new( MemoryStore::new().into_dyn_store(), - keypair.0.secret_bytes().to_vec(), + keypair.0, + unbounded_channel().0, ) - .unwrap(); - - let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; + .await; let tower_id = get_random_user_id(); @@ -592,13 +588,12 @@ mod tests { #[tokio::test] async fn test_add_invalid_appointment() { let keypair = cryptography::get_random_keypair(); - let storage = create_storage( + let mut wt_client = WTClient::new( MemoryStore::new().into_dyn_store(), - keypair.0.secret_bytes().to_vec(), + keypair.0, + unbounded_channel().0, ) - .unwrap(); - - let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; + .await; let tower_id = get_random_user_id(); @@ -636,13 +631,12 @@ mod tests { #[tokio::test] async fn test_move_pending_appointment_to_invalid() { let keypair = cryptography::get_random_keypair(); - let storage = create_storage( + let mut wt_client = WTClient::new( MemoryStore::new().into_dyn_store(), - keypair.0.secret_bytes().to_vec(), + keypair.0, + unbounded_channel().0, ) - .unwrap(); - - let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; + .await; let tower_id = get_random_user_id(); @@ -685,13 +679,12 @@ mod tests { async fn test_move_pending_appointment_to_invalid_multiple_towers() { // Check that moving an appointment from pending to invalid can be done even if multiple towers have a reference to it let keypair = cryptography::get_random_keypair(); - let storage = create_storage( + let mut wt_client = WTClient::new( MemoryStore::new().into_dyn_store(), - keypair.0.secret_bytes().to_vec(), + keypair.0, + unbounded_channel().0, ) - .unwrap(); - - let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; + .await; let tower_id = get_random_user_id(); let another_tower_id = get_random_user_id(); @@ -764,13 +757,12 @@ mod tests { #[tokio::test] async fn test_flag_misbehaving_tower() { let keypair = cryptography::get_random_keypair(); - let storage = create_storage( + let mut wt_client = WTClient::new( MemoryStore::new().into_dyn_store(), - keypair.0.secret_bytes().to_vec(), + keypair.0, + unbounded_channel().0, ) - .unwrap(); - - let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; + .await; let (tower_sk, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); @@ -804,13 +796,12 @@ mod tests { #[tokio::test] async fn test_remove_tower() { let keypair = cryptography::get_random_keypair(); - let storage = create_storage( + let mut wt_client = WTClient::new( MemoryStore::new().into_dyn_store(), - keypair.0.secret_bytes().to_vec(), + keypair.0, + unbounded_channel().0, ) - .unwrap(); - - let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; + .await; let receipt = get_random_registration_receipt(); let (tower_sk, tower_pk) = cryptography::get_random_keypair(); @@ -853,7 +844,6 @@ mod tests { registration_receipt.available_slots(), &appointment_receipt, ); - // FIXME assert!(wt_client .storage .appointment_receipt_exists(locator, tower_id)); @@ -873,13 +863,12 @@ mod tests { // 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 keypair = cryptography::get_random_keypair(); - let storage = create_storage( + let mut wt_client = WTClient::new( MemoryStore::new().into_dyn_store(), - keypair.0.secret_bytes().to_vec(), + keypair.0, + unbounded_channel().0, ) - .unwrap(); - - let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; + .await; let receipt = get_random_registration_receipt(); let (tower1_sk, tower1_pk) = cryptography::get_random_keypair(); @@ -935,13 +924,12 @@ mod tests { #[tokio::test] async fn test_remove_inexistent_tower() { let keypair = cryptography::get_random_keypair(); - let storage = create_storage( + let mut wt_client = WTClient::new( MemoryStore::new().into_dyn_store(), - keypair.0.secret_bytes().to_vec(), + keypair.0, + unbounded_channel().0, ) - .unwrap(); - - let mut wt_client = WTClient::new(storage, keypair.0, unbounded_channel().0).await; + .await; let tower_id = get_random_user_id(); let err = wt_client.remove_tower(tower_id).unwrap_err(); From c5e128b5a665438c827d6c74a89c6fc50961882c Mon Sep 17 00:00:00 2001 From: dzdidi Date: Tue, 25 Feb 2025 15:06:01 +0100 Subject: [PATCH 82/85] add on_commitment_revocation Signed-off-by: dzdidi --- teos-ldk-client/README.md | 4 +- teos-ldk-client/src/lib.rs | 145 ++++++++++++++++++++++ teos-ldk-client/src/storage/encryption.rs | 11 +- 3 files changed, 151 insertions(+), 9 deletions(-) diff --git a/teos-ldk-client/README.md b/teos-ldk-client/README.md index dd0ee27c..1fb7d1ab 100644 --- a/teos-ldk-client/README.md +++ b/teos-ldk-client/README.md @@ -16,7 +16,9 @@ The client instance has the following methods: - `get_subscription_info `: gets the subscription information by querying the tower. - `get_appointment_receipt `: pulls a given appointment receipt from the local database. - `get_registration_receipt `: pulls the latest registration receipt from the local database. -- `on_commitment_revocation `: sends appointments to the registered towers for every new commitment transaction. + +The general usage idea +- `on_commitment_revocation `: sends appointments to the registered towers for every new commitment transaction. # Configuration diff --git a/teos-ldk-client/src/lib.rs b/teos-ldk-client/src/lib.rs index ebc4191e..6545bcd3 100644 --- a/teos-ldk-client/src/lib.rs +++ b/teos-ldk-client/src/lib.rs @@ -1,5 +1,7 @@ use std::collections::{HashMap, HashSet}; use std::fmt; +use std::sync::MutexGuard; +use std::sync::{Arc, Mutex}; use serde::{Deserialize, Serialize}; @@ -7,6 +9,12 @@ use teos_common::appointment::{Appointment, Locator}; use teos_common::net::NetAddr; use teos_common::receipts::AppointmentReceipt; use teos_common::TowerId; +use teos_common::{cryptography, errors}; + +use crate::convert::CommitmentRevocation; +use crate::http::AddAppointmentError; +use crate::net::http; +use crate::wt_client::{RevocationData, WTClient}; pub mod convert; pub mod net; @@ -280,6 +288,143 @@ impl MisbehaviorProof { } } +/// 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). +pub async fn on_commitment_revocation( + wt_client: Arc>, + commitment_revocation: CommitmentRevocation, +) -> Result<(), Box> { + log::debug!( + "New commitment revocation received for channel {}. Commit number {}", + commitment_revocation.channel_id, + commitment_revocation.commit_num + ); + + // TODO: For now, to_self_delay is hardcoded to 42. Revisit and define it better / remove it when / if needed + let locator = Locator::new(commitment_revocation.commitment_txid); + let appointment = Appointment::new( + locator, + cryptography::encrypt( + &commitment_revocation.penalty_tx, + &commitment_revocation.commitment_txid, + ) + .unwrap(), + 42, + ); + let signature = + cryptography::sign(&appointment.to_vec(), &wt_client.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. + let towers = wt_client + .lock() + .unwrap() + .towers + .iter() + .map(|(id, info)| (*id, info.net_addr.clone(), info.status)) + .collect::>(); + + for (tower_id, net_addr, status) in towers { + if status.is_reachable() { + match http::add_appointment(tower_id, &net_addr, &appointment, &signature).await { + Ok((slots, receipt)) => { + wt_client + .lock() + .unwrap() + .add_appointment_receipt(tower_id, locator, slots, &receipt); + log::debug!("Response verified and data stored in the database"); + } + Err(e) => match e { + AddAppointmentError::RequestError(e) => { + if e.is_connection() { + log::warn!( + "{tower_id} cannot be reached. Adding {} to pending appointments", + appointment.locator + ); + let mut state = wt_client.lock().unwrap(); + state.set_tower_status(tower_id, TowerStatus::TemporaryUnreachable); + state.add_pending_appointment(tower_id, &appointment); + send_to_retrier(&state, tower_id, appointment.locator); + } + } + AddAppointmentError::ApiError(e) => match e.error_code { + errors::INVALID_SIGNATURE_OR_SUBSCRIPTION_ERROR => { + log::warn!( + "There is a subscription issue with {tower_id}. Adding {} to pending", + appointment.locator + ); + let mut state = wt_client.lock().unwrap(); + state.set_tower_status(tower_id, TowerStatus::SubscriptionError); + state.add_pending_appointment(tower_id, &appointment); + send_to_retrier(&state, tower_id, appointment.locator); + } + + _ => { + log::warn!( + "{tower_id} rejected the appointment. Error: {}, error_code: {}", + e.error, + e.error_code + ); + wt_client + .lock() + .unwrap() + .add_invalid_appointment(tower_id, &appointment); + } + }, + AddAppointmentError::SignatureError(proof) => { + log::warn!("Cannot recover known tower_id from the appointment receipt. Flagging tower as misbehaving"); + wt_client + .lock() + .unwrap() + .flag_misbehaving_tower(tower_id, proof) + } + }, + }; + } else if status.is_misbehaving() { + 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 {tower_id}. Adding {} to pending", + appointment.locator + ); + } else { + log::warn!( + "{tower_id} is {status}. Adding {} to pending", + appointment.locator, + ); + } + + let mut state = wt_client.lock().unwrap(); + state.add_pending_appointment(tower_id, &appointment); + + if !status.is_unreachable() { + send_to_retrier(&state, tower_id, appointment.locator); + } + } + } + + Ok(()) +} + +/// 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})") + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/teos-ldk-client/src/storage/encryption.rs b/teos-ldk-client/src/storage/encryption.rs index 44b5e9a1..6dd94d88 100644 --- a/teos-ldk-client/src/storage/encryption.rs +++ b/teos-ldk-client/src/storage/encryption.rs @@ -16,16 +16,12 @@ pub(crate) fn encrypt( message: &[u8], secret: &[u8], ) -> Result, chacha20poly1305::aead::Error> { - // Create nonce [0; 12] let nonce = Nonce::default(); - - // Hash the secret to create the encryption key let key_hash = sha256::Hash::hash(secret); let key = Key::from_slice(key_hash.as_byte_array()); - // Create cipher instance let cipher = ChaCha20Poly1305::new(key); - // Encrypt the message + cipher.encrypt(&nonce, message) } @@ -40,10 +36,9 @@ pub(crate) fn decrypt( encrypted_blob: &[u8], secret: &[u8], ) -> Result, chacha20poly1305::aead::Error> { - // Defaults is [0; 12] let nonce = Nonce::default(); - let k = sha256::Hash::hash(secret); - let key = Key::from_slice(k.as_byte_array()); + let key_hash = sha256::Hash::hash(secret); + let key = Key::from_slice(key_hash.as_byte_array()); let cypher = ChaCha20Poly1305::new(key); From e930acbace4000307361f019fd2f02b14f84e7a5 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Wed, 26 Feb 2025 13:49:01 +0100 Subject: [PATCH 83/85] encryptor class Signed-off-by: dzdidi --- teos-ldk-client/src/storage/encryption.rs | 46 --------------- teos-ldk-client/src/storage/encryptor.rs | 68 +++++++++++++++++++++++ teos-ldk-client/src/storage/kv.rs | 13 +++-- teos-ldk-client/src/storage/mod.rs | 2 +- 4 files changed, 77 insertions(+), 52 deletions(-) delete mode 100644 teos-ldk-client/src/storage/encryption.rs create mode 100644 teos-ldk-client/src/storage/encryptor.rs diff --git a/teos-ldk-client/src/storage/encryption.rs b/teos-ldk-client/src/storage/encryption.rs deleted file mode 100644 index 6dd94d88..00000000 --- a/teos-ldk-client/src/storage/encryption.rs +++ /dev/null @@ -1,46 +0,0 @@ -use chacha20poly1305::{ - aead::{Aead, KeyInit}, - ChaCha20Poly1305, Key, Nonce, -}; - -use bitcoin::hashes::{sha256, Hash}; - -/// Encrypts a given message under a given secret using `chacha20poly1305`. -/// -/// The key material used is: -/// - The dispute txid as encryption key. -/// - `[0; 12]` as IV. -/// -/// The message to be encrypted is expected to be the penalty transaction. -pub(crate) fn encrypt( - message: &[u8], - secret: &[u8], -) -> Result, chacha20poly1305::aead::Error> { - let nonce = Nonce::default(); - let key_hash = sha256::Hash::hash(secret); - let key = Key::from_slice(key_hash.as_byte_array()); - - let cipher = ChaCha20Poly1305::new(key); - - cipher.encrypt(&nonce, message) -} - -/// Decrypts an encrypted blob of data using `chacha20poly1305` and a given secret. -/// -/// The key material used is: -/// - The dispute txid as decryption key. -/// - `[0; 12]` as IV. -/// -/// The result is expected to be a penalty transaction. -pub(crate) fn decrypt( - encrypted_blob: &[u8], - secret: &[u8], -) -> Result, chacha20poly1305::aead::Error> { - let nonce = Nonce::default(); - let key_hash = sha256::Hash::hash(secret); - let key = Key::from_slice(key_hash.as_byte_array()); - - let cypher = ChaCha20Poly1305::new(key); - - cypher.decrypt(&nonce, encrypted_blob.as_ref()) -} diff --git a/teos-ldk-client/src/storage/encryptor.rs b/teos-ldk-client/src/storage/encryptor.rs new file mode 100644 index 00000000..13115d7e --- /dev/null +++ b/teos-ldk-client/src/storage/encryptor.rs @@ -0,0 +1,68 @@ +use chacha20poly1305::{ + aead::{Aead, KeyInit}, + ChaCha20Poly1305, Key, Nonce, +}; + +use bitcoin::hashes::{sha256, Hash}; + +/// A struct handling encryption and decryption using ChaCha20Poly1305 +pub(crate) struct Encryptor { + cipher: ChaCha20Poly1305, + nonce: Nonce, +} + +impl Encryptor { + /// Creates a new Encryptor instance with the given secret + /// + /// # Arguments + /// * `secret` - The secret used to derive the encryption key + pub fn new(secret: &[u8]) -> Self { + let key_hash = sha256::Hash::hash(secret); + let key = Key::from_slice(key_hash.as_byte_array()); + + Self { + cipher: ChaCha20Poly1305::new(key), + nonce: Nonce::default(), // [0; 12] + } + } + + /// Encrypts a given message using the initialized cipher + /// + /// # Arguments + /// * `message` - The message to encrypt (expected to be a penalty transaction) + /// + /// # Returns + /// The encrypted message or an encryption error + pub fn encrypt(&self, message: &[u8]) -> Result, chacha20poly1305::aead::Error> { + self.cipher.encrypt(&self.nonce, message) + } + + /// Decrypts an encrypted blob using the initialized cipher + /// + /// # Arguments + /// * `encrypted_blob` - The encrypted data to decrypt + /// + /// # Returns + /// The decrypted message (expected to be a penalty transaction) or a decryption error + pub fn decrypt(&self, encrypted_blob: &[u8]) -> Result, chacha20poly1305::aead::Error> { + self.cipher.decrypt(&self.nonce, encrypted_blob) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_encryption_decryption() { + let secret = b"test_secret"; + let message = b"Hello, World!"; + + let encryptor = Encryptor::new(secret); + + let encrypted = encryptor.encrypt(message).unwrap(); + let decrypted = encryptor.decrypt(&encrypted).unwrap(); + + assert_eq!(message.to_vec(), decrypted); + } +} diff --git a/teos-ldk-client/src/storage/kv.rs b/teos-ldk-client/src/storage/kv.rs index f22083a7..7dd810f9 100644 --- a/teos-ldk-client/src/storage/kv.rs +++ b/teos-ldk-client/src/storage/kv.rs @@ -11,7 +11,7 @@ use teos_common::appointment::{Appointment, Locator}; use teos_common::receipts::{AppointmentReceipt, RegistrationReceipt}; use teos_common::{TowerId, UserId}; -use crate::storage::encryption::{decrypt, encrypt}; +use crate::storage::encryptor::Encryptor; use crate::storage::namespace::{get_appointment_namespace, KeySpace, NameSpace}; impl From for PersisterError { @@ -26,12 +26,15 @@ pub type DynStore = dyn KVStore + Sync + Send; pub struct KVStorage { store: Arc, - sk: Vec, + encryptor: Box, } impl KVStorage { pub fn new(store: Arc, sk: Vec) -> Result { - Ok(KVStorage { store, sk }) + Ok(KVStorage { + store, + encryptor: Box::new(Encryptor::new(&sk[..])), + }) } fn store_item( @@ -42,7 +45,7 @@ impl KVStorage { ) -> Result<(), PersisterError> { let value = bincode::serialize(value).unwrap(); let value = if encrypted { - encrypt(&value, &self.sk).unwrap() + self.encryptor.encrypt(&value).unwrap() } else { value }; @@ -69,7 +72,7 @@ impl KVStorage { ) { Ok(value) => { let value = if encrypted { - decrypt(&value, &self.sk).unwrap() + self.encryptor.decrypt(&value).unwrap() } else { value }; diff --git a/teos-ldk-client/src/storage/mod.rs b/teos-ldk-client/src/storage/mod.rs index e4e212f1..e3f47dbc 100644 --- a/teos-ldk-client/src/storage/mod.rs +++ b/teos-ldk-client/src/storage/mod.rs @@ -2,7 +2,7 @@ pub mod persister; pub use crate::storage::persister::{Persister, PersisterError}; -mod encryption; +mod encryptor; pub(crate) mod kv; mod namespace; From 4dbb41cef2044781a51b8ed82e7c4271b075e6c1 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Thu, 20 Mar 2025 10:30:47 +0100 Subject: [PATCH 84/85] remove unnecessary unwrap on signatures Signed-off-by: dzdidi --- teos-ldk-client/src/lib.rs | 2 +- teos-ldk-client/src/retrier.rs | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/teos-ldk-client/src/lib.rs b/teos-ldk-client/src/lib.rs index 6545bcd3..464a4946 100644 --- a/teos-ldk-client/src/lib.rs +++ b/teos-ldk-client/src/lib.rs @@ -313,7 +313,7 @@ pub async fn on_commitment_revocation( 42, ); let signature = - cryptography::sign(&appointment.to_vec(), &wt_client.lock().unwrap().user_sk).unwrap(); + cryptography::sign(&appointment.to_vec(), &wt_client.lock().unwrap().user_sk); // 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. diff --git a/teos-ldk-client/src/retrier.rs b/teos-ldk-client/src/retrier.rs index 6d7120c4..3ef05a7c 100644 --- a/teos-ldk-client/src/retrier.rs +++ b/teos-ldk-client/src/retrier.rs @@ -489,7 +489,7 @@ impl Retrier { tower_id, &net_addr, &appointment, - &cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(), + &cryptography::sign(&appointment.to_vec(), &user_sk), ) .await { @@ -657,7 +657,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); @@ -803,7 +803,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); @@ -994,7 +994,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 @@ -1143,7 +1143,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); @@ -1307,11 +1307,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); @@ -1414,7 +1414,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); @@ -1495,7 +1495,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 f88c9e01893937978a6a735bd9b83ca6287b3b50 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Thu, 20 Mar 2025 12:27:03 +0100 Subject: [PATCH 85/85] fmt Signed-off-by: dzdidi --- teos-ldk-client/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/teos-ldk-client/src/lib.rs b/teos-ldk-client/src/lib.rs index 464a4946..f6f0e896 100644 --- a/teos-ldk-client/src/lib.rs +++ b/teos-ldk-client/src/lib.rs @@ -312,8 +312,7 @@ pub async fn on_commitment_revocation( .unwrap(), 42, ); - let signature = - cryptography::sign(&appointment.to_vec(), &wt_client.lock().unwrap().user_sk); + let signature = cryptography::sign(&appointment.to_vec(), &wt_client.lock().unwrap().user_sk); // 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.