-
Notifications
You must be signed in to change notification settings - Fork 64
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
Add optional boundaries to getregistrationreceipt #199
base: master
Are you sure you want to change the base?
Conversation
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.
First pass. Check comments inline.
Thanks for taking care of this, I love the proactivity.
Notice that the command does not work as intended at the moment if you pass more than one parameter to it. e.g:
lightning-cli getregistrationreceipt 03488e12bc580caaaa4f575bde2b4754746382fc5e8b5e3d03a632b0f15c5175cc 0 1
"Unexpected json format. Expected a single parameter. Received: 3"
This is due to the TowerId
parsing I mentioned in one of the comments.
Notice, also, how you have only patched the test to cover the old case, but there are no tests for the new cases (hence why you didn't catch the errors on the updated function). This requires tests for both the old and the new cases, so we can make sure that both the latest receipt can be pulled if no boundary is specified, but also that every receipt within a boundary is returned.
Also, for the RPC specific changes, I'll suggest you manually try all the possible inputs (there are only two options, either start and end blocks are specified or not).
watchtower-plugin/src/constants.rs
Outdated
pub const DEFAULT_SUBSCRIPTION_START: Option<i64> = None; | ||
pub const SUBSCRIPTION_START: &str = "subscription-start"; | ||
pub const SUBSCRIPTION_START_DESC: &str = "subscription-start time"; | ||
pub const DEFAULT_SUBSCRIPTION_EXPIRY: Option<i64> = None; | ||
pub const SUBSCRIPTION_EXPIRY: &str = "subscription-expiry"; | ||
pub const SUBSCRIPTION_EXPIRY_DESC: &str = "subscription-expiry time"; |
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.
None of this is required. There is no such thing as a default subscription start or expiry. They depend on the block height at which the user requested the subscription.
watchtower-plugin/src/dbm.rs
Outdated
let mut query = "SELECT available_slots, subscription_start, subscription_expiry, signature FROM registration_receipts WHERE tower_id = ?1".to_string(); | ||
|
||
let mut params = vec![tower_id.to_vec()]; | ||
|
||
if let Some(start) = subscription_start { | ||
query.push_str(" AND subscription_start >= ?2"); | ||
params.push(start.to_be_bytes().to_vec()); | ||
} | ||
|
||
if let Some(expiry) = subscription_expiry { | ||
query.push_str(" AND subscription_expiry <= ?3"); | ||
params.push(expiry.to_be_bytes().to_vec()); | ||
} | ||
|
||
query.push_str(" ORDER BY subscription_expiry DESC LIMIT 1"); | ||
|
||
let mut stmt = self.connection.prepare(&query).unwrap(); | ||
let params: Vec<&dyn ToSql> = params.iter().map(|v| v as &dyn ToSql).collect(); |
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.
Notice that you've changed the behavior of this method.
If the bounds are not passed, the method should work exactly as it was before. Otherwise, it should have this new functionality.
Also, why are you limiting the result? This can potentially return multiple items.
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.
Got it, I will keep exactly old method in case of no optional parameters.
Actually I thought since old function was returning only latest receipt, new one should do same. I will remove the limit.
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.
There's no need for two methods tho, just build the SQL query based on what the input params are.
watchtower-plugin/src/dbm.rs
Outdated
let mut query = "SELECT available_slots, subscription_start, subscription_expiry, signature FROM registration_receipts WHERE tower_id = ?1".to_string(); | ||
|
||
let mut params = vec![tower_id.to_vec()]; | ||
|
||
if let Some(start) = subscription_start { | ||
query.push_str(" AND subscription_start >= ?2"); | ||
params.push(start.to_be_bytes().to_vec()); | ||
} | ||
|
||
if let Some(expiry) = subscription_expiry { | ||
query.push_str(" AND subscription_expiry <= ?3"); | ||
params.push(expiry.to_be_bytes().to_vec()); | ||
} | ||
|
||
query.push_str(" ORDER BY subscription_expiry DESC LIMIT 1"); | ||
|
||
let mut stmt = self.connection.prepare(&query).unwrap(); | ||
let params: Vec<&dyn ToSql> = params.iter().map(|v| v as &dyn ToSql).collect(); |
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.
Check the params
macro, I don't think we need to make it this complicated
watchtower-plugin/src/main.rs
Outdated
let tower_id = TowerId::try_from(v.clone()).map_err(|x| anyhow!(x))?; | ||
let subscription_start = v["subscription-start"].as_u64().map(|v| v as u32); | ||
let subscription_expiry = v["subscription-expiry"].as_u64().map(|v| v as u32); | ||
|
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.
This is not right. Given we are now potentially passing multiple params to the function, we cannot crate the TowerId
as before, given v
now can contain more data.
What is passed the the function is a json of the input params. Check in convert.rs
how other commands handle this (you'll need to create a GetRegistrationReceiptParams
struct and implement the parsing)
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.
Oh my bad, will fix this.
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.
I have implemented RegistrationReceiptParams
struct, but it seems when optional arguments are passed, the query fails, I haven't been able to figure out exact issue in code, code seems fine to me. Can you pls have a look
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.
Check comments inline.
You have still not provided tests where the boundaries are checked, only cases where both are None
.
Regarding why this is not properly returning when you call it with the boundaries, the reason is that you are passing start
and expiry
as be_bytes
, while they are defined in the database as numbers. You need to pas them as such.
watchtower-plugin/src/constants.rs
Outdated
@@ -3,7 +3,6 @@ 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 | |||
|
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.
This line break is not needed
watchtower-plugin/src/convert.rs
Outdated
"Unexpected request format. The request needs 2 parameter. Received: {param_count}" | ||
))) |
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.
Same here, there's not actual change, this does not need reformating
watchtower-plugin/src/dbm.rs
Outdated
query.push_str(" AND subscription_expiry = (SELECT MAX(subscription_expiry) FROM registration_receipts WHERE tower_id = ?1)"); | ||
} | ||
|
||
if let Some(expiry) = subscription_expiry { |
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.
Both start and expiry need to be passed for the query to be valid. This is checking if they are independently passed (one OR the other, but not one AND the other).
You can either check this here and return an error, or in the main function and not call this method if the constraint is not met.
watchtower-plugin/src/convert.rs
Outdated
} else { | ||
None | ||
}; | ||
let subscription_expiry = if let Some(expire) = a.get(2).and_then(|v| v.as_i64()) { |
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.
As mentioned in the database method, this should only accept ether none or both sides of the boundary. I think this should be the right place to make that check, and fail to parse if both are not provided. That is, the parsing accepts either 1, or 3 params.
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.
I have made all the suggested changed, new function seems to be working perfectly in my local device.
Though I am getting output mismatch error for subscription_expiry
in test_load_registration_receipt
even though the function queries for exact same params used to store receipt in db.
error log:
thread 'dbm::tests::test_load_registration_receipt' panicked at 'assertion failed: `(left == right)`
left: `RegistrationReceipt { user_id: UserId(PublicKey(7a7fd8cca7fd5d5e4584f7c6e1453ab67a7b834601c0bb04d9442dba9edee9f860947791f39d7033
d6a5de0e1b221472c60f82abc6585b959ddb399e17383844)), available_slots: 1414565319, subscription_start: 741713076, subscription_expiry: 7417
13496, signature: Some("dhrb3euw99g7gfrhf68ifwsnknro5ksbhs5zh4tfyapqbx5yb3d7rj4hih14fb8in6mmmgihng876pq1dy17t9jfcc1txaq7kycufdet") }`,
right: `RegistrationReceipt { user_id: UserId(PublicKey(7a7fd8cca7fd5d5e4584f7c6e1453ab67a7b834601c0bb04d9442dba9edee9f860947791f39d7033
d6a5de0e1b221472c60f82abc6585b959ddb399e17383844)), available_slots: 1414565521, subscription_start: 741713076, subscription_expiry: 7417
13626, signature: Some("ryxxzerta61taatusksbcgpfkpdsaa7xb8ybsw9pwuath5nseqigndqp4ikhi6gemuaaaiza9ar7ufz8oi9p3k861xb1g69a76ndy6ky") }`', w
atchtower-plugin/src/dbm.rs:752:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
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.
This looks better now. There are still some things to have in mind though:
Notice that, given now we are working with a boundary, it could be the case that multiple registration receipts are a match for a certain boundary. That should be taken into account. This means that the dbm method will need to return a vector instead of a single item.
Also, the naming of the methods may need to be updated to make sense.
Regarding tests, you have updated them to cover the case where a boundary is passed, but now the case where it is not passed is not covered anymore. Think about what are all possible combinations of inputs and do test them all. Also test when multiple receipts are returned by the method
watchtower-plugin/src/convert.rs
Outdated
@@ -200,7 +200,7 @@ impl TryFrom<serde_json::Value> for GetAppointmentParams { | |||
if param_count != 2 { | |||
Err(GetAppointmentError::InvalidFormat(format!( | |||
"Unexpected request format. The request needs 2 parameter. Received: {param_count}" | |||
))) |
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.
This is not needed.
Please do check unnecessary formatting changes before pushing.
watchtower-plugin/src/convert.rs
Outdated
let subscription_start = if let Some(start) = a.get(1).and_then(|v| v.as_i64()) { | ||
if start >= 0 { | ||
Some(start as u32) | ||
} else { | ||
return Err(GetRegistrationReceiptError::InvalidFormat( | ||
"Subscription-start must be a positive integer".to_owned(), | ||
)); | ||
} | ||
} else { | ||
None | ||
}; | ||
let subscription_expiry = if let Some(expire) = a.get(2).and_then(|v| v.as_i64()) { | ||
if expire > subscription_start.unwrap() as i64 { | ||
Some(expire as u32) | ||
} else { | ||
return Err(GetRegistrationReceiptError::InvalidFormat( | ||
"Subscription-expire must be a positive integer and greater than subscription_start".to_owned(), | ||
)); | ||
} | ||
} else { | ||
None | ||
}; | ||
if subscription_start.is_some() != subscription_expiry.is_some() { | ||
return Err(GetRegistrationReceiptError::InvalidFormat( | ||
"Subscription-start and subscription-expiry must be provided together".to_owned(), | ||
)); |
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.
You can merge all this into a single if let
statement.
At this point you have already made sure that the provided number of params is either one or three, so there is no way the user is providing the subscription_start
but not subscription_end
.
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.
Also, we are referencing subscription_start
and subscription_expiry
in snake case, but you are logging them as Subscription-{start, end}
params.push(v); | ||
} | ||
} | ||
GetRegistrationReceiptParams::try_from(json!(params)) |
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.
nit: add a line break here
GetRegistrationReceiptParams::try_from(json!(params)) | ||
} | ||
}, | ||
_ => Err(GetRegistrationReceiptError::InvalidFormat(format!( |
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.
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}'
watchtower-plugin/src/convert.rs
Outdated
match value { | ||
serde_json::Value::Array(a) => { | ||
let param_count = a.len(); | ||
if param_count != 1 && param_count != 3 { |
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.
If you want to emphasize the boundary not being fully passed (given this may be a common error), you can have a special case here for two params before checking one or three were you error that if a boundary is set, both ends of the boundary need to be specified. e.g:
if parame_count == 2 {...}
else if param_count != 1 && param_count != 3 {...}
else {...}
watchtower-plugin/src/dbm.rs
Outdated
WHERE tower_id = ?1)", | ||
) | ||
.unwrap(); | ||
let mut query = "SELECT available_slots, subscription_start, subscription_expiry, signature FROM registration_receipts WHERE tower_id = ?1 AND (subscription_start >=?2 OR ?2 is NULL) AND (subscription_expiry <=?3 OR ?3 is NULL)".to_string(); |
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.
This feels pretty hacky, especially when there is no boundary, well be passing (AND NULL is NULL) AND (NULL is NULL)
which will be valid, but feels pretty unnecessary.
It'll be best to find a way to define the query and params list depending on whether subscription_start
and subscription_expiry
were passed
watchtower-plugin/src/main.rs
Outdated
if let Some(response) = state.get_registration_receipt(tower_id) { | ||
if let Some(response) = | ||
state.get_registration_receipt(tower_id, subscription_start, subscription_expiry) | ||
{ | ||
Ok(json!(response)) | ||
} else { | ||
Err(anyhow!( |
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.
Notice this is not the right error for every case now. It used to be the case because if no receipt could be pulled from the database it meant the tower was unknown. Now, if params have been passed, it could be the case that you could not find a receipt for the specified boundary. This needs to be covered. I'd suggest you check whether the tower can be found in memory if the query errors, and the return the proper error depending on that outcome.
Hey @sr-gi, |
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.
I think this may be working now.
A few extra comments. I'll need to give a final pass to this once everything is covered.
All commits need to be squashed.
watchtower-plugin/src/convert.rs
Outdated
Err(GetRegistrationReceiptError::InvalidFormat(format!( | ||
"Unexpected request format. The request needs 1 or 3 parameter. Received: {param_count}" | ||
))) | ||
} else{ |
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.
A space is missing after {
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 fmt
is not properly formatting this, but here you have a proper formatted version:
diff --git a/watchtower-plugin/src/convert.rs b/watchtower-plugin/src/convert.rs
index d49889c..b9475de 100644
--- a/watchtower-plugin/src/convert.rs
+++ b/watchtower-plugin/src/convert.rs
@@ -289,20 +289,22 @@ impl TryFrom<serde_json::Value> for GetRegistrationReceiptParams {
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()))
+ 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{
+ } 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())
+ GetRegistrationReceiptError::InvalidId("Invalid tower id".to_owned())
})
} else {
Err(GetRegistrationReceiptError::InvalidId(
- "tower_id must be a hex encoded string".to_owned(),
+ "tower_id must be a hex encoded string".to_owned(),
))
}?;
@@ -311,22 +313,24 @@ impl TryFrom<serde_json::Value> for GetRegistrationReceiptParams {
(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(),
- ));
+ "subscription_start must be a positive integer and subscription_expire must be a positive integer greater than subscription_start".to_owned(),
+ ));
}
} else if a.get(1).is_some() || a.get(2).is_some() {
return Err(GetRegistrationReceiptError::InvalidFormat(
- "Subscription_start and subscription_expiry must be provided together as positive integers".to_owned(),
- ));
+ "subscription_start and subscription_expiry must be provided together as positive integers".to_owned(),
+ ));
} else {
(None, None)
};
- Ok(Self {
- tower_id,
- subscription_start,
- subscription_expiry,
- })
+ Ok(
+ Self {
+ tower_id,
+ subscription_start,
+ subscription_expiry,
+ }
+ )
}
},
serde_json::Value::Object(mut m) => {
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 rustfmt
and cargo fmt
are 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 fmt
seems not to properly handle big files with multiple levels of identation :(
watchtower-plugin/src/convert.rs
Outdated
"Subscription_start must be a positive integer and subscription_expire must be a positive integer greater than subscription_start".to_owned(), | ||
)); | ||
} | ||
} else if a.get(1).is_some() || a.get(2).is_some() { |
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.
I don't think this branch is really necessary, is it?
We've already made sure that both the start and the end of the boundary have been provided, or none of them have. At this point is either the boundary is correct (start>=0 and end>start) or it is not, so it cannot be the case that either of them but not both are Some(x)
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.
Actually the method was also allowing text(alphabetic letters) as subscription_start
and subscription_expiry
. I added this branch to handle this case. Should I remove this ?
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.
Oh, right, that's because elements are passed as json Values
and they can be anything really. I think this should be re-arranged then for clarity, since I didn't really get what was the point of having that else.
Can you make it so you capture the values in your if let
as you are atm, but instead of convert them straight away you leave them as Value
s, and then you try to convert them to i64
or fail with a similar error to this one inside that same context?
e.g.:
let (subscription_start, subscription_expiry) = if let (Some(start), Some(expire)) = (a.get(1)), a.get(2)) {
/// Try to convert them to i64, fail if they are not convertable
}
watchtower-plugin/src/dbm.rs
Outdated
.unwrap(); | ||
subscription_start: Option<u32>, | ||
subscription_expiry: Option<u32>, | ||
) -> Vec<RegistrationReceipt> { |
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.
I think we should keep this an Option
, so its either None
or Some(Vec<...>)
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.
The thing is, after reworking this function to return a vector, it should never return None
. It will return an empty vector if 1- the tower wasn't found & 2- the given range doesn't intersect with any subscription.
watchtower-plugin/src/dbm.rs
Outdated
} | ||
let mut stmt = self.connection.prepare(&query).unwrap(); | ||
|
||
let receipts = stmt |
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.
You don't need to collect this
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.
hey @sr-gi can you pls eloberate a little ? I am not seeing any collect here and I believe that below(at line 248) collect is required. Also I have changed that code to the code recommended by Omer now.
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.
By collecting I mean storing it in a temp variable. You're doing:
let receipts = stmt.query_map...
receipts
But you can simply return the statement call:
stmt.query_map...
watchtower-plugin/src/dbm.rs
Outdated
)) | ||
}) | ||
.unwrap() | ||
.collect::<Result<Vec<_>, _>>() |
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.
I think we can simplify this to Result<_, _>
. I'm not really sure why the annotation is needed tbh.
Any ideas @mariocynicys?
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.
I would have written it like this:
let receipts = 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,
))
})
.unwrap() // MappedRows<|&Row| -> Result<RegistrationReceipt, E>>
.map(|r| r.unwrap())
.collect();
It's pretty interesting though how the type after the .unwrap()
(MappedRows<|&Row| -> Result<RegistrationReceipt, E>>
which is equivalent to a Vec<Result<RegistrationReceipt, E>>
when collected) could be collected into Result<Vec<RegistrationReceipt>, E>
.
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.
I think we can simplify this to Result<_, _>. I'm not really sure why the annotation is needed tbh.
I missed the point here 😂, yeah Result<_, _>
is valid.
I would say .map(|res| res.unwrap()).collect();
feels more intuitive/less-hacky though.
There's something I just realized while manually testing this. IIRC if two subscriptions overlapped, the user was only supposed to be storing the latest receipt. However, I just tested this and two receipts were returned where Does this ring a bell @mariocynicys? No worries @ShubhamBhut, this is not due to your PR, but we may have forgotten to remove some data in an older PR. |
Revising this, we made the merger on the tower side and not the client side. |
Referenced this here #208 |
I am having some issues with squashing, I will submit squashed PR after resolving them. I am submitting code for review in case my squashing issues remain longer. |
6046848
to
ed1be9e
Compare
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.
Some small changes. Looks good otherwise.
Remember to squash the commits.
watchtower-plugin/src/dbm.rs
Outdated
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, | ||
)) | ||
}) | ||
.unwrap() | ||
.map(|r| r.unwrap()) | ||
.collect(), | ||
) |
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.
You can simplify this:
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, | |
)) | |
}) | |
.unwrap() | |
.map(|r| r.unwrap()) | |
.collect(), | |
) | |
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, | |
)) | |
}) | |
.unwrap() | |
.map(|r| r.ok()) | |
.collect() |
watchtower-plugin/src/wt_client.rs
Outdated
@@ -180,8 +180,18 @@ impl WTClient { | |||
} | |||
|
|||
/// Gets the latest registration receipt of a given tower. |
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.
This is not true anymore
watchtower-plugin/src/main.rs
Outdated
Err(anyhow!("No registration receipt found for {tower_id}")) | ||
} else { | ||
Err(anyhow!( | ||
"Cannot find {tower_id} within the known towers. Have you registered ?" |
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.
Remove extra space before "?"
watchtower-plugin/src/main.rs
Outdated
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}")) |
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.
Given this implies that the tower is found, an it cannot be the case that a tower is found and a registration receipt is not, it means it can only be hit if a boundary is passed and there is no receipt for that boundary. Therefore:
Err(anyhow!("No registration receipt found for {tower_id}")) | |
Err(anyhow!("No registration receipt found for {tower_id} on the given range")) |
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.
ACK fc794fb
@mariocynicys care to give this a look when you have some time? |
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!
Some nits and this is nearly finished, but there are some stuff I didn't grasp.
)) | ||
}?; | ||
|
||
let (subscription_start, subscription_expiry) = if let (Some(start), Some(expire)) = (a.get(1), a.get(2)){ |
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.
Insert a space after (a.get(1), a.get(2))
before {
.
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.
Has this been fixed?
) | ||
})?; | ||
|
||
let expire = expire.as_i64().ok_or_else(|| { |
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.
nit: can we name this variable expiry
?
let start = start.as_i64().ok_or_else(|| { | ||
GetRegistrationReceiptError::InvalidFormat( | ||
"Subscription_start must be a positive integer".to_owned(), | ||
) | ||
})?; |
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.
If we want it a positive integer, shouldn't we use as_u64
?
(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(), |
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.
Can't they be the same value? If, for instance, I know what specific block I registered in.
|
||
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]){ |
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.
missing a space here as well, before the {
.
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)") |
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.
please add a ;
at the end of this line.
} else { | ||
query.push_str(" AND subscription_start>=?2 AND subscription_expiry <=?3"); | ||
params.push(&subscription_start); | ||
params.push(&subscription_expiry) |
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.
and this one as well.
if subscription_expiry.is_none() { | ||
query.push_str(" AND subscription_expiry = (SELECT MAX(subscription_expiry) FROM registration_receipts WHERE tower_id = ?1)") | ||
} else { | ||
query.push_str(" AND subscription_start>=?2 AND subscription_expiry <=?3"); |
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.
please fix the spacing around the >=
& <=
(keep one space on each side).
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?" | ||
)) | ||
} |
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.
I don't really grasp this part. why is there an .unwrap()
?!
response
is an optional vector, so Option::None
& an empty vector signal different meanings? right?
I guess Option::None
is when the tower isn't found and an empty vector is when the registration range doesn't contain any receipts?
If so, then the .clone().unwrap()
part will panic with no useful information to the user.
Otherwise, we don't need the None
variant anymore.
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.
Yeah, that is actually what i think I was suggesting for, however, checking DBM::load_registration_receipt
I don't think we ever produce a None
return.
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.
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
.unwrap() | ||
.map(|r| r.ok()) | ||
.collect() |
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.
I can't really get my head around how collect()
produces Option<Vec<
here 😕,
but aside from that, I see that the failure that's being accounted for in map(|r| r.ok())
is for the de-serialization of the data from the DB (slots
, start
, ...) which we assume shouldn't fail anyways.
I think we can unwrap
while de-serializing directly since this shouldn't actually fail.
and substitute map(|r| r.ok())
with map(|r| r.unwrap())
. This will make this method return a Vec<
instead.
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.
I think this was previous approach, but then sr-gi suggested to keep it Option<Vec<
ref
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.
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 main.rs
by checking if the tower was part of the state. So this may indeed be simplified.
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.
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.
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 Some(a_possibly_zero_len_vec)
.
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.
Yeah, that is my point, that the suggestion was for that, but we really don't do that here, but always return Some(...)
and check afterward.
I'm happy with either one or the other.
#[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}"), | ||
} | ||
} | ||
} |
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.
There is no distinction in usage between these two different error variants. I think we can combine them into one.
We might also be able to get rid of them and use an Err
instead.
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.
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.
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.
Yeah I noticed other param parsers are doing the same. I think we can make all of them to use Err(String)||Err(&'static str)
.
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.
Try to remove it entirely, then we can do the same for other param errors in a follow up if it made sense.
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.
Err
seems to be working fine, just had to tweak the code where parsing was done. I think I would wait for decision on issue of Vec or Option<vec before final submission, rest changes are done.
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.
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 GetRegistrationReceipt
now
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.
oh, got it. I will add tests for GetRegistrationReceipt
params there. And in case of #199 (comment), I think it would be better as Some(Vec<>) just because its common way in rust-teos code.
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.
And in case of #199 (comment), I think it would be better as Some(Vec<>) just because its common way in rust-teos code.
This would only make sense if #199 (comment) is carried out. I am fine with both ways, but the None
variant has to carry a meaning and not just be there so the interface look similar to other methods.
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.
Agreed
fixes #97
@mariocynicys @sr-gi can you pls have a look at this ? I am actually pretty new to rust.