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

Price Oracle: validate input parameters and extend test coverage: #5013

Merged
merged 6 commits into from
May 9, 2024

Conversation

gregtatcam
Copy link
Collaborator

High Level Overview of Change

Validate trim, time_threshold, document_id are valid Int, UInt, or string convertible to UInt. Validate base_asset and quote_asset are valid currency. Update error codes. Extend Oracle and GetAggregatePrice unit-tests.
Denote unreachable coverage code.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Refactor (non-breaking change that only restructures code)
  • Performance (increase or change in throughput and/or latency)
  • Tests (you added tests for code that already exists, or your new feature included in this PR)
  • Documentation update
  • Chore (no impact to binary, e.g. .gitignore, formatting, dropping support for older tooling)
  • Release

Before / After

Fixes invalid input parameters conversion to uint. For instance, 1.2 was converted to 1 if passed to trim, time_threshold, or document_id. This only allows valid UInt values. Similarly, base_asset and quote_asset were not validated to be a valid currency code. This fix only allows valid currency codes in base_asset and quote_asset.

Test Plan

Extended Oracle and GetAggregatePrices tests.

Validate trim, time_threshold, document_id are valid
Int, UInt, or string convertible to UInt. Validate base_asset
and quote_asset are valid currency. Update error codes.
Extend Oracle and GetAggregatePrice unit-tests.
Denote unreachable coverage code.
@codecov-commenter
Copy link

codecov-commenter commented May 3, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 71.0%. Comparing base (f650949) to head (848fa68).
Report is 79 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff            @@
##           develop   #5013     +/-   ##
=========================================
+ Coverage     70.9%   71.0%   +0.1%     
=========================================
  Files          796     796             
  Lines        66792   66793      +1     
  Branches     10998   10986     -12     
=========================================
+ Hits         47377   47420     +43     
+ Misses       19415   19373     -42     
Files with missing lines Coverage Δ
src/ripple/app/tx/impl/DeleteOracle.cpp 100.0% <ø> (+27.5%) ⬆️
src/ripple/app/tx/impl/SetOracle.cpp 100.0% <ø> (+5.6%) ⬆️
src/ripple/rpc/handlers/GetAggregatePrice.cpp 100.0% <100.0%> (+8.0%) ⬆️
src/ripple/rpc/handlers/LedgerEntry.cpp 81.9% <100.0%> (+1.0%) ⬆️

... and 4 files with indirect coverage changes

Impacted file tree graph

Json::StaticString const& field,
unsigned int def =
0) -> std::variant<std::uint32_t, error_code_i> {
if (params.isMember(field))
{
if (!params[field].isConvertibleTo(Json::ValueType::uintValue))
return rpcORACLE_MALFORMED;
if (!validUInt(params, field))
Copy link
Collaborator

Choose a reason for hiding this comment

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

What is the difference between these two lines of code?

Copy link
Collaborator

Choose a reason for hiding this comment

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

If I'm reading the code correctly, it's that the old version accepted booleans and floats and would just convert them?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

isConvertible is

 return (other == nullValue && value_.uint_ == 0) ||
           (other == intValue && value_.uint_ <= (unsigned)maxInt) ||
          other == uintValue || other == realValue ||
          other == stringValue || other == booleanValue;

which means it can convert a wider range of other type. For instance, it'll convert 1.2 to 1, which is invalid in this case.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

If I'm reading the code correctly, it's that the old version accepted booleans and floats and would just convert them?

Correct.


meta = ledger->txRead(prevTx).second;

prevChain = chain;
Copy link
Collaborator

Choose a reason for hiding this comment

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

What does moving this line do? I don't really understand what prevChain is doing, and there don't appear to be any tests checking this behavior.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This handles an unlikely case when CreatedNode/ModifiedNode not found in the inner loop for AffectedNodes. If this happens then prevChain is going to be equal to chain and the iteration stops. I don't know if it's possible to reproduce it unless the ledger state is changed in a way, which is not possible to do with the transactions.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Does moving this line of code make any difference other than the slight efficiency improvement from running fewer times?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

prevChain has to be assigned outside of the inner loop. Initially prevChain is nullptr and chain points to oracle. If there is no AffectedNodes then both prevChain and chain are unchanged and the check (prevChain == chain) is not going to fail.

Comment on lines +205 to +206
if (params[field].asString().empty())
return rpcINVALID_PARAMS;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Isn't this check already included in currencyFromJson?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, it could be removed. It's just a bit faster.

BEAST_EXPECT(
oracle.expectLastUpdateTime(testStartTime.count() + 450));
BEAST_EXPECT(oracle.expectLastUpdateTime(
static_cast<std::uint32_t>(testStartTime.count() + 450)));
Copy link
Collaborator

@mvadari mvadari May 6, 2024

Choose a reason for hiding this comment

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

Is there a reason the other lines were switched to using closeTime() but this one still uses testStartTime?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The two lines that used testStartTime should have not actually used it because testStartTime is already added in Oracle::set to the passed in LastUpdateTime value.

@@ -622,6 +678,39 @@ struct Oracle_test : public beast::unit_test::suite
}
}

void
testInvalidLedgerEntry()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should these tests be here or in LedgerRPC_test?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks. It should be moved.

@@ -39,16 +55,16 @@ using DataSeries = std::vector<std::tuple<
struct CreateArg
{
std::optional<AccountID> owner = std::nullopt;
std::optional<std::uint32_t> documentID = 1;
std::optional<AnyValue> documentID = 1;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why are these types becoming less specific? Isn't that checked at the validation level?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I wanted to be able to test a wider range of valid/invalid values similar to how QA does it. Unfortunately the test framework does its own validation and will not call rpc if the parameters don't match their defined SField type. The validation is done because the transaction is signed. I left this change so that eventually can extend the testing to support unsigned transactions.


auto jr = env.rpc("json", "get_aggregate_price", to_string(jv));
toJson(jv[jss::time_threshold], *timeThreshold);
// Convert "%None%" to None
Copy link
Collaborator

@mvadari mvadari May 7, 2024

Choose a reason for hiding this comment

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

Should"%None%" be a global test constant, since it's a special case?

@seelabs seelabs merged commit f4da2e3 into XRPLF:develop May 9, 2024
17 checks passed
@seelabs seelabs mentioned this pull request May 16, 2024
1 task
sophiax851 pushed a commit to sophiax851/rippled that referenced this pull request Jun 12, 2024
…PLF#5013)

* Price Oracle: validate input parameters and extend test coverage:

Validate trim, time_threshold, document_id are valid
Int, UInt, or string convertible to UInt. Validate base_asset
and quote_asset are valid currency. Update error codes.
Extend Oracle and GetAggregatePrice unit-tests.
Denote unreachable coverage code.

* Set one-line LCOV_EXCL_LINE

* Move ledger_entry tests to LedgerRPC_test.cpp

* Add constants for "None"

* Fix LedgerRPC test

---------

Co-authored-by: Scott Determan <[email protected]>
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.

5 participants