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

Amendment fixReducedOffersV2 fixes issue #4937 #5032

Merged
merged 6 commits into from
Jun 13, 2024

Conversation

scottschurr
Copy link
Collaborator

High Level Overview of Change

In issue #4937 @shortthefomo reported additional instances of order book blocking since the fixReducedOffersV1 amendment went live. Thanks to that report I was able to identify an additional case where blocking offers could be generated. This pull request fixes the newly identified case.

Context of Change

Having order books occasionally blocked by offers that were modified so that their quality was worse than the originally placed quality has been a problem for a while. Three different ways that this could occur were identified and addressed by amendment fixReducedOffersV1. All three of those case were addressed by carefully looking at and fixing the numeric rounding applied during calculations.

This new amendment, fixReducedOffersV2, identifies and fixes an additional rounding case that was missed during the research for fixReducedOffersV1 .

Beyond the fix itself, there are two kinds of unit test additions in this pull request.

  1. There's a new test that directly exercises the failing case both with and without the amendment. It demonstrates that the old rounding behavior could lead to blocked order books. It also shows that with the new rounding behavior the order books are no longer blocked for this range of cases.

  2. A few pre-existing test cases failed due to the changed rounding. Those test cases were examined and the tests patched to handle the new rounding behavior.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • Tests (you added tests for code that already exists, or your new feature included in this PR)

API Impact

None.

Copy link

codecov bot commented May 31, 2024

Codecov Report

Attention: Patch coverage is 97.14286% with 1 line in your changes missing coverage. Please review.

Project coverage is 71.3%. Comparing base (263e984) to head (e5dbc1d).

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##           develop   #5032   +/-   ##
=======================================
  Coverage     71.3%   71.3%           
=======================================
  Files          796     796           
  Lines        66883   66887    +4     
  Branches     10889   10871   -18     
=======================================
+ Hits         47658   47682   +24     
+ Misses       19225   19205   -20     
Files Coverage Δ
src/ripple/app/paths/AMMOffer.h 83.3% <ø> (ø)
src/ripple/app/paths/impl/AMMOffer.cpp 83.1% <100.0%> (+1.2%) ⬆️
src/ripple/app/paths/impl/BookStep.cpp 93.1% <100.0%> (-0.1%) ⬇️
src/ripple/app/tx/impl/Offer.h 97.6% <100.0%> (+0.1%) ⬆️
src/ripple/protocol/Feature.h 100.0% <ø> (ø)
src/ripple/protocol/impl/Feature.cpp 93.9% <ø> (ø)
src/ripple/protocol/impl/Quality.cpp 91.8% <100.0%> (+0.6%) ⬆️
src/ripple/protocol/Quality.h 98.5% <94.1%> (+2.7%) ⬆️

... and 7 files with indirect coverage changes

Impacted file tree graph


testLineQuality(features);
testFalseDry(features);
testDirectStep(features - reducedOffersV2);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why need to run this test with reducedOffersV2 enabled? There is no change to testDirectStep with/without the amendment.

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 agree, it's not needed. I was just following a pre-existing pattern where testDirectStep and testBookStep were having their amendments adjusted as a pair. I'd be fine with removing this test iteration if you'd like.

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 it should be removed if it doesn't accomplish anything. It just increases the tests running time without any benefit.

