Skip to content

Commit fc9d94e

Browse files
committed
itest: add RPC oracle with spread based asset rates
This commit adds a fully configurable RPC oracle that can serve asset rates that differ for buy and sell. We then add a test that shows and asserts proper asset conversion to satoshis and back.
1 parent d5262c4 commit fc9d94e

File tree

6 files changed

+862
-88
lines changed

6 files changed

+862
-88
lines changed

itest/assets_test.go

Lines changed: 187 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
"github.com/lightninglabs/taproot-assets/tapscript"
3434
"github.com/lightningnetwork/lnd/fn"
3535
"github.com/lightningnetwork/lnd/lnrpc"
36+
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
3637
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
3738
"github.com/lightningnetwork/lnd/lntest/rpc"
3839
"github.com/lightningnetwork/lnd/lntest/wait"
@@ -61,8 +62,8 @@ func createTestAssetNetwork(t *harnessTest, net *NetworkHarness, charlieTap,
6162
daveTap, erinTap, fabiaTap, yaraTap, universeTap *tapClient,
6263
mintedAsset *taprpc.Asset, assetSendAmount, charlieFundingAmount,
6364
daveFundingAmount,
64-
erinFundingAmount uint64, pushSat int64) (*tchrpc.FundChannelResponse,
65-
*tchrpc.FundChannelResponse, *tchrpc.FundChannelResponse) {
65+
erinFundingAmount uint64, pushSat int64) (*lnrpc.ChannelPoint,
66+
*lnrpc.ChannelPoint, *lnrpc.ChannelPoint) {
6667

6768
ctxb := context.Background()
6869
assetID := mintedAsset.AssetGenesis.AssetId
@@ -256,7 +257,26 @@ func createTestAssetNetwork(t *harnessTest, net *NetworkHarness, charlieTap,
256257
t.t, erinTap.node, fabiaTap.node, erinFundingAmount, assetID,
257258
)
258259

259-
return fundRespCD, fundRespDY, fundRespEF
260+
chanPointCD := &lnrpc.ChannelPoint{
261+
OutputIndex: uint32(fundRespCD.OutputIndex),
262+
FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{
263+
FundingTxidStr: fundRespCD.Txid,
264+
},
265+
}
266+
chanPointDY := &lnrpc.ChannelPoint{
267+
OutputIndex: uint32(fundRespDY.OutputIndex),
268+
FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{
269+
FundingTxidStr: fundRespDY.Txid,
270+
},
271+
}
272+
chanPointEF := &lnrpc.ChannelPoint{
273+
OutputIndex: uint32(fundRespEF.OutputIndex),
274+
FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{
275+
FundingTxidStr: fundRespEF.Txid,
276+
},
277+
}
278+
279+
return chanPointCD, chanPointDY, chanPointEF
260280
}
261281

262282
func assertNumAssetUTXOs(t *testing.T, tapdClient *tapClient,
@@ -586,6 +606,65 @@ func getAssetChannelBalance(t *testing.T, node *HarnessNode, assetID []byte,
586606
balance.RemoteBalance.Sat
587607
}
588608

609+
func fetchChannel(t *testing.T, node *HarnessNode,
610+
chanPoint *lnrpc.ChannelPoint) *lnrpc.Channel {
611+
612+
ctxb := context.Background()
613+
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
614+
defer cancel()
615+
616+
channelResp, err := node.ListChannels(ctxt, &lnrpc.ListChannelsRequest{
617+
ActiveOnly: true,
618+
})
619+
require.NoError(t, err)
620+
621+
chanFundingHash, err := lnrpc.GetChanPointFundingTxid(chanPoint)
622+
require.NoError(t, err)
623+
624+
chanPointStr := fmt.Sprintf("%v:%v", chanFundingHash,
625+
chanPoint.OutputIndex)
626+
627+
var targetChan *lnrpc.Channel
628+
for _, channel := range channelResp.Channels {
629+
if channel.ChannelPoint == chanPointStr {
630+
targetChan = channel
631+
632+
break
633+
}
634+
}
635+
require.NotNil(t, targetChan)
636+
637+
return targetChan
638+
}
639+
640+
func assertChannelSatBalance(t *testing.T, node *HarnessNode,
641+
chanPoint *lnrpc.ChannelPoint, local, remote int64) {
642+
643+
targetChan := fetchChannel(t, node, chanPoint)
644+
645+
require.InDelta(t, local, targetChan.LocalBalance, 1)
646+
require.InDelta(t, remote, targetChan.RemoteBalance, 1)
647+
}
648+
649+
func assertChannelAssetBalance(t *testing.T, node *HarnessNode,
650+
chanPoint *lnrpc.ChannelPoint, local, remote uint64) {
651+
652+
targetChan := fetchChannel(t, node, chanPoint)
653+
654+
var assetBalance rfqmsg.JsonAssetChannel
655+
err := json.Unmarshal(targetChan.CustomChannelData, &assetBalance)
656+
require.NoError(t, err)
657+
658+
require.Len(t, assetBalance.Assets, 1)
659+
660+
require.InDelta(t, local, assetBalance.Assets[0].LocalBalance, 1)
661+
require.InDelta(t, remote, assetBalance.Assets[0].RemoteBalance, 1)
662+
}
663+
664+
func routingFee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {
665+
return amt + (amt / 1000_000) + 1000
666+
}
667+
589668
func sendAssetKeySendPayment(t *testing.T, src, dst *HarnessNode, amt uint64,
590669
assetID []byte, btcAmt fn.Option[int64],
591670
expectedStatus lnrpc.Payment_PaymentStatus,
@@ -702,9 +781,11 @@ func createAndPayNormalInvoice(t *testing.T, src, rfqPeer, dst *HarnessNode,
702781
})
703782
require.NoError(t, err)
704783

705-
return payInvoiceWithAssets(
784+
numUnits, _ := payInvoiceWithAssets(
706785
t, src, rfqPeer, invoiceResp, assetID, smallShards,
707786
)
787+
788+
return numUnits
708789
}
709790

710791
func payInvoiceWithSatoshi(t *testing.T, payer *HarnessNode,
@@ -730,7 +811,7 @@ func payInvoiceWithSatoshi(t *testing.T, payer *HarnessNode,
730811

731812
func payInvoiceWithAssets(t *testing.T, payer, rfqPeer *HarnessNode,
732813
invoice *lnrpc.AddInvoiceResponse, assetID []byte,
733-
smallShards bool) uint64 {
814+
smallShards bool) (uint64, rfqmath.BigIntFixedPoint) {
734815

735816
ctxb := context.Background()
736817
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
@@ -774,19 +855,21 @@ func payInvoiceWithAssets(t *testing.T, payer, rfqPeer *HarnessNode,
774855
rate, err := rfqrpc.UnmarshalFixedPoint(rpcRate)
775856
require.NoError(t, err)
776857

858+
t.Logf("Got quote for %v asset units per BTC", rate)
859+
777860
amountMsat := lnwire.MilliSatoshi(decodedInvoice.NumMsat)
778861
milliSatsFP := rfqmath.MilliSatoshiToUnits(amountMsat, *rate)
779862
numUnits := milliSatsFP.ScaleTo(0).ToUint64()
780-
msatPerUnit := uint64(decodedInvoice.NumMsat) / numUnits
781-
t.Logf("Got quote for %v asset units at %v msat/unit from peer %s "+
863+
msatPerUnit := float64(decodedInvoice.NumMsat) / float64(numUnits)
864+
t.Logf("Got quote for %v asset units at %3f msat/unit from peer %s "+
782865
"with SCID %d", numUnits, msatPerUnit, peerPubKey,
783866
acceptedQuote.Scid)
784867

785868
result, err := getAssetPaymentResult(stream)
786869
require.NoError(t, err)
787870
require.Equal(t, lnrpc.Payment_SUCCEEDED, result.Status)
788871

789-
return numUnits
872+
return numUnits, *rate
790873
}
791874

792875
func createAssetInvoice(t *testing.T, dstRfqPeer, dst *HarnessNode,
@@ -825,19 +908,103 @@ func createAssetInvoice(t *testing.T, dstRfqPeer, dst *HarnessNode,
825908
rate, err := rfqrpc.UnmarshalFixedPoint(rpcRate)
826909
require.NoError(t, err)
827910

911+
t.Logf("Got quote for %v asset units per BTC", rate)
912+
828913
assetUnits := rfqmath.NewBigIntFixedPoint(assetAmount, 0)
829914
numMSats := rfqmath.UnitsToMilliSatoshi(assetUnits, *rate)
830-
mSatPerUnit := uint64(decodedInvoice.NumMsat) / assetAmount
915+
mSatPerUnit := float64(decodedInvoice.NumMsat) / float64(assetAmount)
831916

832917
require.EqualValues(t, numMSats, decodedInvoice.NumMsat)
833918

834-
t.Logf("Got quote for %d sats at %v msat/unit from peer %x with SCID "+
835-
"%d", decodedInvoice.NumMsat, mSatPerUnit, dstRfqPeer.PubKey[:],
836-
resp.AcceptedBuyQuote.Scid)
919+
t.Logf("Got quote for %d mSats at %3f msat/unit from peer %x with "+
920+
"SCID %d", decodedInvoice.NumMsat, mSatPerUnit,
921+
dstRfqPeer.PubKey[:], resp.AcceptedBuyQuote.Scid)
837922

838923
return resp.InvoiceResult
839924
}
840925

926+
// assertInvoiceHtlcAssets makes sure the invoice with the given hash shows the
927+
// individual HTLCs that arrived for it and that they show the correct asset
928+
// amounts when decoded.
929+
func assertInvoiceHtlcAssets(t *testing.T, node *HarnessNode,
930+
addedInvoice *lnrpc.AddInvoiceResponse, assetAmount uint64) {
931+
932+
ctxb := context.Background()
933+
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
934+
defer cancel()
935+
936+
invoice, err := node.InvoicesClient.LookupInvoiceV2(
937+
ctxt, &invoicesrpc.LookupInvoiceMsg{
938+
InvoiceRef: &invoicesrpc.LookupInvoiceMsg_PaymentAddr{
939+
PaymentAddr: addedInvoice.PaymentAddr,
940+
},
941+
},
942+
)
943+
require.NoError(t, err)
944+
require.NotEmpty(t, invoice.Htlcs)
945+
946+
t.Logf("Asset invoice: %v", toProtoJSON(t, invoice))
947+
948+
var totalAssetAmount uint64
949+
for _, htlc := range invoice.Htlcs {
950+
require.NotEmpty(t, htlc.CustomChannelData)
951+
952+
jsonHtlc := &rfqmsg.JsonHtlc{}
953+
err := json.Unmarshal(htlc.CustomChannelData, jsonHtlc)
954+
require.NoError(t, err)
955+
956+
for _, balance := range jsonHtlc.Balances {
957+
totalAssetAmount += balance.Amount
958+
}
959+
}
960+
961+
// Due to rounding we allow up to 1 unit of error.
962+
require.InDelta(t, assetAmount, totalAssetAmount, 1)
963+
}
964+
965+
// assertPaymentHtlcAssets makes sure the payment with the given hash shows the
966+
// individual HTLCs that arrived for it and that they show the correct asset
967+
// amounts when decoded.
968+
func assertPaymentHtlcAssets(t *testing.T, node *HarnessNode, payHash []byte,
969+
assetAmount uint64) {
970+
971+
ctxb := context.Background()
972+
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
973+
defer cancel()
974+
975+
stream, err := node.RouterClient.TrackPaymentV2(
976+
ctxt, &routerrpc.TrackPaymentRequest{
977+
PaymentHash: payHash,
978+
NoInflightUpdates: true,
979+
},
980+
)
981+
require.NoError(t, err)
982+
983+
payment, err := stream.Recv()
984+
require.NoError(t, err)
985+
require.NotNil(t, payment)
986+
require.NotEmpty(t, payment.Htlcs)
987+
988+
t.Logf("Asset payment: %v", toProtoJSON(t, payment))
989+
990+
var totalAssetAmount uint64
991+
for _, htlc := range payment.Htlcs {
992+
require.NotNil(t, htlc.Route)
993+
require.NotEmpty(t, htlc.Route.CustomChannelData)
994+
995+
jsonHtlc := &rfqmsg.JsonHtlc{}
996+
err := json.Unmarshal(htlc.Route.CustomChannelData, jsonHtlc)
997+
require.NoError(t, err)
998+
999+
for _, balance := range jsonHtlc.Balances {
1000+
totalAssetAmount += balance.Amount
1001+
}
1002+
}
1003+
1004+
// Due to rounding we allow up to 1 unit of error.
1005+
require.InDelta(t, assetAmount, totalAssetAmount, 1)
1006+
}
1007+
8411008
func waitForSendEvent(t *testing.T,
8421009
sendEvents taprpc.TaprootAssets_SubscribeSendEventsClient,
8431010
expectedState tapfreighter.SendState) {
@@ -862,6 +1029,14 @@ type coOpCloseBalanceCheck func(t *testing.T, local, remote *HarnessNode,
8621029
closeTx *wire.MsgTx, closeUpdate *lnrpc.ChannelCloseUpdate,
8631030
assetID, groupKey []byte, universeTap *tapClient)
8641031

1032+
// noOpCoOpCloseBalanceCheck is a no-op implementation of the co-op close
1033+
// balance check that can be used in tests.
1034+
func noOpCoOpCloseBalanceCheck(_ *testing.T, _, _ *HarnessNode, _ *wire.MsgTx,
1035+
_ *lnrpc.ChannelCloseUpdate, _, _ []byte, _ *tapClient) {
1036+
1037+
// This is a no-op function.
1038+
}
1039+
8651040
// closeAssetChannelAndAssert closes the channel between the local and remote
8661041
// node and asserts the final balances of the closing transaction.
8671042
func closeAssetChannelAndAssert(t *harnessTest, net *NetworkHarness,

0 commit comments

Comments
 (0)