Skip to content

Commit ce6a485

Browse files
GeorgeTsagkguggero
authored andcommitted
rpcserver: calculate and validate invoice with sats amount
The previously ignored value/value_msat fields are now properly being accounted for. We now accept only one value related field to be set.
1 parent fce1fce commit ce6a485

File tree

1 file changed

+126
-11
lines changed

1 file changed

+126
-11
lines changed

rpcserver.go

Lines changed: 126 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7746,11 +7746,24 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
77467746
time.Duration(expirySeconds) * time.Second,
77477747
)
77487748

7749+
// We now want to calculate the upper bound of the RFQ order, which
7750+
// either is the asset amount specified by the user, or the converted
7751+
// satoshi amount of the invoice, expressed in asset units, using the
7752+
// local price oracle's conversion rate.
7753+
maxUnits, err := calculateAssetMaxAmount(
7754+
ctx, r.cfg.PriceOracle, specifier, req.AssetAmount, iReq,
7755+
r.cfg.RfqManager.GetPriceDeviationPpm(),
7756+
)
7757+
if err != nil {
7758+
return nil, fmt.Errorf("error calculating asset max "+
7759+
"amount: %w", err)
7760+
}
7761+
77497762
rpcSpecifier := marshalAssetSpecifier(specifier)
77507763

77517764
resp, err := r.AddAssetBuyOrder(ctx, &rfqrpc.AddAssetBuyOrderRequest{
77527765
AssetSpecifier: &rpcSpecifier,
7753-
AssetMaxAmt: req.AssetAmount,
7766+
AssetMaxAmt: maxUnits,
77547767
Expiry: uint64(expiryTimestamp.Unix()),
77557768
PeerPubKey: peerPubKey[:],
77567769
TimeoutSeconds: uint32(
@@ -7784,7 +7797,7 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
77847797
// Satoshi that we need to pay. We can now update the invoice with this
77857798
// amount.
77867799
invoiceAmtMsat, err := validateInvoiceAmount(
7787-
acceptedQuote, req.AssetAmount,
7800+
acceptedQuote, req.AssetAmount, iReq,
77887801
)
77897802
if err != nil {
77907803
return nil, fmt.Errorf("error validating invoice amount: %w",
@@ -7893,10 +7906,78 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
78937906
}, nil
78947907
}
78957908

7909+
// calculateAssetMaxAmount calculates the max units to be placed in the invoice
7910+
// RFQ quote order. When adding invoices based on asset units, that value is
7911+
// directly returned. If using the value/value_msat fields of the invoice then
7912+
// a price oracle query will take place to calculate the max units of the quote.
7913+
func calculateAssetMaxAmount(ctx context.Context, priceOracle rfq.PriceOracle,
7914+
specifier asset.Specifier, requestAssetAmount uint64,
7915+
inv *lnrpc.Invoice, deviationPPM uint64) (uint64, error) {
7916+
7917+
// Let's unmarshall the satoshi related fields to see if an amount was
7918+
// set based on those.
7919+
amtMsat, err := lnrpc.UnmarshallAmt(inv.Value, inv.ValueMsat)
7920+
if err != nil {
7921+
return 0, err
7922+
}
7923+
7924+
// Let's make sure that only one type of amount is set, in order to
7925+
// avoid ambiguous behavior. This field dictates the actual value of the
7926+
// invoice so let's be strict and only allow one possible value to be
7927+
// set.
7928+
if requestAssetAmount > 0 && amtMsat != 0 {
7929+
return 0, fmt.Errorf("cannot set both asset amount and sats " +
7930+
"amount")
7931+
}
7932+
7933+
// If the invoice is being added based on asset units, there's nothing
7934+
// to do so return the amount directly.
7935+
if amtMsat == 0 {
7936+
return requestAssetAmount, nil
7937+
}
7938+
7939+
// If the invoice defines the desired amount in satoshis, we need to
7940+
// query our oracle first to get an estimation on the asset rate. This
7941+
// will help us establish a quote with the correct amount of asset
7942+
// units.
7943+
maxUnits, err := rfq.EstimateAssetUnits(
7944+
ctx, priceOracle, specifier, amtMsat,
7945+
)
7946+
if err != nil {
7947+
return 0, err
7948+
}
7949+
7950+
maxMathUnits := rfqmath.NewBigIntFromUint64(maxUnits)
7951+
7952+
// Since we used a different oracle price query above calculate the max
7953+
// amount of units, we want to add some breathing room to account for
7954+
// price fluctuations caused by the small-time delay, plus the fact that
7955+
// the agreed upon quote may be different. If we don't add this safety
7956+
// window the peer may allow a routable amount that evaluates to less
7957+
// than what we ask for.
7958+
// Apply the tolerance margin twice. Once due to the ask/bid price
7959+
// deviation that may occur during rfq negotiation, and once for the
7960+
// price movement that may occur between querying the oracle and
7961+
// acquiring the quote. We don't really care about this margin being too
7962+
// big, this only affects the max units our peer agrees to route.
7963+
tolerance := rfqmath.NewBigIntFromUint64(deviationPPM)
7964+
7965+
maxMathUnits = rfqmath.AddTolerance(maxMathUnits, tolerance)
7966+
maxMathUnits = rfqmath.AddTolerance(maxMathUnits, tolerance)
7967+
7968+
return maxMathUnits.ToUint64(), nil
7969+
}
7970+
78967971
// validateInvoiceAmount validates the quote against the invoice we're trying to
78977972
// add. It returns the value in msat that should be included in the invoice.
78987973
func validateInvoiceAmount(acceptedQuote *rfqrpc.PeerAcceptedBuyQuote,
7899-
requestAssetAmount uint64) (lnwire.MilliSatoshi, error) {
7974+
requestAssetAmount uint64, inv *lnrpc.Invoice) (lnwire.MilliSatoshi,
7975+
error) {
7976+
7977+
invoiceAmtMsat, err := lnrpc.UnmarshallAmt(inv.Value, inv.ValueMsat)
7978+
if err != nil {
7979+
return 0, err
7980+
}
79007981

79017982
// Now that we have the accepted quote, we know the amount in Satoshi
79027983
// that we need to pay. We can now update the invoice with this amount.
@@ -7910,22 +7991,56 @@ func validateInvoiceAmount(acceptedQuote *rfqrpc.PeerAcceptedBuyQuote,
79107991
err)
79117992
}
79127993

7913-
// Convert the asset amount into a fixed-point.
7914-
assetAmount := rfqmath.NewBigIntFixedPoint(requestAssetAmount, 0)
7915-
7916-
// Calculate the invoice amount in msat.
7917-
newInvoiceAmtMsat := rfqmath.UnitsToMilliSatoshi(
7918-
assetAmount, *askAssetRate,
7994+
// We either have a requested amount in milli satoshi that we want to
7995+
// validate against the quote's max amount (in which case we overwrite
7996+
// the invoiceUnits), or we have a requested amount in asset units that
7997+
// we want to convert into milli satoshis (and overwrite
7998+
// newInvoiceAmtMsat).
7999+
var (
8000+
newInvoiceAmtMsat = invoiceAmtMsat
8001+
invoiceUnits = requestAssetAmount
79198002
)
8003+
switch {
8004+
case invoiceAmtMsat != 0:
8005+
// If the invoice was created with a satoshi amount, we need to
8006+
// calculate the units.
8007+
invoiceUnits = rfqmath.MilliSatoshiToUnits(
8008+
invoiceAmtMsat, *askAssetRate,
8009+
).ScaleTo(0).ToUint64()
8010+
8011+
// Now let's see if the negotiated quote can actually route the
8012+
// amount we need in msat.
8013+
maxFixedUnits := rfqmath.NewBigIntFixedPoint(
8014+
acceptedQuote.AssetMaxAmount, 0,
8015+
)
8016+
maxRoutableMsat := rfqmath.UnitsToMilliSatoshi(
8017+
maxFixedUnits, *askAssetRate,
8018+
)
8019+
8020+
if maxRoutableMsat <= invoiceAmtMsat {
8021+
return 0, fmt.Errorf("cannot create invoice for %v "+
8022+
"msat, max routable amount is %v msat",
8023+
invoiceAmtMsat, maxRoutableMsat)
8024+
}
8025+
8026+
default:
8027+
// Convert the asset amount into a fixed-point.
8028+
assetAmount := rfqmath.NewBigIntFixedPoint(invoiceUnits, 0)
8029+
8030+
// Calculate the invoice amount in msat.
8031+
newInvoiceAmtMsat = rfqmath.UnitsToMilliSatoshi(
8032+
assetAmount, *askAssetRate,
8033+
)
8034+
}
79208035

79218036
// If the invoice is for an asset unit amount smaller than the minimal
79228037
// transportable amount, we'll return an error, as it wouldn't be
79238038
// payable by the network.
7924-
if acceptedQuote.MinTransportableUnits > requestAssetAmount {
8039+
if acceptedQuote.MinTransportableUnits > invoiceUnits {
79258040
return 0, fmt.Errorf("cannot create invoice for %d asset "+
79268041
"units, as the minimal transportable amount is %d "+
79278042
"units with the current rate of %v units/BTC",
7928-
requestAssetAmount, acceptedQuote.MinTransportableUnits,
8043+
invoiceUnits, acceptedQuote.MinTransportableUnits,
79298044
acceptedQuote.AskAssetRate)
79308045
}
79318046

0 commit comments

Comments
 (0)