6
6
"encoding/json"
7
7
"errors"
8
8
"fmt"
9
+ "sort"
10
+ "strings"
9
11
"sync"
10
12
"time"
11
13
@@ -17,10 +19,12 @@ import (
17
19
"github.com/lightninglabs/taproot-assets/fn"
18
20
"github.com/lightninglabs/taproot-assets/rfqmath"
19
21
"github.com/lightninglabs/taproot-assets/rfqmsg"
20
- lfn "github.com/lightningnetwork/lnd/fn/v2"
22
+ "github.com/lightninglabs/taproot-assets/taprpc/rfqrpc"
23
+ "github.com/lightningnetwork/lnd/lnrpc"
21
24
"github.com/lightningnetwork/lnd/lnutils"
22
25
"github.com/lightningnetwork/lnd/lnwire"
23
26
"github.com/lightningnetwork/lnd/routing/route"
27
+ "github.com/lightningnetwork/lnd/zpay32"
24
28
)
25
29
26
30
const (
@@ -41,7 +45,9 @@ const (
41
45
type ChannelLister interface {
42
46
// ListChannels returns a list of channels that are available for
43
47
// routing.
44
- ListChannels (ctx context.Context ) ([]lndclient.ChannelInfo , error )
48
+ ListChannels (ctx context.Context , activeOnly , publicOnly bool ,
49
+ _ ... lndclient.ListChannelsOption ) ([]lndclient.ChannelInfo ,
50
+ error )
45
51
}
46
52
47
53
// ScidAliasManager is an interface that can add short channel ID (SCID) aliases
@@ -546,63 +552,38 @@ func (m *Manager) handleOutgoingMessage(outgoingMsg rfqmsg.OutgoingMsg) error {
546
552
func (m * Manager ) addScidAlias (scidAlias uint64 , assetSpecifier asset.Specifier ,
547
553
peer route.Vertex ) error {
548
554
549
- // Retrieve all local channels.
550
555
ctxb := context .Background ()
551
- localChans , err := m .cfg .ChannelLister .ListChannels (ctxb )
552
- if err != nil {
553
- // Not being able to call lnd to add the alias is a critical
554
- // error, which warrants shutting down, as something is wrong.
555
- return fn .NewCriticalError (
556
- fmt .Errorf ("add alias: error listing local channels: " +
557
- "%w" , err ),
558
- )
559
- }
560
-
561
- // Filter for channels with the given peer.
562
- peerChannels := lfn .Filter (
563
- localChans , func (c lndclient.ChannelInfo ) bool {
564
- return c .PubKeyBytes == peer
565
- },
556
+ peerChans , err := m .FetchChannel (
557
+ ctxb , assetSpecifier , & peer , NoIntention ,
566
558
)
559
+ if err != nil && ! strings .Contains (
560
+ err .Error (), "no asset channel balance found for" ,
561
+ ) {
567
562
568
- var baseSCID uint64
569
- for _ , localChan := range peerChannels {
570
- if len (localChan .CustomChannelData ) == 0 {
571
- continue
572
- }
573
-
574
- var assetData rfqmsg.JsonAssetChannel
575
- err = json .Unmarshal (localChan .CustomChannelData , & assetData )
576
- if err != nil {
577
- log .Warnf ("Unable to unmarshal channel asset data: %v" ,
578
- err )
579
- continue
580
- }
563
+ return err
564
+ }
581
565
582
- match , err := m .ChannelMatchesFully (
583
- ctxb , assetData , assetSpecifier ,
566
+ // As a fallback, if we didn't find any compatible channels with the
567
+ // peer, let's pick any channel that is available with this peer. This
568
+ // is okay, because non-strict forwarding will ask each channel if the
569
+ // bandwidth matches the provided specifier.
570
+ if len (peerChans [peer ]) == 0 {
571
+ peerChans , err = m .FetchChannel (
572
+ ctxb , asset.Specifier {}, & peer , NoIntention ,
584
573
)
585
574
if err != nil {
586
575
return err
587
576
}
588
-
589
- // TODO(george): Instead of returning the first result,
590
- // try to pick the best channel for what we're trying to
591
- // do (receive/send). Binding a baseSCID means we're
592
- // also binding the asset liquidity on that channel.
593
- if match {
594
- baseSCID = localChan .ChannelID
595
- break
596
- }
597
577
}
598
578
599
- // As a fallback, if the base SCID is not found and there's only one
600
- // channel with the target peer, assume that the base SCID corresponds
601
- // to that channel.
602
- if baseSCID == 0 && len (peerChannels ) == 1 {
603
- baseSCID = peerChannels [0 ].ChannelID
579
+ if len (peerChans [peer ]) == 0 {
580
+ return fmt .Errorf ("cannot add scid alias with peer=%v, no " +
581
+ "compatible channels found for %s" , peer ,
582
+ & assetSpecifier )
604
583
}
605
584
585
+ baseSCID := peerChans [peer ][0 ].ChannelInfo .ChannelID
586
+
606
587
// At this point, if the base SCID is still not found, we return an
607
588
// error. We can't map the SCID alias to a base SCID.
608
589
if baseSCID == 0 {
@@ -1052,6 +1033,250 @@ func (m *Manager) ChannelMatchesFully(ctx context.Context,
1052
1033
return true , nil
1053
1034
}
1054
1035
1036
+ // TapChannel is a helper struct that combines the information of an asset
1037
+ // specifier that is satisfied by a channel with the channels' general
1038
+ // information.
1039
+ type TapChannel struct {
1040
+ // Specifier is the asset Specifier that is satisfied by this channels'
1041
+ // assets.
1042
+ Specifier asset.Specifier
1043
+
1044
+ // ChannelInfo is the information about the channel the asset is
1045
+ // committed to.
1046
+ ChannelInfo lndclient.ChannelInfo
1047
+
1048
+ // AssetInfo contains the asset related info of the channel.
1049
+ AssetInfo rfqmsg.JsonAssetChannel
1050
+ }
1051
+
1052
+ // PeerChanMap is a structure that maps peers to channels. This is used for
1053
+ // filtering asset channels against an asset specifier.
1054
+ type PeerChanMap map [route.Vertex ][]TapChannel
1055
+
1056
+ // ComputeChannelAssetBalance computes the total local and remote balance for
1057
+ // each asset channel that matches the provided asset specifier.
1058
+ func (m * Manager ) ComputeChannelAssetBalance (ctx context.Context ,
1059
+ activeChannels []lndclient.ChannelInfo ,
1060
+ specifier asset.Specifier ) (PeerChanMap , bool , error ) {
1061
+
1062
+ var (
1063
+ peerChanMap = make (PeerChanMap )
1064
+ haveGroupedAssetChannels bool
1065
+ )
1066
+ for chanIdx := range activeChannels {
1067
+ var (
1068
+ pass bool
1069
+ assetData rfqmsg.JsonAssetChannel
1070
+ )
1071
+
1072
+ openChan := activeChannels [chanIdx ]
1073
+
1074
+ // If there specifier is empty, we skip all asset balance
1075
+ // related checks.
1076
+ if specifier .IsSome () {
1077
+ if len (openChan .CustomChannelData ) == 0 {
1078
+ continue
1079
+ }
1080
+
1081
+ err := json .Unmarshal (
1082
+ openChan .CustomChannelData , & assetData ,
1083
+ )
1084
+ if err != nil {
1085
+ return nil , false , fmt .Errorf ("unable to " +
1086
+ "unmarshal asset data: %w" , err )
1087
+ }
1088
+
1089
+ if len (assetData .GroupKey ) > 0 {
1090
+ haveGroupedAssetChannels = true
1091
+ }
1092
+
1093
+ // Check if the assets of this channel match the
1094
+ // provided specifier.
1095
+ pass , err = m .ChannelMatchesFully (
1096
+ ctx , assetData , specifier ,
1097
+ )
1098
+ if err != nil {
1099
+ return nil , false , err
1100
+ }
1101
+ }
1102
+
1103
+ // We also append the channel in the case where the specifier is
1104
+ // empty. This means that the caller doesn't really care about
1105
+ // the type of balance.
1106
+ if pass || ! specifier .IsSome () {
1107
+ peerChanMap [openChan .PubKeyBytes ] = append (
1108
+ peerChanMap [openChan .PubKeyBytes ],
1109
+ TapChannel {
1110
+ Specifier : specifier ,
1111
+ ChannelInfo : openChan ,
1112
+ AssetInfo : assetData ,
1113
+ },
1114
+ )
1115
+ }
1116
+ }
1117
+
1118
+ return peerChanMap , haveGroupedAssetChannels , nil
1119
+ }
1120
+
1121
+ // ChanIntention defines the intention of calling rfqChannel. This helps with
1122
+ // returning the channel that is most suitable for what we want to do.
1123
+ type ChanIntention uint8
1124
+
1125
+ const (
1126
+ // NoIntention defines the absence of any intention, signalling that we
1127
+ // don't really care which channel is returned.
1128
+ NoIntention ChanIntention = iota
1129
+
1130
+ // SendIntention defines the intention to send over an asset channel.
1131
+ SendIntention
1132
+
1133
+ // ReceiveIntention defines the intention to receive over an asset
1134
+ // channel.
1135
+ ReceiveIntention
1136
+ )
1137
+
1138
+ // FetchChannel returns the channel to use for RFQ operations. It returns a map
1139
+ // of peers and their eligible channels. If a peerPubKey is specified then the
1140
+ // map will only contain one entry for that peer.
1141
+ func (m * Manager ) FetchChannel (ctx context.Context , specifier asset.Specifier ,
1142
+ peerPubKey * route.Vertex ,
1143
+ intention ChanIntention ) (PeerChanMap , error ) {
1144
+
1145
+ activeChannels , err := m .cfg .ChannelLister .ListChannels (
1146
+ ctx , true , false ,
1147
+ )
1148
+ if err != nil {
1149
+ return nil , err
1150
+ }
1151
+
1152
+ balancesMap , haveGroupChans , err := m .ComputeChannelAssetBalance (
1153
+ ctx , activeChannels , specifier ,
1154
+ )
1155
+ if err != nil {
1156
+ return nil , fmt .Errorf ("error computing available asset " +
1157
+ "channel balance: %w" , err )
1158
+ }
1159
+
1160
+ // If the user uses the asset ID to specify what asset to use, that will
1161
+ // not work for asset channels that have multiple UTXOs of grouped
1162
+ // assets. The result wouldn't be deterministic anyway (meaning, it's
1163
+ // not guaranteed that a specific asset ID is moved within a channel
1164
+ // when an HTLC is sent, the allocation logic decides which actual UTXO
1165
+ // is used). So we tell the user to use the group key instead, at least
1166
+ // for channels that have multiple UTXOs of grouped assets.
1167
+ if specifier .HasId () && len (balancesMap ) == 0 && haveGroupChans {
1168
+ return nil , fmt .Errorf ("no compatible asset channel found for " +
1169
+ "%s, make sure to use group key for grouped asset " +
1170
+ "channels" , & specifier )
1171
+ }
1172
+
1173
+ if len (balancesMap ) == 0 {
1174
+ return nil , fmt .Errorf ("no asset channel balance found for %s" ,
1175
+ & specifier )
1176
+ }
1177
+
1178
+ switch intention {
1179
+ case SendIntention :
1180
+ // When sending we care about the volume of our local balances,
1181
+ // so we sort by local balances in descending order.
1182
+ for k , v := range balancesMap {
1183
+ sort .Slice (v , func (i , j int ) bool {
1184
+ return v [i ].AssetInfo .LocalBalance >
1185
+ v [j ].AssetInfo .LocalBalance
1186
+ })
1187
+
1188
+ balancesMap [k ] = v
1189
+ }
1190
+ case ReceiveIntention :
1191
+ // When sending we care about the volume of the remote balances,
1192
+ // so we sort by remote balances in descending order.
1193
+ for k , v := range balancesMap {
1194
+ sort .Slice (v , func (i , j int ) bool {
1195
+ return v [i ].AssetInfo .RemoteBalance >
1196
+ v [j ].AssetInfo .RemoteBalance
1197
+ })
1198
+
1199
+ balancesMap [k ] = v
1200
+ }
1201
+ case NoIntention :
1202
+ // We don't care about sending or receiving, this means that
1203
+ // the method was called as a dry check. Do nothing.
1204
+ }
1205
+
1206
+ // If a peer public key was specified, we always want to use that to
1207
+ // filter the asset channels.
1208
+ if peerPubKey != nil {
1209
+ _ , ok := balancesMap [* peerPubKey ]
1210
+ if ! ok {
1211
+ return nil , fmt .Errorf ("no asset channels found for " +
1212
+ "%s and peer=%s" , & specifier , peerPubKey )
1213
+ }
1214
+
1215
+ filteredRes := make (PeerChanMap )
1216
+ filteredRes [* peerPubKey ] = balancesMap [* peerPubKey ]
1217
+
1218
+ balancesMap = filteredRes
1219
+ }
1220
+
1221
+ return balancesMap , nil
1222
+ }
1223
+
1224
+ // InboundPolicyFetcher is a helper that fetches the inbound policy of a channel
1225
+ // based on its chanID.
1226
+ type InboundPolicyFetcher func (ctx context.Context , chanID uint64 ,
1227
+ remotePubStr string ) (* lnrpc.RoutingPolicy , error )
1228
+
1229
+ // RfqToHopHint creates the hop hint representation which encapsulates certain
1230
+ // quote information along with some other data required by the payment to
1231
+ // succeed.
1232
+ // Depending on whether the hold flag is set, we either return the lnrpc version
1233
+ // of a hop hint or the zpay32 version. This is because we use the lndclient
1234
+ // wrapper for hold invoices while we use the raw lnrpc endpoint for simple
1235
+ // invoices.
1236
+ func (m * Manager ) RfqToHopHint (ctx context.Context ,
1237
+ policyFetcher InboundPolicyFetcher , channelID uint64 ,
1238
+ peerPubKey route.Vertex , quote * rfqrpc.PeerAcceptedBuyQuote ,
1239
+ hold bool ) (* zpay32.HopHint , error ) {
1240
+
1241
+ inboundPolicy , err := policyFetcher (ctx , channelID , peerPubKey .String ())
1242
+ if err != nil {
1243
+ return nil , fmt .Errorf ("unable to get inbound channel " +
1244
+ "policy for channel with ID %d: %w" , channelID , err )
1245
+ }
1246
+
1247
+ peerPub , err := btcec .ParsePubKey (peerPubKey [:])
1248
+ if err != nil {
1249
+ return nil , fmt .Errorf ("error parsing peer " +
1250
+ "pubkey: %w" , err )
1251
+ }
1252
+ hopHint := & zpay32.HopHint {
1253
+ NodeID : peerPub ,
1254
+ ChannelID : quote .Scid ,
1255
+ FeeBaseMSat : uint32 (inboundPolicy .FeeBaseMsat ),
1256
+ FeeProportionalMillionths : uint32 (
1257
+ inboundPolicy .FeeRateMilliMsat ,
1258
+ ),
1259
+ CLTVExpiryDelta : uint16 (
1260
+ inboundPolicy .TimeLockDelta ,
1261
+ ),
1262
+ }
1263
+
1264
+ return hopHint , nil
1265
+ }
1266
+
1267
+ // Zpay32HopHintToLnrpc converts a zpay32 hop hint to the lnrpc representation.
1268
+ func Zpay32HopHintToLnrpc (h * zpay32.HopHint ) * lnrpc.HopHint {
1269
+ return & lnrpc.HopHint {
1270
+ NodeId : fmt .Sprintf (
1271
+ "%x" , h .NodeID .SerializeCompressed (),
1272
+ ),
1273
+ ChanId : h .ChannelID ,
1274
+ FeeBaseMsat : h .FeeBaseMSat ,
1275
+ FeeProportionalMillionths : h .FeeProportionalMillionths ,
1276
+ CltvExpiryDelta : uint32 (h .CLTVExpiryDelta ),
1277
+ }
1278
+ }
1279
+
1055
1280
// publishSubscriberEvent publishes an event to all subscribers.
1056
1281
func (m * Manager ) publishSubscriberEvent (event fn.Event ) {
1057
1282
// Iterate over the subscribers and deliver the event to each one.
0 commit comments