Skip to content

Commit ba9afdb

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 af9c9bd commit ba9afdb

File tree

6 files changed

+830
-88
lines changed

6 files changed

+830
-88
lines changed

itest/assets_test.go

Lines changed: 155 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ func createTestAssetNetwork(t *harnessTest, net *NetworkHarness, charlieTap,
6161
daveTap, erinTap, fabiaTap, yaraTap, universeTap *tapClient,
6262
mintedAsset *taprpc.Asset, assetSendAmount, charlieFundingAmount,
6363
daveFundingAmount,
64-
erinFundingAmount uint64, pushSat int64) (*tchrpc.FundChannelResponse,
65-
*tchrpc.FundChannelResponse, *tchrpc.FundChannelResponse) {
64+
erinFundingAmount uint64, pushSat int64) (*lnrpc.ChannelPoint,
65+
*lnrpc.ChannelPoint, *lnrpc.ChannelPoint) {
6666

6767
ctxb := context.Background()
6868
assetID := mintedAsset.AssetGenesis.AssetId
@@ -256,7 +256,26 @@ func createTestAssetNetwork(t *harnessTest, net *NetworkHarness, charlieTap,
256256
t.t, erinTap.node, fabiaTap.node, erinFundingAmount, assetID,
257257
)
258258

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

262281
func assertNumAssetUTXOs(t *testing.T, tapdClient *tapClient,
@@ -586,6 +605,67 @@ func getAssetChannelBalance(t *testing.T, node *HarnessNode, assetID []byte,
586605
balance.RemoteBalance.Sat
587606
}
588607

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

705-
return payInvoiceWithAssets(
785+
numUnits, _ := payInvoiceWithAssets(
706786
t, src, rfqPeer, invoiceResp, assetID, smallShards,
707787
)
788+
789+
return numUnits
708790
}
709791

710792
func payInvoiceWithSatoshi(t *testing.T, payer *HarnessNode,
@@ -730,7 +812,7 @@ func payInvoiceWithSatoshi(t *testing.T, payer *HarnessNode,
730812

731813
func payInvoiceWithAssets(t *testing.T, payer, rfqPeer *HarnessNode,
732814
invoice *lnrpc.AddInvoiceResponse, assetID []byte,
733-
smallShards bool) uint64 {
815+
smallShards bool) (uint64, rfqmath.BigIntFixedPoint) {
734816

735817
ctxb := context.Background()
736818
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
@@ -774,19 +856,21 @@ func payInvoiceWithAssets(t *testing.T, payer, rfqPeer *HarnessNode,
774856
rate, err := rfqrpc.UnmarshalFixedPoint(rpcRate)
775857
require.NoError(t, err)
776858

859+
t.Logf("Got quote for %v asset units per BTC", rate)
860+
777861
amountMsat := lnwire.MilliSatoshi(decodedInvoice.NumMsat)
778862
milliSatsFP := rfqmath.MilliSatoshiToUnits(amountMsat, *rate)
779863
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 "+
864+
msatPerUnit := float64(decodedInvoice.NumMsat) / float64(numUnits)
865+
t.Logf("Got quote for %v asset units at %3f msat/unit from peer %s "+
782866
"with SCID %d", numUnits, msatPerUnit, peerPubKey,
783867
acceptedQuote.Scid)
784868

785869
result, err := getAssetPaymentResult(stream)
786870
require.NoError(t, err)
787871
require.Equal(t, lnrpc.Payment_SUCCEEDED, result.Status)
788872

789-
return numUnits
873+
return numUnits, *rate
790874
}
791875

792876
func createAssetInvoice(t *testing.T, dstRfqPeer, dst *HarnessNode,
@@ -825,19 +909,70 @@ func createAssetInvoice(t *testing.T, dstRfqPeer, dst *HarnessNode,
825909
rate, err := rfqrpc.UnmarshalFixedPoint(rpcRate)
826910
require.NoError(t, err)
827911

912+
t.Logf("Got quote for %v asset units per BTC", rate)
913+
828914
assetUnits := rfqmath.NewBigIntFixedPoint(assetAmount, 0)
829915
numMSats := rfqmath.UnitsToMilliSatoshi(assetUnits, *rate)
830-
mSatPerUnit := uint64(decodedInvoice.NumMsat) / assetAmount
916+
mSatPerUnit := float64(decodedInvoice.NumMsat) / float64(assetAmount)
831917

832918
require.EqualValues(t, numMSats, decodedInvoice.NumMsat)
833919

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)
920+
t.Logf("Got quote for %d mSats at %3f msat/unit from peer %x with "+
921+
"SCID %d", decodedInvoice.NumMsat, mSatPerUnit,
922+
dstRfqPeer.PubKey[:], resp.AcceptedBuyQuote.Scid)
837923

838924
return resp.InvoiceResult
839925
}
840926

927+
// assertPaymentHtlcAssets makes sure the payment with the given hash shows the
928+
// individual HTLCs that arrived for it and that they show the correct asset
929+
// amounts for the given ID when decoded.
930+
func assertPaymentHtlcAssets(t *testing.T, node *HarnessNode, payHash []byte,
931+
assetID []byte, assetAmount uint64) {
932+
933+
ctxb := context.Background()
934+
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
935+
defer cancel()
936+
937+
stream, err := node.RouterClient.TrackPaymentV2(
938+
ctxt, &routerrpc.TrackPaymentRequest{
939+
PaymentHash: payHash,
940+
NoInflightUpdates: true,
941+
},
942+
)
943+
require.NoError(t, err)
944+
945+
payment, err := stream.Recv()
946+
require.NoError(t, err)
947+
require.NotNil(t, payment)
948+
require.NotEmpty(t, payment.Htlcs)
949+
950+
t.Logf("Asset payment: %v", toProtoJSON(t, payment))
951+
952+
targetID := hex.EncodeToString(assetID)
953+
954+
var totalAssetAmount uint64
955+
for _, htlc := range payment.Htlcs {
956+
require.NotNil(t, htlc.Route)
957+
require.NotEmpty(t, htlc.Route.CustomChannelData)
958+
959+
jsonHtlc := &rfqmsg.JsonHtlc{}
960+
err := json.Unmarshal(htlc.Route.CustomChannelData, jsonHtlc)
961+
require.NoError(t, err)
962+
963+
for _, balance := range jsonHtlc.Balances {
964+
if balance.AssetID != targetID {
965+
continue
966+
}
967+
968+
totalAssetAmount += balance.Amount
969+
}
970+
}
971+
972+
// Due to rounding we allow up to 1 unit of error.
973+
require.InDelta(t, assetAmount, totalAssetAmount, 1)
974+
}
975+
841976
func waitForSendEvent(t *testing.T,
842977
sendEvents taprpc.TaprootAssets_SubscribeSendEventsClient,
843978
expectedState tapfreighter.SendState) {
@@ -862,6 +997,14 @@ type coOpCloseBalanceCheck func(t *testing.T, local, remote *HarnessNode,
862997
closeTx *wire.MsgTx, closeUpdate *lnrpc.ChannelCloseUpdate,
863998
assetID, groupKey []byte, universeTap *tapClient)
864999

1000+
// noOpCoOpCloseBalanceCheck is a no-op implementation of the co-op close
1001+
// balance check that can be used in tests.
1002+
func noOpCoOpCloseBalanceCheck(_ *testing.T, _, _ *HarnessNode, _ *wire.MsgTx,
1003+
_ *lnrpc.ChannelCloseUpdate, _, _ []byte, _ *tapClient) {
1004+
1005+
// This is a no-op function.
1006+
}
1007+
8651008
// closeAssetChannelAndAssert closes the channel between the local and remote
8661009
// node and asserts the final balances of the closing transaction.
8671010
func closeAssetChannelAndAssert(t *harnessTest, net *NetworkHarness,

0 commit comments

Comments
 (0)