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

Change the signature of consensus_encode to return io::Error's #494

Merged
merged 1 commit into from
Jan 12, 2021

Conversation

stevenroose
Copy link
Collaborator

This is instead of encode::Errors because the encoders should
not be allowed to return errors that don't originate in the writer
they are writing into.

This is a part of the method definition that has been relied upon for a
while already.

@apoelstra apoelstra added the API break This PR requires a version bump for the next release label Oct 9, 2020
@apoelstra
Copy link
Member

Nice!

I think this should get its own version bump, separate from other changes, because it requires some invasive but mechanical changes for downstream users.

Copy link
Collaborator

@dr-orlovsky dr-orlovsky left a comment

Choose a reason for hiding this comment

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

Pls see one command with consideration of doing FromStr impl as well, otherwise utACK

///
/// Returns an empty error if and only if the string is
/// larger than 12 characters in length.
pub fn from_str<S: Into<Cow<'static, str>>>(s: S) -> Result<CommandString, ()> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Some methods rely on FromStr implementation to do a conversion; for instance if use clap for parsing commands from command-line parameters (which may be a case for cli tools operating with bitcoin P2P network). So I propose also to add impl FromStr for CommandString and from it call this method directly

@stevenroose
Copy link
Collaborator Author

Nice!

I think this should get its own version bump, separate from other changes, because it requires some invasive but mechanical changes for downstream users.

Can it not just go with the next breaking version bump? I mean it doesn't really matter if it has it's own bump, right? At some point people will have to do it if they want to keep updating.

}
}

#[deprecated(since = "0.26.0", note = "use CommandString::from")]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Trying to understand why this and above are depricated. The message will be really surprising in practice: instead of something.from() users are asked to use something::<CommandString>.from(), but even that is not clear from the message... Because in most cases method is called on the object, without additional qualifiers, and it will be up to the compiler to decide wich implementation to call.

It seams reasonable just to remove this methods, and nothing bad will happen.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah I removed it. I pushed a new commit cleaning up the CommandString API.

Comment on lines 82 to 83
if f.len() > 12 {
CommandString(f[0..12].into())
Copy link
Member

Choose a reason for hiding this comment

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

I don't like this, I'd rather remove the From impl or panic here then do unexpected things

Copy link
Member

Choose a reason for hiding this comment

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

oh I see you deprecated it, hmm idk what's the right thing to do
also, deprecating a trait impl doesn't do anything UX wise:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c67bc53c2372c636208adbfab79fa02e
rust-lang/rust#39935

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Hmm, that seems to be a cargo bug. I would prefer not to panic, so perhaps just removing the method is the best option.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

K I pushed a new commit that cleans up the CommandString API. I thin it's such a infrequently used type that all the fancy construction methods are not needed. So I removed all of them.

Comment on lines 69 to 70
// Don't use CommandString::from to avoid allocating when
// it's not needed.
Copy link
Member

Choose a reason for hiding this comment

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

You're saying don't call CommandString::from to avoid allocating but you then call to_owned() which allocates?

Copy link
Collaborator Author

@stevenroose stevenroose Oct 25, 2020

Choose a reason for hiding this comment

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

Hmm, that might have been related to an earlier version of the impl or wrongly formulated.. I don't remember. I think we can't use CommandString::from because &'a str doesn't implement Into<Cow<'static, str>>, but I'm not sure, let me try.

Ah k so

72 |             Ok(CommandString(s.into()))
   |                              ^^^^^^^^
   = note: expected  `std::borrow::Cow<'static, _>`
              found  `std::borrow::Cow<'_, _>`

I was right, and I don't know what to do here :| I'd be in favor of dropping FromStr as well for this type and just have the basic constructor which has Into<Cow<..>>.

@stevenroose
Copy link
Collaborator Author

Rebased and added commit to clean up the CommandString API.

@Kixunil
Copy link
Collaborator

Kixunil commented Oct 25, 2020

Just noticed this randomly and I have to say I'm not a huge fan of io::Error. I wrote my reasons on reddit recently.

I don't know the exact context here, so feel free to ignore me if it's unrelated.

@stevenroose
Copy link
Collaborator Author

Just noticed this randomly and I have to say I'm not a huge fan of io::Error. I wrote my reasons on reddit recently.

Yeah I don't think that's quite related. Like currently it's about the difference between returning an io::Error or an encoding::Error::Io(io::Error). The latter case is confusing because encode::Error has other variants that the method should never return.

Also, this PR is really neutral about io::Error itself. It just happens to be the error that io::Write and io::Read return. Which are the traits used in the encode::Encodable and encode::Decodable traits.

src/network/message.rs Outdated Show resolved Hide resolved
///
/// Returns an empty error if and only if the string is
/// larger than 12 characters in length.
pub fn from<S: Into<Cow<'static, str>>>(s: S) -> Result<CommandString, ()> {
Copy link
Member

Choose a reason for hiding this comment

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

nit, Calling the function from outside the From trait is somewhat weird

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah I couldn't think of a better name. I thought of create but it's quite conventional to use from or from_xxx for converter methods. I also considered just new. Open to opinions, though, don't feel strongly about this. I just thought from was good enough.

Copy link
Contributor

Choose a reason for hiding this comment

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

Open to opinions

Personally, new would probably be the first thing I would look for when trying to construct one of these.

Another thing to consider might be to name it try_from given that you are returning a Result.

Copy link
Collaborator

Choose a reason for hiding this comment

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

According to rust API guidelines we should use with_str as a function name: https://rust-lang.github.io/api-guidelines/naming.html#casing-conforms-to-rfc-430-c-case

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not sure, the guildlines say with_more_details, however in this case intput is not "more details", it's literally the source of informtion. My understanding is that with_more_details is used for special cases (with_capacity is an obvious one)

The function header also kinda leaks internal representation but I guess it's fine?
Implementing TryFrom would be best, but AFAIR it's not in supported MSRV.

@stevenroose
Copy link
Collaborator Author

Rebased.

@stevenroose stevenroose force-pushed the encode-io-err branch 2 times, most recently from 29ab439 to 0b54429 Compare November 1, 2020 12:46
elichai
elichai previously approved these changes Nov 1, 2020
Copy link
Member

@elichai elichai left a comment

Choose a reason for hiding this comment

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

ACK

@apoelstra
Copy link
Member

Nice!
I think this should get its own version bump, separate from other changes, because it requires some invasive but mechanical changes for downstream users.

Can it not just go with the next breaking version bump? I mean it doesn't really matter if it has it's own bump, right? At some point people will have to do it if they want to keep updating.

Yes, but if we do it in the same bump as a bunch of other stuff, then they have to do these changes in a single commit alongside a bunch of other stuff.

Copy link
Collaborator

@dr-orlovsky dr-orlovsky left a comment

Choose a reason for hiding this comment

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

Seems like there a few things potentially reducing stability of the software that I missed in my previous review

///
/// Returns an empty error if and only if the string is
/// larger than 12 characters in length.
pub fn from<S: Into<Cow<'static, str>>>(s: S) -> Result<CommandString, ()> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

According to rust API guidelines we should use with_str as a function name: https://rust-lang.github.io/api-guidelines/naming.html#casing-conforms-to-rfc-430-c-case

Comment on lines 72 to 74
if strbytes.len() > 12 {
return Err(encode::Error::UnrecognizedNetworkCommand(self.0.clone().into_owned()));
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

This change will lead to a panic if the length of the CommandString is larger than 12. Why not to use ENAMETOOLONG io::Error equivalent?

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think so. The PR removes the uncheck instantiation path for CommandString through From and replaces it with CommandString::new, which checks that the string is <=12 bytes long.

@@ -202,7 +206,7 @@ impl NetworkMessage {

/// Return the CommandString for the message command.
pub fn command(&self) -> CommandString {
self.cmd().into()
CommandString::from(self.cmd()).expect("valid cmd string")
Copy link
Collaborator

Choose a reason for hiding this comment

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

This opens an attack vector to the rust-bitcoin-based software by just sending them invalid command string causing a crash

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think so, the return value of .cmd() is a static string defines by the enum variant, not attacker controlled.

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 the message in expect should say invalid. The complete message sounding like "program panicked because cmd string is valid" is strange.

Copy link
Contributor

@sgeisler sgeisler left a comment

Choose a reason for hiding this comment

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

This seems to be on hold for now but I really like the changes, does away with a lot of weirdness. Will do a more thorough review once it's merge-ready.

@@ -202,7 +206,7 @@ impl NetworkMessage {

/// Return the CommandString for the message command.
pub fn command(&self) -> CommandString {
self.cmd().into()
CommandString::from(self.cmd()).expect("valid cmd string")
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think so, the return value of .cmd() is a static string defines by the enum variant, not attacker controlled.

Comment on lines 72 to 74
if strbytes.len() > 12 {
return Err(encode::Error::UnrecognizedNetworkCommand(self.0.clone().into_owned()));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think so. The PR removes the uncheck instantiation path for CommandString through From and replaces it with CommandString::new, which checks that the string is <=12 bytes long.

@apoelstra apoelstra added this to the 0.26.0 milestone Jan 11, 2021
@apoelstra
Copy link
Member

Setting to 0.26 milestone. Originally we had planned to punt to 0.27 because it is an API-breaking-only change, but I this we should prioritize it since our code currently unwraps these objects whenever we encode into hash engines, for example, so it would be better to eliminate theoretical panics.

On the other hand if this winds up blocking 0.26 then I'll probably move it to 0.27 :)

Needs rebase.

@dr-orlovsky dr-orlovsky mentioned this pull request Jan 12, 2021
This is instead of encode::Errors because the encoders should
not be allowed to return errors that don't originate in the writer
they are writing into.

This is a part of the method definition that has been relied upon for a
while already.
Copy link
Member

@apoelstra apoelstra left a comment

Choose a reason for hiding this comment

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

ack 61918df

@elichai elichai merged commit d2344d3 into rust-bitcoin:master Jan 12, 2021
@JeremyRubin
Copy link
Contributor

post-ack

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
API break This PR requires a version bump for the next release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants