-
Notifications
You must be signed in to change notification settings - Fork 119
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
Part 1: RFQ uses fixed-points #1136
Conversation
756bc82
to
bceafd1
Compare
Pull Request Test Coverage Report for Build 11073777673Details
💛 - Coveralls |
Current progress of feeding fixedpoints through the code, and building the commit structure through |
63133bb
to
ffa577d
Compare
I need to re-write this so that we use We must use |
ffa577d
to
e213d57
Compare
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.
Nice work! In particular, I found the commit structure very easy to follow.
I think my main comments are related to the usage of the new FixedPoint
integer, namely:
- The main constructor should always be used. We take a value, then scale that, to arrive at the coefficient. At times I see a value (which may have already been scaled) used directly to create the struct, by passing the constructor. I think we should make the fields private to force everything to use the constructor.
- If this isn't done properly, then we'll end up manipulating a doubly or even triply scaled value. The eventual unit tests should also serve to bind this to make sure conversion is happening properly everywhere.
- We only want to use the big int version. The uint64 version was added mainly for completions sake.
rfqmath/arithmetic.go
Outdated
// integer is negative or exceeds the maximum value for `uint64`. | ||
func (b BigInt) ToUint64Safe() (uint64, error) { | ||
if !b.value.IsUint64() { | ||
return 0, fmt.Errorf("cannot convert %s to uint64", |
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.
We should make this into a concrete error so future tests can assert agains it.
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.
Also does this need to be added to the main interface?
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.
I'm not sure if it should be added to the interface. The long term solution as I see it is to replace ToUint64
with ToUint64Safe
. So end up with ToUint64() (uint64, error)
in the interface. But I didn't want to do that here because that would change quite a few lines.
// TODO(ffranr): Temp solution. | ||
AskPrice: event.AssetRate.Coefficient.ToUint64(), | ||
Expiry: event.Expiry, | ||
AskAssetRate: &rfqrpc.FixedPoint{ |
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.
Same here, the constructors should always be used. This is how we make sure that Coefficient
is the right size internally. We want to scale up with the constructor, not before.
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.
Would you like me to create a marshal constructor for the fixed-point RPC type?
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.
Yeah, maybe just a rfqrpc.MarshalFixedPoint
that does exactly this. Just to make sure we don't accidentally forget the scale or coefficient (however unlikely that is).
@@ -62,6 +62,10 @@ func MilliSatoshiToUnits[N Int[N]](milliSat lnwire.MilliSatoshi, | |||
// compute the total amount of mSAT (X) as follows: | |||
// - X = (U / Y) * M | |||
// - where M is the number of mSAT in a BTC (100,000,000,000). | |||
// | |||
// TODO(ffranr): This function only works with BigInt as the underlying |
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.
We only want to use the big int version. The other was added just to have another implementation, and also demonstrate where the unit64 will lose precision.
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.
With this TODO, in a later PR, I hope to make UnitsToMilliSatoshi
specific to BigInt
fixed-points by removing the generic type parameter.
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
480c411
to
aa53ed4
Compare
@Roasbeef: review reminder |
Ok, discussed offline, and for now re format, we'll:
|
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.
Reviewed 30 of 30 files at r1, 1 of 29 files at r4.
Reviewable status: 2 of 30 files reviewed, 27 unresolved discussions (waiting on @ffranr)
rpcserver.go
line 7174 at r4 (raw file):
// Convert the asset amount into a fixed-point. assetAmount := rfqmsg.NewBigIntFixedPoint(req.AssetAmount, 0)
This should be using the decimal display of the given asset.
rfq/order.go
line 128 at r4 (raw file):
// Check that the HTLC amount is not greater than the negotiated maximum // amount. maxAssetAmount := rfqmsg.NewBigIntFixedPoint(c.MaxAssetAmount, 0)
Same here, should be using the proper scale value (from the decimal display).
This commit introduces the `BigInt.String` method, which can be used to safely (without overflow) export a `BigInt` to RPC or JSON.
This two points are relevant to the part 2 PR: #1141
This change is included in this PR. |
In both of these comments I think you're asking me to construct the fixed-points using the asset's decimal display (instead of just using a scale of 0 and setting the coefficient directly). I don't see why we should care about the decimal display value here. I think the value of the fixed-points remains the same, and the calculation is unaffected. IMO looking up the decimal display introduces an unnecessary potential for error. Why would we need the asset's decimal display here? |
c5a94c3
to
8825621
Compare
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.
Nice work! I think this is pretty close, with the exception of two uses of 0 as the scale that aren't marked with // TODO: temp solution
.
Perhaps those instances could be partially recreated in a unit test to show they do the right thing. Properly executing the specific logic within a unit or integration test is probably pretty hard. So just a demo of the conversion part in a unit test should give us some confidence with some real-world number examples that are easy to follow as mere humans.
// TODO(ffranr): Temp solution. | ||
AskPrice: event.AssetRate.Coefficient.ToUint64(), | ||
Expiry: event.Expiry, | ||
AskAssetRate: &rfqrpc.FixedPoint{ |
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.
Yeah, maybe just a rfqrpc.MarshalFixedPoint
that does exactly this. Just to make sure we don't accidentally forget the scale or coefficient (however unlikely that is).
rpcserver.go
Outdated
} | ||
|
||
// Convert the asset amount into a fixed-point. | ||
assetAmount := rfqmsg.NewBigIntFixedPoint(req.AssetAmount, 0) |
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.
I thought about this and what @Roasbeef said a lot, and I think I agree with him that we can't use 0 here.
Because we are going to calculate a price for the asset, it is important that we know the inherent representation of the asset. So basically if we use 0, we would imply one asset unit being 1 dollar, even though the asset might have a decimal display of 3 or 6.
So we do need to query the decimal display value here and use it. That also makes sure we have synced the issuance proof for that asset when attempting to create an invoice.
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 how I see it:
We're calculating msat value from asset units and an asset unit to BTC rate. The decimal display is not relevant to the calculation. I've added this unit test to show that scale 0 works here: 875c0e2
Why is decimal display useful here for converting asset units to msats?
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.
Are we saying that req.AssetAmount
is not in asset units but in real world currency units here? I think it's in asset units because it's a uint64
, right?
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.
Ultimately it is in asset units, just unscaled. I think that's where they divergence is in our thought models here, and also where I want to make sure we're very careful (re use of constructors, etc). My fear was that if this was already a scaled value (output from some other operation that didn't scale down), then we'd compound an error by not carrying along what the true scale should be.
So far now, I think this seems to be fine, but ultimately the further unit and itests will be the true judge.
I think my scenario above can still happen, if we ever write a scaled value to wire/rpc w/o the scale itself. Will think about how we can add some sanity checks or assertions elsewhere to make sure we catch instances like this.
rfq/order.go
Outdated
@@ -128,7 +125,12 @@ func (c *AssetSalePolicy) CheckHtlcCompliance( | |||
|
|||
// Check that the HTLC amount is not greater than the negotiated maximum | |||
// amount. | |||
maxOutboundAmount := lnwire.MilliSatoshi(c.MaxAssetAmount) * c.AskPrice | |||
maxAssetAmount := rfqmsg.NewBigIntFixedPoint(c.MaxAssetAmount, 0) |
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.
See previous comment, we need to fetch the decimal display unit here and use it.
outgoingAssetAmount := rfqmath.MilliSatoshiToUnits( | ||
htlc.AmountOutMsat, c.AskAssetRate, | ||
) | ||
amt := outgoingAssetAmount.ScaleTo(0).ToUint64() |
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.
Hmm, from looking at the Godoc of ToUint64()
the ScaleTo(0)
shouldn't be required, as it mentions "scaled down from the existing scale". But it looks like the implementation doesn't match that. Perhaps something to fix later on.
This commit introduces a type alias for a fixed-point type where the coefficient is a `big.Int`.
This commit introduces a `big.Int` fixed-point constant representing the number of milli-satoshis in one BTC.
875c0e2
to
3a37e87
Compare
This commit updates the RFQ price oracle's `QueryRateTick` endpoint to return asset-to-BTC conversion rates as fixed-point numbers.
This commit replaces the BuyRequest.BipPrice field with SuggestedAssetRate, changing a `uint64` price to an asset-to-BTC rate represented as a fixed-point number.
This commit replaces the BuyAccept.AskPrice field with AssetRate, changing a `uint64` price to an asset-to-BTC rate represented as a fixed-point number.
This commit changes the PeerAcceptedBuyQuote.AskPrice field from a `uint64` to a fixed-point representation for the asset-to-BTC rate. It also updates the invoice milli-sat calculation to use the newly introduced fixed-point field.
This commit modifies the AssetSalePolicy struct to carry the asset-to-BTC rate instead of a "price". It also updates the policy check equations to use the new rate field.
This commit replaces the SellRequest.AskPrice field with SuggestedAssetRate, changing a `uint64` price to an asset-to-BTC rate represented as a fixed-point number.
This commit replaces the SellAccept.BidPrice field with AssetRate, changing a `uint64` price to an asset-to-BTC rate represented as a fixed-point number.
This test ensures that converting an asset amount to fixed-point values with different scales produces the same result when used in `UnitsToMilliSatoshi`. The test confirms that the absence of a decimal display value (scale) does not affect the final result.
3a37e87
to
eeeb198
Compare
I think our confusion might come down to how we define fields. Consider this part of the code which is contentious:
From my POV:
|
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 🐒
@@ -4,21 +4,185 @@ go 1.22 | |||
|
|||
toolchain go1.22.3 | |||
|
|||
replace github.com/lightninglabs/taproot-assets => ../../../ | |||
replace ( | |||
github.com/lightninglabs/taproot-assets => ../../../ |
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.
I think this should be removed before merge?
Add a unit test demonstrating how a price oracle returns a TAP asset-to-BTC rate and how that rate is used to convert an asset amount into msats.
The high-level goal of this change is to represent asset-to-BTC conversion rates using the new fixed-point number type. This PR updates several "price" related fields that were previously typed as milli-satoshis, which were used as a workaround in the Proof of Concept (PoC).
It is part of the ongoing effort to resolve: #871.
The ultimate goal is to align tapd with these BLIP changes: Roasbeef/blips#4
Note to Reviewers
This is the first phase of the fixed-point refactor, targeting the
approved-rfq-fixedpoint
branch. This branch will accumulate all approved changes required to close #871 before being merged intomain
.There are several
TODO(ffranr): Temp solution.
comments in this PR, which will be fully addressed in subsequent PRs before mergingapproved-rfq-fixedpoint
intomain
.Please review the commits in sequence. Note that some commit diffs may overlap slightly.
This change is