Skip to content

Commit 28c99fa

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 28c99fa

File tree

6 files changed

+828
-88
lines changed

6 files changed

+828
-88
lines changed

itest/assets_test.go

Lines changed: 153 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,65 @@ 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+
func routingFee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {
664+
return amt + (amt / 1000_000) + 1000
665+
}
666+
589667
func sendAssetKeySendPayment(t *testing.T, src, dst *HarnessNode, amt uint64,
590668
assetID []byte, btcAmt fn.Option[int64],
591669
expectedStatus lnrpc.Payment_PaymentStatus,
@@ -702,9 +780,11 @@ func createAndPayNormalInvoice(t *testing.T, src, rfqPeer, dst *HarnessNode,
702780
})
703781
require.NoError(t, err)
704782

705-
return payInvoiceWithAssets(
783+
numUnits, _ := payInvoiceWithAssets(
706784
t, src, rfqPeer, invoiceResp, assetID, smallShards,
707785
)
786+
787+
return numUnits
708788
}
709789

710790
func payInvoiceWithSatoshi(t *testing.T, payer *HarnessNode,
@@ -730,7 +810,7 @@ func payInvoiceWithSatoshi(t *testing.T, payer *HarnessNode,
730810

731811
func payInvoiceWithAssets(t *testing.T, payer, rfqPeer *HarnessNode,
732812
invoice *lnrpc.AddInvoiceResponse, assetID []byte,
733-
smallShards bool) uint64 {
813+
smallShards bool) (uint64, rfqmath.BigIntFixedPoint) {
734814

735815
ctxb := context.Background()
736816
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
@@ -774,19 +854,21 @@ func payInvoiceWithAssets(t *testing.T, payer, rfqPeer *HarnessNode,
774854
rate, err := rfqrpc.UnmarshalFixedPoint(rpcRate)
775855
require.NoError(t, err)
776856

857+
t.Logf("Got quote for %v asset units per BTC", rate)
858+
777859
amountMsat := lnwire.MilliSatoshi(decodedInvoice.NumMsat)
778860
milliSatsFP := rfqmath.MilliSatoshiToUnits(amountMsat, *rate)
779861
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 "+
862+
msatPerUnit := float64(decodedInvoice.NumMsat) / float64(numUnits)
863+
t.Logf("Got quote for %v asset units at %3f msat/unit from peer %s "+
782864
"with SCID %d", numUnits, msatPerUnit, peerPubKey,
783865
acceptedQuote.Scid)
784866

785867
result, err := getAssetPaymentResult(stream)
786868
require.NoError(t, err)
787869
require.Equal(t, lnrpc.Payment_SUCCEEDED, result.Status)
788870

789-
return numUnits
871+
return numUnits, *rate
790872
}
791873

792874
func createAssetInvoice(t *testing.T, dstRfqPeer, dst *HarnessNode,
@@ -825,19 +907,70 @@ func createAssetInvoice(t *testing.T, dstRfqPeer, dst *HarnessNode,
825907
rate, err := rfqrpc.UnmarshalFixedPoint(rpcRate)
826908
require.NoError(t, err)
827909

910+
t.Logf("Got quote for %v asset units per BTC", rate)
911+
828912
assetUnits := rfqmath.NewBigIntFixedPoint(assetAmount, 0)
829913
numMSats := rfqmath.UnitsToMilliSatoshi(assetUnits, *rate)
830-
mSatPerUnit := uint64(decodedInvoice.NumMsat) / assetAmount
914+
mSatPerUnit := float64(decodedInvoice.NumMsat) / float64(assetAmount)
831915

832916
require.EqualValues(t, numMSats, decodedInvoice.NumMsat)
833917

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

838922
return resp.InvoiceResult
839923
}
840924

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

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

0 commit comments

Comments
 (0)