{
env.require(balance(carol, EUR(1)));
env.require(balance(bob, USD(0.4)));
env.require(balance(bob, EUR(999)));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does it make sense to extend this test to show that the order book is actually blocked without the amendment?

{
if (limit < stpAmt.in)
{
stpAmt.in = limit;
auto const inLmt =
mulRatio(stpAmt.in, QUALITY_ONE, transferRateIn, /*roundUp*/ false);
ofrAmt = offer.limitIn(ofrAmt, inLmt);
// It turns out we can prevent order book blocking by (strictly)
// rounding down the ceil_in() result. This adjustment changes
Copy link
Collaborator

Choose a reason for hiding this comment

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

Would it be beneficial to add a comment here and in limitStepOut explaining why the choice of the rounding in each case prevents order book blocking?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sure. Maybe something like...

// It turns out we can prevent order book blocking by (strictly)
// rounding down the ceil_in() result.  By rounding down we
// guarantee that the quality of an offer left in the ledger is
// as good or better than the quality of the containing order
// book page.

}
else
{
BEAST_EXPECT(blockedCount > 80);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't we check for the exact count of the blocking rates since the same tests are executed on every run? If this count changes in the next release then doesn't it indicate some change that needs to be investigated?

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 could. But the other, similar, tests in this file also just check for a threshold in the failing case. When I was initially developing the similar tests I found that small changes to the test would result in these numbers bouncing a bit. So just checking a threshold made development easier. The goal here is to verify that there was indeed a problem and that this test exercises the problem. Even one blocking offer would still be a problem, but we're verifying that there's at least a certain number.

The check where the number really matters in the one on line 782. We really want that value to be zero. That's how we have confidence that the problem is fully addressed, at least for this case.

So, yeah, I could put in an exact number. But my preference is not to. You okay with that?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah, it's fine.

Copy link
Collaborator

@gregtatcam gregtatcam 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. I left a few minor comments for you to consider.

Copy link
Collaborator Author

@scottschurr scottschurr left a comment

Choose a reason for hiding this comment

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

Thanks for the review @gregtatcam. I've addressed a couple of your comments. There were also two that I didn't do anything with, but I left notes about why I they are the way they are. I'm open to continuing the conversation on those if you'd like.

{
if (limit < stpAmt.in)
{
stpAmt.in = limit;
auto const inLmt =
mulRatio(stpAmt.in, QUALITY_ONE, transferRateIn, /*roundUp*/ false);
ofrAmt = offer.limitIn(ofrAmt, inLmt);
// It turns out we can prevent order book blocking by (strictly)
// rounding down the ceil_in() result. This adjustment changes
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sure. Maybe something like...

// It turns out we can prevent order book blocking by (strictly)
// rounding down the ceil_in() result.  By rounding down we
// guarantee that the quality of an offer left in the ledger is
// as good or better than the quality of the containing order
// book page.


testLineQuality(features);
testFalseDry(features);
testDirectStep(features - reducedOffersV2);
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 agree, it's not needed. I was just following a pre-existing pattern where testDirectStep and testBookStep were having their amendments adjusted as a pair. I'd be fine with removing this test iteration if you'd like.

}
else
{
BEAST_EXPECT(blockedCount > 80);
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 could. But the other, similar, tests in this file also just check for a threshold in the failing case. When I was initially developing the similar tests I found that small changes to the test would result in these numbers bouncing a bit. So just checking a threshold made development easier. The goal here is to verify that there was indeed a problem and that this test exercises the problem. Even one blocking offer would still be a problem, but we're verifying that there's at least a certain number.

The check where the number really matters in the one on line 782. We really want that value to be zero. That's how we have confidence that the problem is fully addressed, at least for this case.

So, yeah, I could put in an exact number. But my preference is not to. You okay with that?

Copy link
Collaborator

@gregtatcam gregtatcam 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

@seelabs seelabs left a comment

Choose a reason for hiding this comment

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

I think this is ready to go, but I left a couple of nits for you to consider. I'll approve it after you take a look. I'm fine with however you decide to resolve the nits (including keeping them as they are).

@@ -103,15 +104,19 @@ class AMMOffer
limitOut(
TAmounts<TIn, TOut> const& offrAmt,
TOut const& limit,
bool fixReducedOffers,
Rules const& rules,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Consider using the globally available rules rather than passing this as a parameter. (also fine as-is, but I think I'd like to get away from passing rules around as much as we do and just using the new global).

limitIn(
TAmounts<TIn, TOut> const& offrAmt,
TIn const& limit,
Rules const& rules,
Copy link
Collaborator

Choose a reason for hiding this comment

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

See comment above about passing rules as a param.

@@ -200,6 +200,29 @@ class Quality
toAmount<In>(stRes.in), toAmount<Out>(stRes.out));
}

Amounts
ceil_in_strict(Amounts const& amount, STAmount const& limit, bool roundUp)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could use a comment for how "strict" differs from the non-strict function.


template <class In, class Out>
TAmounts<In, Out>
ceil_in_strict(
Copy link
Collaborator

Choose a reason for hiding this comment

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

We usually don't define functions inline in the class. I know this is a member template and that gets a little tedious outside of the class. I'd prefer it was defined out of the class, but it's a minor nit.

TAmounts<In, Out> const& amount,
In const& limit,
bool roundUp) const
{
Copy link
Collaborator

Choose a reason for hiding this comment

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

This looks like a copy of ceil_in with one function called changed.

If ceil_in is being deprecated and will be replaced with ceil_in_strict then I actually prefer the code duplication. And I think this is the case. If this is not the case, then let's look at merging the two.

On the same topic, if the ceil_in is being deprecated, consider renaming ceil_in to ceil_in_deprecated or somesuch and renaming ceil_in_strict to ceil_in.

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'm not sure about ceil_in being deprecated. But it certainly has an odd behavior regarding rounding. So we might eventually want to replace all uses of it with ceil_in_strict. But I'd like to hold off on renaming either of them until we know that we're actually going to make that change (which would have to go under an amendment). There are a few things in this code base that have been "deprecated" for quite a few years without ever being removed. I'd like to not add to that confusion.

Amounts
Quality::ceil_in(Amounts const& amount, STAmount const& limit) const
template <STAmount (
*DivRoundFunc)(STAmount const&, STAmount const&, Issue const&, bool)>
Copy link
Collaborator

Choose a reason for hiding this comment

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

I usually like passing the callback as a parameter rather than as a template. But I'm also fine with this as-is.

Copy link
Collaborator

@seelabs seelabs left a comment

Choose a reason for hiding this comment

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

👍

@scottschurr scottschurr added the Passed Passed code review & PR owner thinks it's ready to merge. Perf sign-off may still be required. label Jun 13, 2024
@scottschurr
Copy link
Collaborator Author

scottschurr commented Jun 13, 2024

Proposed commit message:

fixReducedOffersV2: prevent offers from blocking order books:

Fixes issue #4937.

The fixReducedOffersV1 amendment fixed certain forms of offer
modification that could lead to blocked order books.  Reduced
offers can block order books if the effective quality of the
reduced offer is worse than the quality of the original offer
(from the perspective of the taker). It turns out that, for
small values, the quality of the reduced offer can be
significantly affected by the rounding mode used during
scaling computations.

Issue #4937 identified an additional code path that modified
offers in a way that could lead to blocked order books.  This
commit changes the rounding in that newly located code path so
the quality of the modified offer is never worse than the
quality of the offer as it was originally placed.

It is possible that additional ways of producing blocking
offers will come to light.  Therefore there may be a future
need for a V3 amendment.

@seelabs seelabs merged commit ae7ea33 into XRPLF:develop Jun 13, 2024
19 checks passed
@seelabs seelabs mentioned this pull request Jun 20, 2024
1 task
ximinez added a commit to ximinez/rippled that referenced this pull request Jul 1, 2024
* upstream/develop: (32 commits)
  fixInnerObjTemplate2 amendment (XRPLF#5047)
  Set version to 2.3.0-b1
  Ignore restructuring commits (XRPLF#4997)
  Recompute loops (XRPLF#4997)
  Rewrite includes (XRPLF#4997)
  Rearrange sources (XRPLF#4997)
  Move CMake directory (XRPLF#4997)
  Add bin/physical.sh (XRPLF#4997)
  Prepare to rearrange sources: (XRPLF#4997)
  Change order of checks in amm_info: (XRPLF#4924)
  Add the fixEnforceNFTokenTrustline amendment: (XRPLF#4946)
  Replaces the usage of boost::string_view with std::string_view (XRPLF#4509)
  docs: explain how to find a clang-format patch generated by CI (XRPLF#4521)
  XLS-52d: NFTokenMintOffer (XRPLF#4845)
  chore: remove repeat words (XRPLF#5041)
  Expose all amendments known by libxrpl (XRPLF#5026)
  fixReducedOffersV2: prevent offers from blocking order books: (XRPLF#5032)
  Additional unit tests for testing deletion of trust lines (XRPLF#4886)
  Fix conan typo: (XRPLF#5044)
  Add new command line option to make replaying transactions easier: (XRPLF#5027)
  ...
vlntb pushed a commit to vlntb/rippled that referenced this pull request Aug 23, 2024
…5032)

Fixes issue XRPLF#4937.

The fixReducedOffersV1 amendment fixed certain forms of offer
modification that could lead to blocked order books.  Reduced
offers can block order books if the effective quality of the
reduced offer is worse than the quality of the original offer
(from the perspective of the taker). It turns out that, for
small values, the quality of the reduced offer can be
significantly affected by the rounding mode used during
scaling computations.

Issue XRPLF#4937 identified an additional code path that modified
offers in a way that could lead to blocked order books.  This
commit changes the rounding in that newly located code path so
the quality of the modified offer is never worse than the
quality of the offer as it was originally placed.

It is possible that additional ways of producing blocking
offers will come to light.  Therefore there may be a future
need for a V3 amendment.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Amendment Bug Passed Passed code review & PR owner thinks it's ready to merge. Perf sign-off may still be required.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants