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

Surface bitcoind rpc error information #2057

Merged
merged 1 commit into from
Mar 2, 2023

Conversation

johncantrell97
Copy link
Contributor

Currently the RpcClient only surfaces bitcoind's rpc error message but not the error code. The code is useful downstream to more reliably understand what went wrong.

This creates a new struct RpcError that holds the code and message and then provides that as the inner payload to std::io::Error that is already being returned.

let message = error["message"].as_str().unwrap_or("unknown error");
return Err(std::io::Error::new(std::io::ErrorKind::Other, message));
let rpc_error = RpcError {
code: error["code"].as_i64().unwrap_or(-1),
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we make RpcError::code an Option given -1 is RPC_MISC_ERROR? Possibly the same for message.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah I guess we could do that. I'm not sure what case (if ever) it won't have a code and message. I purposefully mapped it to RPC_MISC_ERROR since that's sort of what it is but happy to do it as Option's too if you think that makes more sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

maybe it'd be a little cleaner to make RpcError an enum with a struct variant and an "Unknown" variant when there's no code/message 🤷‍♂️

I guess you don't gain much, still need to do a match to find out error code

Copy link
Contributor

Choose a reason for hiding this comment

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

Let's keep it as is. Like you said, it probably shouldn't ever happen and the code is used as a catch-all for when the RPC handling code throws an exception in Core.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, Matt had made a comment that we should probably just use a new RpcError enum that wraps the io Error. think it’s worth just doing that?

Copy link
Contributor

Choose a reason for hiding this comment

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

That's a much larger change and is orthogonal, IMO, if you're just trying to include an extra piece of data. Changing the error type here means would make it inconsistent with RestCleint, and you'd ultimately need to wrap it again at the BlockSourceError level, anyhow, unless you want to change that as well. But that already is an enum.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, that makes sense. I was trying to avoid having to deal with a cascade of changes like that. If we're okay with this solution then it would work for what we needed (access to bitcoind error code)

jkczyz
jkczyz previously approved these changes Feb 28, 2023
@jkczyz
Copy link
Contributor

jkczyz commented Feb 28, 2023

Oh, could you update the commit message with context? We should make sure it's there independent of Github.

@@ -68,9 +87,11 @@ impl RpcClient {

let error = &response["error"];
if !error.is_null() {
// TODO: Examine error code for a more precise std::io::ErrorKind.
Copy link
Contributor

Choose a reason for hiding this comment

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

Looking into this a little more, I think the idea was that some errors may be considered transient while others are persistent. And this would affect the conversion done here:

impl From<std::io::Error> for BlockSourceError {
fn from(e: std::io::Error) -> BlockSourceError {
match e.kind() {
std::io::ErrorKind::InvalidData => BlockSourceError::persistent(e),
std::io::ErrorKind::InvalidInput => BlockSourceError::persistent(e),
_ => BlockSourceError::transient(e),
}
}
}

This let's the user know if they should give-up or retry.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah ok, that makes sense. Well I can put the TODO back. Seems like to actually impl the TODO we would need to map the error rpc error codes into buckets for persistent vs transient.

Users of the RpcClient had no way to access the error code
returned by bitcoind's rpc.  We embed a new RpcError struct
as the inner error for the returned io::Error. Users can access
both the code and the message using this inner struct.
Copy link
Contributor

@dunxen dunxen left a comment

Choose a reason for hiding this comment

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

LGTM with further specifying ErrorKind in a future PR.

@jkczyz
Copy link
Contributor

jkczyz commented Mar 2, 2023

Note that we currently check that the error contains some message strings in the sample client: https://github.com/lightningdevkit/ldk-sample/blob/e1a87a7e8533bb666ed8002032b124e628da8e12/src/bitcoind_client.rs#L261-L267

This should still work, but we may want to check the error code instead assuming that makes sense.

@jkczyz jkczyz merged commit f114515 into lightningdevkit:main Mar 2, 2023
@TheBlueMatt
Copy link
Collaborator

I'm not really sure how we expect users to use this - in one specific case we happen to return a field inside the std::io::Error, so in theory they could downcast and do type-checking, but they have to know that its (maybe) there, which isn't documented anywhere afaict, and its not clear to me that std::io::ErrorKind::Other implies its there, so you can't just use that to check either.

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

Successfully merging this pull request may close these issues.

None yet

4 participants