-
Notifications
You must be signed in to change notification settings - Fork 68
Add optional boundaries to getregistrationreceipt #199
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -258,6 +258,117 @@ impl TryFrom<serde_json::Value> for GetAppointmentParams { | |
| } | ||
| } | ||
|
|
||
| // Errors related to `getregistrationreceipt` command | ||
| #[derive(Debug)] | ||
| pub enum GetRegistrationReceiptError { | ||
| InvalidId(String), | ||
| InvalidFormat(String), | ||
| } | ||
|
|
||
| impl std::fmt::Display for GetRegistrationReceiptError { | ||
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
| match self { | ||
| GetRegistrationReceiptError::InvalidId(x) => write!(f, "{x}"), | ||
| GetRegistrationReceiptError::InvalidFormat(x) => write!(f, "{x}"), | ||
| } | ||
| } | ||
| } | ||
|
Comment on lines
+262
to
+275
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no distinction in usage between these two different error variants. I think we can combine them into one.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually I looked how other methods had this implemented and followed them 😅. Which way do you think would be better, keeping as it is, merging into one error or removing this entirely ? I will make changes accordingly.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I noticed other param parsers are doing the same. I think we can make all of them to use
Try to remove it entirely, then we can do the same for other param errors in a follow up if it made sense.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The reason why this is here is to make it properly testable. If you look at the other params, there are test suits to make sure the proper error is returned. I guess we should add them too for
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh, got it. I will add tests for
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This would only make sense if #199 (comment) is carried out. I am fine with both ways, but the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed |
||
|
|
||
| // Parameters related to the `getregistrationreceipt` command | ||
| #[derive(Debug)] | ||
| pub struct GetRegistrationReceiptParams { | ||
|
sr-gi marked this conversation as resolved.
|
||
| pub tower_id: TowerId, | ||
| pub subscription_start: Option<u32>, | ||
| pub subscription_expiry: Option<u32>, | ||
| } | ||
|
|
||
| impl TryFrom<serde_json::Value> for GetRegistrationReceiptParams { | ||
| type Error = GetRegistrationReceiptError; | ||
|
|
||
| fn try_from(value: serde_json::Value) -> Result<Self, Self::Error> { | ||
| match value { | ||
| serde_json::Value::Array(a) => { | ||
| let param_count = a.len(); | ||
| if param_count == 2 { | ||
| Err(GetRegistrationReceiptError::InvalidFormat(( | ||
| "Both ends of boundary (subscription_start and subscription_expiry) are required.").to_string() | ||
| )) | ||
| } else if param_count != 1 && param_count != 3 { | ||
| Err(GetRegistrationReceiptError::InvalidFormat(format!( | ||
| "Unexpected request format. The request needs 1 or 3 parameter. Received: {param_count}" | ||
| ))) | ||
| } else { | ||
| let tower_id = if let Some(s) = a.get(0).unwrap().as_str() { | ||
| TowerId::from_str(s).map_err(|_| { | ||
| GetRegistrationReceiptError::InvalidId("Invalid tower id".to_owned()) | ||
| }) | ||
| } else { | ||
| Err(GetRegistrationReceiptError::InvalidId( | ||
| "tower_id must be a hex encoded string".to_owned(), | ||
| )) | ||
| }?; | ||
|
|
||
| let (subscription_start, subscription_expiry) = if let (Some(start), Some(expire)) = (a.get(1), a.get(2)){ | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Insert a space after
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Has this been fixed? |
||
| let start = start.as_i64().ok_or_else(|| { | ||
| GetRegistrationReceiptError::InvalidFormat( | ||
| "Subscription_start must be a positive integer".to_owned(), | ||
| ) | ||
| })?; | ||
|
Comment on lines
+312
to
+316
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we want it a positive integer, shouldn't we use |
||
|
|
||
| let expire = expire.as_i64().ok_or_else(|| { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: can we name this variable |
||
| GetRegistrationReceiptError::InvalidFormat( | ||
| "Subscription_expire must be a positive integer".to_owned(), | ||
| ) | ||
| })?; | ||
|
|
||
| if start >= 0 && expire > start { | ||
| (Some(start as u32), Some(expire as u32)) | ||
| } else { | ||
| return Err(GetRegistrationReceiptError::InvalidFormat( | ||
| "subscription_start must be a positive integer and subscription_expire must be a positive integer greater than subscription_start".to_owned(), | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't they be the same value? If, for instance, I know what specific block I registered in. |
||
| )); | ||
| } | ||
| } else { | ||
| (None, None) | ||
| }; | ||
|
|
||
| Ok( | ||
| Self { | ||
| tower_id, | ||
| subscription_start, | ||
| subscription_expiry, | ||
| } | ||
| ) | ||
| } | ||
| }, | ||
| serde_json::Value::Object(mut m) => { | ||
| let allowed_keys = ["tower_id", "subscription_start", "subscription_expiry"]; | ||
| let param_count = m.len(); | ||
|
|
||
| if m.is_empty() || param_count > allowed_keys.len() { | ||
| Err(GetRegistrationReceiptError::InvalidFormat(format!("Unexpected request format. The request needs 1-3 parameters. Received: {param_count}"))) | ||
| } else if !m.contains_key(allowed_keys[0]){ | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. missing a space here as well, before the |
||
| Err(GetRegistrationReceiptError::InvalidId(format!("{} is mandatory", allowed_keys[0]))) | ||
| } else if !m.iter().all(|(k, _)| allowed_keys.contains(&k.as_str())) { | ||
| Err(GetRegistrationReceiptError::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); | ||
| } | ||
| } | ||
|
|
||
| GetRegistrationReceiptParams::try_from(json!(params)) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: add a line break here |
||
| } | ||
| }, | ||
| _ => Err(GetRegistrationReceiptError::InvalidFormat(format!( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Optional parameters are usually expressed in square brackets, so this should read something like: |
||
| "Unexpected request format. Expected: tower_id [subscription_start] [subscription_expire]. 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 { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,7 +3,7 @@ use std::iter::FromIterator; | |
| use std::path::PathBuf; | ||
| use std::str::FromStr; | ||
|
|
||
| use rusqlite::{params, Connection, Error as SqliteError}; | ||
| use rusqlite::{params, Connection, Error as SqliteError, ToSql}; | ||
|
|
||
| use bitcoin::secp256k1::SecretKey; | ||
|
|
||
|
|
@@ -209,36 +209,43 @@ impl DBM { | |
| Some(tower) | ||
| } | ||
|
|
||
| /// Loads the latest registration receipt for a given tower. | ||
| /// | ||
| /// Loads the registration receipt(s) for a given tower in the given subscription range. | ||
| /// If no range is given, then loads the latest receipt | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could you add a period after this docstring. |
||
| /// 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<RegistrationReceipt> { | ||
| 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(); | ||
| subscription_start: Option<u32>, | ||
| subscription_expiry: Option<u32>, | ||
| ) -> Option<Vec<RegistrationReceipt>> { | ||
| let mut query = "SELECT available_slots, subscription_start, subscription_expiry, signature FROM registration_receipts WHERE tower_id = ?1".to_string(); | ||
|
|
||
| let tower_id_encoded = tower_id.to_vec(); | ||
| let mut params: Vec<&dyn ToSql> = vec![&tower_id_encoded]; | ||
|
|
||
| if subscription_expiry.is_none() { | ||
| query.push_str(" AND subscription_expiry = (SELECT MAX(subscription_expiry) FROM registration_receipts WHERE tower_id = ?1)") | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please add a |
||
| } else { | ||
| query.push_str(" AND subscription_start>=?2 AND subscription_expiry <=?3"); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please fix the spacing around the |
||
| params.push(&subscription_start); | ||
| params.push(&subscription_expiry) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and this one as well. |
||
| } | ||
| let mut stmt = self.connection.prepare(&query).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(); | ||
| stmt.query_map(params.as_slice(), |row| { | ||
| let slots: u32 = row.get(0)?; | ||
| let start: u32 = row.get(1)?; | ||
| let expiry: u32 = row.get(2)?; | ||
| let signature: String = row.get(3)?; | ||
|
|
||
| Ok(RegistrationReceipt::with_signature( | ||
| user_id, slots, start, expiry, signature, | ||
| )) | ||
| }) | ||
| .ok() | ||
| .unwrap() | ||
| .map(|r| r.ok()) | ||
| .collect() | ||
|
Comment on lines
+246
to
+248
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can't really get my head around how I think we can
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this was previous approach, but then sr-gi suggested to keep it
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think my point here was being able to distinguish between when no data was found because the tower was not even there, and when the range didn't produce any output. However, we are checking that afterward in
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I would be for implementing this kind of logic, but the one implemented in here doesn't actually distinguish between the two cases. It always returns
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, that is my point, that the suggestion was for that, but we really don't do that here, but always return I'm happy with either one or the other. |
||
| } | ||
|
|
||
| /// Removes a tower record from the database. | ||
|
|
@@ -725,34 +732,49 @@ mod tests { | |
| let tower_id = get_random_user_id(); | ||
| let net_addr = "talaia.watch"; | ||
| let receipt = get_random_registration_receipt(); | ||
| let subscription_start = Some(receipt.subscription_start()); | ||
| let subscription_expiry = Some(receipt.subscription_expiry()); | ||
|
|
||
| // 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(), | ||
| dbm.load_registration_receipt( | ||
| tower_id, | ||
| receipt.user_id(), | ||
| subscription_start, | ||
| subscription_expiry | ||
| ) | ||
| .unwrap()[0], | ||
| receipt | ||
| ); | ||
|
|
||
| // Add another receipt for the same tower with a higher expiry and check this last one is loaded | ||
| // Add another receipt for the same tower with a higher expiry and check that output gives vector of both receipts | ||
| let middle_receipt = get_registration_receipt_from_previous(&receipt); | ||
| let latest_receipt = get_registration_receipt_from_previous(&middle_receipt); | ||
|
|
||
| let latest_subscription_expiry = Some(latest_receipt.subscription_expiry()); | ||
|
|
||
| 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 | ||
| dbm.load_registration_receipt( | ||
| tower_id, | ||
| latest_receipt.user_id(), | ||
| subscription_start, | ||
| latest_subscription_expiry | ||
| ) | ||
| .unwrap(), | ||
| vec![receipt, latest_receipt.clone()] | ||
| ); | ||
|
|
||
| // Add a final one with a lower expiry and check the last is still loaded | ||
| // Add a final one with a lower expiry and check if the lastest receipt is loaded when boundry | ||
| // params are not passed | ||
| dbm.store_tower_record(tower_id, net_addr, &middle_receipt) | ||
| .unwrap(); | ||
| assert_eq!( | ||
| dbm.load_registration_receipt(tower_id, latest_receipt.user_id()) | ||
| .unwrap(), | ||
| dbm.load_registration_receipt(tower_id, latest_receipt.user_id(), None, None) | ||
| .unwrap()[0], | ||
| latest_receipt | ||
| ); | ||
| } | ||
|
|
@@ -765,13 +787,20 @@ mod tests { | |
| let tower_id = get_random_user_id(); | ||
| let net_addr = "talaia.watch"; | ||
| let receipt = get_random_registration_receipt(); | ||
| let subscription_start = Some(receipt.subscription_start()); | ||
| let subscription_expiry = Some(receipt.subscription_expiry()); | ||
|
|
||
| // 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(), | ||
| dbm.load_registration_receipt( | ||
| tower_id, | ||
| receipt.user_id(), | ||
| subscription_start, | ||
| subscription_expiry | ||
| ) | ||
| .unwrap()[0], | ||
| receipt | ||
| ); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,7 +18,9 @@ use teos_common::protos as common_msgs; | |
| use teos_common::TowerId; | ||
| use teos_common::{cryptography, errors}; | ||
|
|
||
| use watchtower_plugin::convert::{CommitmentRevocation, GetAppointmentParams, RegisterParams}; | ||
| use watchtower_plugin::convert::{ | ||
| CommitmentRevocation, GetAppointmentParams, GetRegistrationReceiptParams, RegisterParams, | ||
| }; | ||
| use watchtower_plugin::net::http::{ | ||
| self, get_request, post_request, process_post_response, AddAppointmentError, ApiResponse, | ||
| RequestError, | ||
|
|
@@ -127,22 +129,33 @@ async fn register( | |
| Ok(json!(receipt)) | ||
| } | ||
|
|
||
| /// Gets the latest registration receipt from the client to a given tower (if it exists). | ||
| /// | ||
| /// Gets the registration receipt(s) from the client to a given tower (if it exists) in the given | ||
| /// range. If no range is given, then gets the latest registration receipt. | ||
| /// This is pulled from the database | ||
| async fn get_registration_receipt( | ||
| plugin: Plugin<Arc<Mutex<WTClient>>>, | ||
| v: serde_json::Value, | ||
| ) -> Result<serde_json::Value, Error> { | ||
| let tower_id = TowerId::try_from(v).map_err(|x| anyhow!(x))?; | ||
| let params = GetRegistrationReceiptParams::try_from(v).map_err(|x| anyhow!(x))?; | ||
| let tower_id = params.tower_id; | ||
| let subscription_start = params.subscription_start; | ||
| let subscription_expiry = params.subscription_expiry; | ||
| let state = plugin.state().lock().unwrap(); | ||
|
|
||
| if let Some(response) = state.get_registration_receipt(tower_id) { | ||
| Ok(json!(response)) | ||
| let response = | ||
| state.get_registration_receipt(tower_id, subscription_start, subscription_expiry); | ||
| if response.clone().unwrap().is_empty() { | ||
| if state.towers.contains_key(&tower_id) { | ||
| Err(anyhow!( | ||
| "No registration receipt found for {tower_id} on the given range" | ||
| )) | ||
| } else { | ||
| Err(anyhow!( | ||
| "Cannot find {tower_id} within the known towers. Have you registered?" | ||
| )) | ||
| } | ||
|
Comment on lines
+147
to
+156
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't really grasp this part. why is there an I guess Otherwise, we don't need the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, that is actually what i think I was suggesting for, however, checking
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd be good to test this out by creating a test that queries a tower that is not part of the DB and see whether the method is returning an empty vec or None |
||
| } else { | ||
| Err(anyhow!( | ||
| "Cannot find {tower_id} within the known towers. Have you registered?" | ||
| )) | ||
| Ok(json!(response)) | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -179,9 +179,20 @@ impl WTClient { | |
| Ok(()) | ||
| } | ||
|
|
||
| /// Gets the latest registration receipt of a given tower. | ||
| pub fn get_registration_receipt(&self, tower_id: TowerId) -> Option<RegistrationReceipt> { | ||
| self.dbm.load_registration_receipt(tower_id, self.user_id) | ||
| /// Gets the registration receipt(s) of a given tower in the given range. | ||
| /// If no range is given then gets the latest registration receipt | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. end this docstring with a period. |
||
| pub fn get_registration_receipt( | ||
| &self, | ||
| tower_id: TowerId, | ||
| subscription_start: Option<u32>, | ||
| subscription_expiry: Option<u32>, | ||
| ) -> Option<Vec<RegistrationReceipt>> { | ||
| self.dbm.load_registration_receipt( | ||
| tower_id, | ||
| self.user_id, | ||
| subscription_start, | ||
| subscription_expiry, | ||
| ) | ||
| } | ||
|
|
||
| /// Loads a tower record from the database. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure why
rust fmtis not properly formatting this, but here you have a proper formatted version:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for sharing the formatted code. Both
rustfmtandcargo fmtare not formatting this code. Did it work for you or you had to format manually ?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately I had to do it manually, I don't know why
rust fmtseems not to properly handle big files with multiple levels of identation :(