-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
fixReducedOffersV1
: curtail the occurrence of order books that are blocked by reduced offers
#4512
Conversation
The goal of this amendment is to curtail the occurrence of order books that are blocked by reduced offers. This commit identifies three ways in which offers can be reduced: 1. A new offer can be partially crossed by existing offers, so the new offer is reduced when placed in the ledger. 2. An in-ledger offer can be partially crossed by a new offer in a transaction. So the in-ledger offer is reduced by the new offer. 3. An in-ledger offer may be under-funded. In this case the in-ledger offer is scaled down to match the available funds. 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. This commit adjusts some rounding modes so that the quality of a reduced offer is always at least as good (from the taker's perspective) as the original offer. The amendment is titled fixReducedOffersV1 because additional ways of producing reduced offers may come to light. So there may be a future need for a V2 amendment.
@@ -577,6 +586,7 @@ BookStep<TIn, TOut, TDerived>::forEachOffer( | |||
sb, afView, book_, sb.parentCloseTime(), counter, j_); | |||
|
|||
bool const flowCross = afView.rules().enabled(featureFlowCross); | |||
bool const fixReduced = afView.rules().enabled(fixReducedOffersV1); | |||
bool offerAttempted = false; | |||
std::optional<Quality> ofrQ; | |||
while (offers.step()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a comment for line 590 else if (*ofrQ != offer.quality())
below (doesn't look like github allows commenting on collapsed lines). I'm not sure if this is a problem but judging from the PR comment
This commit adjusts some rounding modes so that the quality of a reduced offer is always at least as good (from the taker's perspective) as the original offer.
I assume it's possible that the reduced offer written back to the ledger has a better quality than the original offer, in which case this offer might block the order book since the check on line 590 would fail for this offer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good for you to point out.
The way I understand this code (and @seelabs may correct me on this), forEachOffer()
is intended to run through all offers that have the same Quality
in an order book. So we get the Quality
of the offer at the tip of the order book -- that's how the optional
is filled in. If any subsequent offers in the order book have a different Quality
, then we return from forEachOffer()
.
But that doesn't mean we're done. It just means that we've done as much as we can with this Strand
for now. If the Quality
from this go-round is good enough, then the payment engine will try this stand again and get some more offers out of this path.
One more source of confusion is that the code works with two different Quality
s here. There is the Quality
that is encoded in the offer book directory page -- that should be the Quality
of the offer as initially placed (not necessarily reflecting the actual Quality
of a reduced offer). I'm pretty sure that's the Quality
being used in the place you pointed out. There are other places that deal in the actual Quality
of an offer.
At any rate, the determination that a particular order book is blocked happens further up the call stack here: https://github.com/XRPLF/rippled/blob/develop/src/ripple/app/paths/impl/StrandFlow.h#L638-L644
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. I missed that this better quality offer or the next one after it are going to be picked on the next payment engine iteration if there is more output left.
There was a problem hiding this 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 believe the top-most commit addresses your comments.
@@ -577,6 +586,7 @@ BookStep<TIn, TOut, TDerived>::forEachOffer( | |||
sb, afView, book_, sb.parentCloseTime(), counter, j_); | |||
|
|||
bool const flowCross = afView.rules().enabled(featureFlowCross); | |||
bool const fixReduced = afView.rules().enabled(fixReducedOffersV1); | |||
bool offerAttempted = false; | |||
std::optional<Quality> ofrQ; | |||
while (offers.step()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good for you to point out.
The way I understand this code (and @seelabs may correct me on this), forEachOffer()
is intended to run through all offers that have the same Quality
in an order book. So we get the Quality
of the offer at the tip of the order book -- that's how the optional
is filled in. If any subsequent offers in the order book have a different Quality
, then we return from forEachOffer()
.
But that doesn't mean we're done. It just means that we've done as much as we can with this Strand
for now. If the Quality
from this go-round is good enough, then the payment engine will try this stand again and get some more offers out of this path.
One more source of confusion is that the code works with two different Quality
s here. There is the Quality
that is encoded in the offer book directory page -- that should be the Quality
of the offer as initially placed (not necessarily reflecting the actual Quality
of a reduced offer). I'm pretty sure that's the Quality
being used in the place you pointed out. There are other places that deal in the actual Quality
of an offer.
At any rate, the determination that a particular order book is blocked happens further up the call stack here: https://github.com/XRPLF/rippled/blob/develop/src/ripple/app/paths/impl/StrandFlow.h#L638-L644
There are compiler warnings on OSX M1 Ventura 13.3.1(a), clang 14.03:
I don't see these warnings on |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gregtatcam, sorry that I wasn't paying close enough attention last time. The most recent commit adds the method that you suggested.
I just looked at the latest develop. I see this commit which I think fixes the compiler warning you are seeing: 8d482d3 I can rebase if you'd like, as long as it wouldn't mess up @HowardHinnant's review. |
I checked that the rebase fixes this warning. No need to rebase now. |
Should we consider including in this PR a fix for the related issue: https://ripplelabs.atlassian.net/browse/RXL-226? The fix is to pass the |
We could consider including that fix. My current inclination is to put that fix on a separate amendment. These three fixes are useful and have thorough unit tests. At the least we'd need to add unit tests for the new fix. With those tests I'd hope to learn whether we can set Beyond that, I heard a report recently that the the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 LGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added one minor suggestion.
I'm fine with moving NumberRoundModeGuard
to Number.h. And I think that is a good name for this functionality.
On adding Number::away_from_zero
. I'm fine with doing this if there is agreement it would be useful. And it looks like a fairly minor addition in terms of effort. It wasn't included initially because it wasn't asked for and it isn't part of the C API. However C20 did add "round away from zero on tie", which is somewhat similar.
We should add it if it would simplify client code, and not if it would just add confusion.
The topmost commit addresses comments by @HowardHinnant. In terms of adding a new This is the situation:
The variable
Here I'm using Since I'm looking at this harder, I happen to have enough information to synthesize the behavior that I want:
I guess I'm inclined to use this work around, since it's the only place in the code that would use the new Thoughts? |
The top-most commit fixes the rounding problem in divRound without requiring a new rounding mode in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me.
@gregtatcam, do you want a last opportunity to look over this pull request before I mark it |
I'll review tonight. |
Thanks, looks good! |
…F#4512) Curtail the occurrence of order books that are blocked by reduced offers with the implementation of the fixReducedOffersV1 amendment. This commit identifies three ways in which offers can be reduced: 1. A new offer can be partially crossed by existing offers, so the new offer is reduced when placed in the ledger. 2. An in-ledger offer can be partially crossed by a new offer in a transaction. So the in-ledger offer is reduced by the new offer. 3. An in-ledger offer may be under-funded. In this case the in-ledger offer is scaled down to match the available funds. 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. This commit adjusts some rounding modes so that the quality of a reduced offer is always at least as good (from the taker's perspective) as the original offer. The amendment is titled fixReducedOffersV1 because additional ways of producing reduced offers may come to light. Therefore, there may be a future need for a V2 amendment.
ignore me missed that this was merged! |
Curtail the occurrence of order books that are blocked by reduced offers with the implementation of the fixReducedOffersV1 amendment. This commit identifies three ways in which offers can be reduced: 1. A new offer can be partially crossed by existing offers, so the new offer is reduced when placed in the ledger. 2. An in-ledger offer can be partially crossed by a new offer in a transaction. So the in-ledger offer is reduced by the new offer. 3. An in-ledger offer may be under-funded. In this case the in-ledger offer is scaled down to match the available funds. 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. This commit adjusts some rounding modes so that the quality of a reduced offer is always at least as good (from the taker's perspective) as the original offer. The amendment is titled fixReducedOffersV1 because additional ways of producing reduced offers may come to light. Therefore, there may be a future need for a V2 amendment.
…F#4512) Curtail the occurrence of order books that are blocked by reduced offers with the implementation of the fixReducedOffersV1 amendment. This commit identifies three ways in which offers can be reduced: 1. A new offer can be partially crossed by existing offers, so the new offer is reduced when placed in the ledger. 2. An in-ledger offer can be partially crossed by a new offer in a transaction. So the in-ledger offer is reduced by the new offer. 3. An in-ledger offer may be under-funded. In this case the in-ledger offer is scaled down to match the available funds. 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. This commit adjusts some rounding modes so that the quality of a reduced offer is always at least as good (from the taker's perspective) as the original offer. The amendment is titled fixReducedOffersV1 because additional ways of producing reduced offers may come to light. Therefore, there may be a future need for a V2 amendment.
…F#4512) Curtail the occurrence of order books that are blocked by reduced offers with the implementation of the fixReducedOffersV1 amendment. This commit identifies three ways in which offers can be reduced: 1. A new offer can be partially crossed by existing offers, so the new offer is reduced when placed in the ledger. 2. An in-ledger offer can be partially crossed by a new offer in a transaction. So the in-ledger offer is reduced by the new offer. 3. An in-ledger offer may be under-funded. In this case the in-ledger offer is scaled down to match the available funds. 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. This commit adjusts some rounding modes so that the quality of a reduced offer is always at least as good (from the taker's perspective) as the original offer. The amendment is titled fixReducedOffersV1 because additional ways of producing reduced offers may come to light. Therefore, there may be a future need for a V2 amendment.
High Level Overview of Change
The goal of this amendment is to curtail the occurrence of order books that are blocked by reduced offers.
This commit identifies three ways in which offers can be reduced:
A new offer can be partially crossed by existing offers, so the new offer is reduced when placed in the ledger.
An in-ledger offer can be partially crossed by a new offer in a transaction. So the in-ledger offer is reduced by the new offer.
An in-ledger offer may be under-funded. In this case the in-ledger offer is scaled down to match the available funds.
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.
This commit adjusts some rounding modes so that the quality of a reduced offer is always at least as good (from the taker's perspective) as the original offer.
These rounding changes are on an amendment because they will affect transaction outcomes. The amendment is titled
fixReducedOffersV1
because additional ways of producing reduced offers may come to light. So there may be a future need for aV2
amendment.Context of Change
Occasional blocked order books have been a long-standing low-level irritant with the DEX. There has been at least one patch that helped to manage the problem. But the patch did not really make the problem go away. This pull request goes to the root of these blockages and just makes them go away.
First, it's important to recognize that both XRP and IOUs have finite representations in the ledger. For example, it is not possible to represent fractions of drops. IOUs also have a finite representation that becomes apparent with very small or very large numbers. So when a number cannot be precisely represented in XRP or by an IOU it must be rounded.
This pull request changes the rounding mode that is used when calculating a reduced offer. The rounding is changed so that the quality of the reduced offer is always the same or better than the quality of the original offer (viewing the offer's quality from the taker's perspective).
Note that this change affects the values that offers in the ledger contain. However the change in representation is at most either...
As the values in offers get tiny, rounding effects can have an outsize influence on the quality of an offer. But the offers are only being adjusted by minute amounts of actual funds.
Type of Change
I'm calling this a breaking change because outcomes on the DEX are affected. People who care deeply about the specifics of DEX corner cases would do well to understand the consequences of this pull request. The pull request also includes an amendment, which can cause amendment blockage.
I'm unsure whether the Doc team will want to include discussion of the details of this change. But they should be aware of the change and decide for themselves.
Interactions with
Number
It turns out that this pull request interacts with the new
Number
integration withSTAmount
. There are three specific things to note:STAmount::canonicalize rounding
The rounding characteristics ofSTAmount::canonicalize()
changed whenNumber
was integrated. The new defaultcanonicalize
rounding interacted badly with the rounding thatfixReducedOffersV1
requires. Fortunately,Number
provides a way to remotely control its rounding mode. This pull request uses that capability.Number
Rounding Modes The rounding modes thatNumber
provides do not align precisely with the rounding thatSTAmount
operations use.STAmount
uses abool
namedroundUp
. But, what thatbool
really means isroundAwayFromZero
. So the rounding direction reverses for negative numbers. If thebool
istrue
STAmount
rounds away from zero, andfalse
rounds toward zero.Number
provides atowards_zero
rounding mode. So that's covered. ButNumber
does not provide anaway_from_zero
rounding mode. So I faked this withNumber
'supward
rounding mode. This should usually work. And it works in all of my tests becauseSTAmount
values are usually positive.But it would be nice if
Number
provided anaway_from_zero
rounding mode. That would remove the compromise of faking it withupward
.Number::RoundModeGuard
. As I was surgically controlling theNumber
rounding mode to get the behavior I wanted out ofSTAmount::canonicalize()
I introduced a bug. I wanted a way to change theNumber
rounding mode only while I was in a scope and have that mode revert when I left scope. I thought thatNumber
provided that precise tool withsaveNumberRoundMode
. But I was wrong. I did not read the code carefully enough and made assumptions about the behavior. So I misusedsaveNumberRoundMode
and @HowardHinnant set me right.@HowardHinnant ended up implementing
NumberRoundModeGuard
to give me exactly the behavior that I wanted. You can see that (almost trivial) implementation in STAmount.cpp of this pull request.In general I personally think
NumberRoundModeGuard
is exactly the tool that folks should reach for when surgically adjusting the rounding mode. I'd like to suggest that it should be a permanent fixture inNumber
.Number
Request Summary Here are the changes I'm hoping for:away_from_zero
rounding mode toNumber
.Number::RoundModeGuard
a part of theNumber
implementation similar tosaveNumberRoundMode
.I'll be curious what @gregtatcam and @HowardHinnant think regarding these requests.
I'm requesting @gregtatcam and @HowardHinnant as reviewers since they have the most experience with
Number
. I'm hoping that @seelabs will also take a gander since these changes are integrated into the payment engine.