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

APIv2(ledger_entry) : check error #4630

Merged
merged 9 commits into from
Sep 8, 2023

Conversation

PeterChen13579
Copy link
Contributor

High Level Overview of Change

Fix issue #4550 on ledger entry

Context of Change

"check" field is verified to be a string. (Check the above Github issue link for more info.) Everything is under API Version 2 as it is a breaking change.

Type of Change

  • [X ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • [ X] Tests (You added tests for code that already exists, or your new feature included in this PR)

Added own unit test for API Version 2;

@PeterChen13579 PeterChen13579 changed the title APIv2(update check field) APIv2(ledger_entry) : error message Jul 19, 2023
@PeterChen13579 PeterChen13579 changed the title APIv2(ledger_entry) : error message APIv2(ledger_entry) : check error Jul 19, 2023
Json::Value const jrr = env.rpc(
"json", "ledger_entry", to_string(jvParams))[jss::result];

if (apiVersion < 2u)

Choose a reason for hiding this comment

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

nit: why is apiVersion checked with < 2u in this file compared > 1u? seems better to keep consistent throughout codebase (if most files have already established some standard)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the test file, it is specifically checking results for apiVersion < 2u and apiVersion other than that (>=2) , while in non-test files, the new error codes only execute if apiVersion is greater than 1. There is a guideline already established here:

Choose a reason for hiding this comment

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

Don't have access to that doc, but thanks for elaboration. If you don't mind sharing it, my email is [email protected]

Is > 1u in non-test file equivalent to >= 2u in test file? These seem to always be equivalent expressions since it is impossible to have decimal (1.5) here, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yea > 1u in non-test file is equivalent to >= 2u in test file.

Copy link
Collaborator

@arihantkothari arihantkothari left a comment

Choose a reason for hiding this comment

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

LGTM, but comments can be improved.

@@ -51,7 +51,15 @@ doLedgerEntry(RPC::JsonContext& context)

if (context.params.isMember(jss::index))
{
if (!uNodeIndex.parseHex(context.params[jss::index].asString()))
// Document states "index" field is a string. check for this apiVersion
Copy link
Collaborator

Choose a reason for hiding this comment

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

What document are you referring to ? xrpl.org ? You can make this clear in the comments and remove ambiguity.

Copy link
Collaborator

@godexsoft godexsoft left a comment

Choose a reason for hiding this comment

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

LGTM. Feel free to ignore my minor comments or apply the suggestions - up to you 👍

// apiVersion 2 onwards. invalidParam error is thrown if the above
// condition is false
if (!context.params[jss::nft_page].isString() &&
context.apiVersion > 1u)
Copy link
Collaborator

Choose a reason for hiding this comment

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

minor: You could switch the checks around to save some unnecessary work in case apiVersion is 1

// XRPL Doc states "index" field is a string. check for this apiVersion
// 2 onwards. invalidParam error is thrown if the above condition is
// false
if (!context.params[jss::index].isString() && context.apiVersion > 1u)
Copy link
Collaborator

Choose a reason for hiding this comment

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

minor: same thing with switching params around

@intelliot
Copy link
Collaborator

@godexsoft @arihantkothari to review new changes

@intelliot intelliot added this to the 1.12 milestone Aug 16, 2023
Copy link
Collaborator

@godexsoft godexsoft left a comment

Choose a reason for hiding this comment

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

👍

Copy link
Collaborator

@arihantkothari arihantkothari left a comment

Choose a reason for hiding this comment

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

lgtm 👍

Copy link
Collaborator

@ximinez ximinez left a comment

Choose a reason for hiding this comment

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

You made changes to the cases for jss::index, jss::check, jss::directory, and jss::nft_page, but you only added a test case for jss::check. As noted below, the change for nft_page is unnecessary. That aside, you also need to test index and directory. However, the directory case knows how to handle a json object, so you also need to test with something it can't handle: arrays.

Comment on lines 55 to 56
// 2 onwards. invalidParam error is thrown if the above condition is
// false
Copy link
Collaborator

Choose a reason for hiding this comment

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

To be precise, the error is not thrown, it's returned. I think it's worth rewriting this and all the other comments to say, "invalidParam error is returned if the above condition is false.", or even simpler, "invalidParam error is returned if not."

Json::Value checkParams;
checkParams[jss::owner] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU";
checkParams[jss::ledger_index] = "validated";
jvParams[jss::check] = checkParams;
Copy link
Collaborator

Choose a reason for hiding this comment

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

You're reusing the jvParams from the previous test here, so that contains features, etc. To guarantee that you're testing what you expect to be testing, start with a new jvParams.

Comment on lines 359 to 369
if (context.params[jss::nft_page].isString())
{
// XRPL Doc states "nft_page" field is a string. check for this
// apiVersion 2 onwards. invalidParam error is thrown if the above
// condition is false
if (context.apiVersion > 1u &&
!context.params[jss::nft_page].isString())
{
uNodeIndex = beast::zero;
jvResult[jss::error] = "invalidParams";
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Line 359 is checking whether nft_page is a string, so the check on line 365 that it's a string can never fail. In other words, this block is unnecessary.

Copy link
Collaborator

@ximinez ximinez left a comment

Choose a reason for hiding this comment

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

After posting my previous review, I dug a little further, and ended up going down a rabbit hole.

The original issue that this PR fixes is #4550. The basic problem is that the request is returning an "Internal error" for some bad inputs. The goal is to change that to return "invalidParams".

The question is why is "Internal error" being returned in the first place. Long story short, the Json::Value::asString() function throws a Json::error if the value is actually a Json object or array.

So I went through doLedgerEntry and looked for every unchecked call to asString. There were actually about a dozen more that were not handled in the original PR. I also expanded the unit tests to check all of those cases, including verifying that the checked calls to asString returned something other than "Internal error". That commit is here. ximinez@4c9bb17 (Feel free to cherry-pick that onto your branch.)

But then I looked over the whole change set and was really unhappy with all of these piecemeal checks.

There's a much cleaner way to do it, which eliminates the worry of missing any cases and is future-proof (in case later lookups neglect some of those checks). Just catch the thrown Json::error once. Here's that commit: dbe7c5c (Feel free to cherry-pick that one, too, but you'll have to grab the first one to merge cleanly. Or just merge the branch.)

It's much prettier if you hide whitespace changes.

@PeterChen13579
Copy link
Contributor Author

@ximinez Thank you for all the insight! 😄. I pretty much cherry-picked the try-catch and unittest. I agree it's a LOT cleaner this way and prevents future errors to occur.

@PeterChen13579 PeterChen13579 requested a review from ximinez August 17, 2023 20:36
Copy link
Collaborator

@ximinez ximinez left a comment

Choose a reason for hiding this comment

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

Looks good

@intelliot intelliot requested a review from connorjchen August 23, 2023 17:01
@intelliot
Copy link
Collaborator

@godexsoft @arihantkothari - this PR has changed since the time you reviewed it. Could you have a look at the latest?

@godexsoft
Copy link
Collaborator

The diff generated by github for this PR is horrible, very hard to understand what the actual changes are.

My 2c:

  • It looks like the change does simplify the code a bit - nice
  • However, relying on exceptions to handle logic is not a very good practice (this is verifying user input)
  • Although this PR does not introduce this behaviour - it was already there in one way or another. Sometimes exceptions are a necessary evil.

Since @ximinez is happy with the change I have no objections.

@ximinez
Copy link
Collaborator

ximinez commented Aug 23, 2023

The diff generated by github for this PR is horrible, very hard to understand what the actual changes are.

Click the gear at the top of the diff, and check "Hide whitespace", then click "Apply and reload". Makes a huge difference on diffs like this.

  • However, relying on exceptions to handle logic is not a very good practice (this is verifying user input)

Sometimes it's a great practice, especially when you're dealing with user input, because you never know what kind of wacky input a user is going to send you, and trying to guess everything a user can do wrong can make the code a lot more complex and hard to read. Compare that to a catch that handles any unexpected behavior...

Besides, like you said, we already have exception handling - that's what returns the "Internal error" in the current version. This change just puts a prettier error on it.

@intelliot intelliot modified the milestones: 1.12, 1.13 Aug 23, 2023
@godexsoft
Copy link
Collaborator

@ximinez Thanks for the nifty trick to make the diff more readable!

In my opinion, user input is not a good place for using exceptions but as i said, they are sometimes a necessary evil, especially if your codebase is already relying on them.
A better solution in my opinion would be a wrapper that hides all the verification and conversions away and returns either values or errors thru std::expected (or ripple::Expected in case of rippled atm.).
I totally understand that one can't simply change the way things are working so I have no objections in this case, just stating my opinion.

My point is - the way to simplify code is thru abstractions that can be tested separately and hide irrelevant details.
Of course i can't disagree with the fact that catching one specific exception is better than "pokemon exception handling" :)

@intelliot
Copy link
Collaborator

note: updates rippled (with api_version 2) to behave as Clio already does

@intelliot intelliot merged commit c6f6375 into XRPLF:develop Sep 8, 2023
gregtatcam pushed a commit to gregtatcam/rippled that referenced this pull request Sep 8, 2023
)

- Verify "check", used to retrieve a Check object, is a string.
- Verify "nft_page", used to retrieve an NFT Page, is a string.
- Verify "index", used to retrieve any type of ledger object by its
  unique ID, is a string.
- Verify "directory", used to retrieve a DirectoryNode, is a string or
  an object.

This change only impacts api_version 2 since it is a breaking change.

https://xrpl.org/ledger_entry.html

Fix XRPLF#4550
ckeshava pushed a commit to ckeshava/rippled that referenced this pull request Sep 22, 2023
)

- Verify "check", used to retrieve a Check object, is a string.
- Verify "nft_page", used to retrieve an NFT Page, is a string.
- Verify "index", used to retrieve any type of ledger object by its
  unique ID, is a string.
- Verify "directory", used to retrieve a DirectoryNode, is a string or
  an object.

This change only impacts api_version 2 since it is a breaking change.

https://xrpl.org/ledger_entry.html

Fix XRPLF#4550
ckeshava pushed a commit to ckeshava/rippled that referenced this pull request Sep 25, 2023
)

- Verify "check", used to retrieve a Check object, is a string.
- Verify "nft_page", used to retrieve an NFT Page, is a string.
- Verify "index", used to retrieve any type of ledger object by its
  unique ID, is a string.
- Verify "directory", used to retrieve a DirectoryNode, is a string or
  an object.

This change only impacts api_version 2 since it is a breaking change.

https://xrpl.org/ledger_entry.html

Fix XRPLF#4550
sophiax851 pushed a commit to sophiax851/rippled that referenced this pull request Jun 12, 2024
)

- Verify "check", used to retrieve a Check object, is a string.
- Verify "nft_page", used to retrieve an NFT Page, is a string.
- Verify "index", used to retrieve any type of ledger object by its
  unique ID, is a string.
- Verify "directory", used to retrieve a DirectoryNode, is a string or
  an object.

This change only impacts api_version 2 since it is a breaking change.

https://xrpl.org/ledger_entry.html

Fix XRPLF#4550
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

[ledger_entry with invalid combination of parameters return internal error] (Version: [1.11.0-rc2]])
6 participants