Skip to content
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

Refactor function signatures where appropriate #481

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

benalleng
Copy link

Many of these function signatures were audited in #405 and implemented here.

I am going to leave any new function signatures outside of the ones here to a new PR so I don't bloat this PR anymore than it already is.

Many of these function signaures were audited in payjoin#405 and implemented
here.
@@ -88,6 +88,8 @@ impl AppTrait for App {
.send()
.await
.with_context(|| "HTTP request failed")?;
let response_str = response.text().await.with_context(|| "Failed to read seponse")?;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this seem like an ok way to turn this into a string?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think so.

previously the response body was copied into a Vec<u8>, and then given as a io::Read. .text(), otoh, treats it as a utf8 string, not all &[u8]-ish things can be made into a String and subsequently a &str, but since this is v1 that should be OK, the data should be base64 followed by query param encoded data, all of which should be ASCII.

however, it seems a bit more defensive, and consistent with v2 to think of all responses as &[u8], so that the payjoin crate is responsible for handling that error, if we change the signature to take a &str, then payjoin-cli and other wallets would be responsible for enforcing this part of BIP78's rules which they should be rather agnostic about, similarly to how the payjoin crate should be agnostic to transport errors etc (and by the same token should not accept a response object as its argument).

nit:

Suggested change
let response_str = response.text().await.with_context(|| "Failed to read seponse")?;
let response_str = response.text().await.with_context(|| "Failed to read reponse")?;

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems fine, though the extract context is probably unnecessary. I might also suggest doing it inline like it was before:

let psbt = ctx.process_response(&response.text().await?).map_err(|e| {
...

@benalleng benalleng changed the title refactor: refactor function signatures Refactor function signatures where appropriate Jan 14, 2025
Copy link
Collaborator

@nothingmuch nothingmuch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partial review, I'm not sure how strongly I feel about &[u8] in place of &str, I think I could be convinced that that's just bikeshedding but I think the separation of responsibilities arguments is more than just bikeshedding.

I would very much appreciate it if the commits were split up, some of it is trivial refactoring, other parts are a bit more semantic, it all looks good to me but I found it a little noisy to read the large diff as small changes touch many different files, and reviewing commit by commit would be easier, we can always squash later with no effort if that feels too noisy (but I'm personally partial to small commits):

  1. validate_input_utxos remove treat_missing_as_error param
  2. process_response, &mut impl std::io::Read -> &[u8]
  3. extract_v2, Url -> &Url
  4. new_v1 (Url, Vec<u8>) -> (&Url, &[u8])
  5. making ohttp_decapsulate pub(crate) (seems appropriate but not sure i understand the rationale, if it's just because that's possible then cACK)
  6. decrypt_message_b ...

some of these are really trivial but touch many files

some of these are a bit more subtle

it would make sense IMO to have one commit per changed fn, and maybe even separate v1 and v2 code into separate pull requests?

@@ -88,6 +88,8 @@ impl AppTrait for App {
.send()
.await
.with_context(|| "HTTP request failed")?;
let response_str = response.text().await.with_context(|| "Failed to read seponse")?;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think so.

previously the response body was copied into a Vec<u8>, and then given as a io::Read. .text(), otoh, treats it as a utf8 string, not all &[u8]-ish things can be made into a String and subsequently a &str, but since this is v1 that should be OK, the data should be base64 followed by query param encoded data, all of which should be ASCII.

however, it seems a bit more defensive, and consistent with v2 to think of all responses as &[u8], so that the payjoin crate is responsible for handling that error, if we change the signature to take a &str, then payjoin-cli and other wallets would be responsible for enforcing this part of BIP78's rules which they should be rather agnostic about, similarly to how the payjoin crate should be agnostic to transport errors etc (and by the same token should not accept a response object as its argument).

nit:

Suggested change
let response_str = response.text().await.with_context(|| "Failed to read seponse")?;
let response_str = response.text().await.with_context(|| "Failed to read reponse")?;

@@ -229,15 +229,15 @@ impl Sender {
/// Extract serialized V1 Request and Context from a Payjoin Proposal
pub fn extract_v1(&self) -> Result<(Request, V1Context), url::ParseError> {
let url = serialize_url(
self.endpoint.clone(),
&self.endpoint.clone(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this .clone() is no longer necessary since we're passing a reference now.

let mut res_str = String::new();
response.read_to_string(&mut res_str).map_err(InternalValidationError::Io)?;
let proposal = Psbt::from_str(&res_str).map_err(|_| ResponseError::parse(&res_str))?;
pub fn process_response(self, response: &str) -> Result<Psbt, ResponseError> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The note in #405 says response could be a &[u8], but I agree using &str seems more straightforward.

@DanGould is there a reason we should use bytes instead of a string?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems @nothingmuch addressed this same question in his review and I missed it when writing this.

@@ -88,6 +88,8 @@ impl AppTrait for App {
.send()
.await
.with_context(|| "HTTP request failed")?;
let response_str = response.text().await.with_context(|| "Failed to read seponse")?;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems fine, though the extract context is probably unnecessary. I might also suggest doing it inline like it was before:

let psbt = ctx.process_response(&response.text().await?).map_err(|e| {
...

@nothingmuch
Copy link
Collaborator

Re &[u8] vs impl std::io::Read, BIP 78 does not restrict the size of the response, but specifies that content length must be included. the v1 receiver enforces a reasonable limit on this:

if content_length > 4_000_000 * 4 / 3 {
return Err(InternalRequestError::ContentLengthTooLarge(content_length).into());
}

however, a similar limit was not included in payjoin-cli prior to this PR, response.bytes().to_vec() could potentially make unbounded allocations given a large enough response body:

https://github.com/payjoin/rust-payjoin/pull/481/files#diff-40ff0a2747a0024cf54e5c3831bdd0a9e5ae32e714ce629826e5d8455ed0d50bL99

or in the sender:

response.read_to_string(&mut res_str).map_err(InternalValidationError::Io)?;

I opened #483, but since this PR changes the process_response function signature, maybe it'd be more convenient to address the payjoin-cli part of this in this PR at your discretion

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants