@@ -2,6 +2,7 @@ package rfq
2
2
3
3
import (
4
4
"context"
5
+ "encoding/hex"
5
6
"encoding/json"
6
7
"fmt"
7
8
"sync"
@@ -61,6 +62,14 @@ type (
61
62
SellAcceptMap map [SerialisedScid ]rfqmsg.SellAccept
62
63
)
63
64
65
+ // GroupLookup is an interface that helps us look up a group of an asset based
66
+ // on the asset ID.
67
+ type GroupLookup interface {
68
+ // QueryAssetGroup fetches the group information of an asset, if it
69
+ // belongs in a group.
70
+ QueryAssetGroup (context.Context , asset.ID ) (* asset.AssetGroup , error )
71
+ }
72
+
64
73
// ManagerCfg is a struct that holds the configuration parameters for the RFQ
65
74
// manager.
66
75
type ManagerCfg struct {
@@ -84,6 +93,10 @@ type ManagerCfg struct {
84
93
// determine the available channels for routing.
85
94
ChannelLister ChannelLister
86
95
96
+ // GroupLookup is an interface that helps us querry asset groups by
97
+ // asset IDs.
98
+ GroupLookup GroupLookup
99
+
87
100
// AliasManager is the SCID alias manager. This component is injected
88
101
// into the manager once lnd and tapd are hooked together.
89
102
AliasManager ScidAliasManager
@@ -165,6 +178,12 @@ type Manager struct {
165
178
SerialisedScid , rfqmsg.SellAccept ,
166
179
]
167
180
181
+ // groupKeyLookupCache is a map that helps us quickly perform an
182
+ // in-memory look up of the group an asset belongs to. Since this
183
+ // information is static and generated during minting, it is not
184
+ // possible for an asset to change groups.
185
+ groupKeyLookupCache lnutils.SyncMap [asset.ID , * btcec.PublicKey ]
186
+
168
187
// subscribers is a map of components that want to be notified on new
169
188
// events, keyed by their subscription ID.
170
189
subscribers lnutils.SyncMap [uint64 , * fn.EventReceiver [fn.Event ]]
@@ -539,18 +558,7 @@ func (m *Manager) addScidAlias(scidAlias uint64, assetSpecifier asset.Specifier,
539
558
return c .PubKeyBytes == peer
540
559
}, localChans )
541
560
542
- // Identify the correct channel to use as the base SCID for the alias
543
- // by inspecting the asset data in the custom channel data.
544
- assetID , err := assetSpecifier .UnwrapIdOrErr ()
545
- if err != nil {
546
- return fmt .Errorf ("asset ID must be specified when adding " +
547
- "alias: %w" , err )
548
- }
549
-
550
- var (
551
- assetIDStr = assetID .String ()
552
- baseSCID uint64
553
- )
561
+ var baseSCID uint64
554
562
for _ , localChan := range peerChannels {
555
563
if len (localChan .CustomChannelData ) == 0 {
556
564
continue
@@ -564,12 +572,20 @@ func (m *Manager) addScidAlias(scidAlias uint64, assetSpecifier asset.Specifier,
564
572
continue
565
573
}
566
574
567
- for _ , channelAsset := range assetData .Assets {
568
- gen := channelAsset .AssetInfo .AssetGenesis
569
- if gen .AssetID == assetIDStr {
570
- baseSCID = localChan .ChannelID
571
- break
572
- }
575
+ match , err := m .ChannelCompatible (
576
+ ctxb , assetData .Assets , assetSpecifier ,
577
+ )
578
+ if err != nil {
579
+ return err
580
+ }
581
+
582
+ // TODO(george): Instead of returning the first result,
583
+ // try to pick the best channel for what we're trying to
584
+ // do (receive/send). Binding a baseSCID means we're
585
+ // also binding the asset liquidity on that channel.
586
+ if match {
587
+ baseSCID = localChan .ChannelID
588
+ break
573
589
}
574
590
}
575
591
@@ -583,8 +599,8 @@ func (m *Manager) addScidAlias(scidAlias uint64, assetSpecifier asset.Specifier,
583
599
// At this point, if the base SCID is still not found, we return an
584
600
// error. We can't map the SCID alias to a base SCID.
585
601
if baseSCID == 0 {
586
- return fmt .Errorf ("add alias: base SCID not found for asset: " +
587
- "%v" , assetID )
602
+ return fmt .Errorf ("add alias: base SCID not found for %s" ,
603
+ & assetSpecifier )
588
604
}
589
605
590
606
log .Debugf ("Adding SCID alias %d for base SCID %d" , scidAlias , baseSCID )
@@ -917,6 +933,99 @@ func (m *Manager) RemoveSubscriber(
917
933
return nil
918
934
}
919
935
936
+ // getAssetGroupKey retrieves the group key of an asset based on its ID.
937
+ func (m * Manager ) getAssetGroupKey (ctx context.Context ,
938
+ id asset.ID ) (fn.Option [btcec.PublicKey ], error ) {
939
+
940
+ // First, see if we have already queried our DB for this ID.
941
+ v , ok := m .groupKeyLookupCache .Load (id )
942
+ if ok {
943
+ return fn .Some (* v ), nil
944
+ }
945
+
946
+ // Perform the DB query.
947
+ group , err := m .cfg .GroupLookup .QueryAssetGroup (ctx , id )
948
+ if err != nil {
949
+ return fn .None [btcec.PublicKey ](), err
950
+ }
951
+
952
+ // If the asset does not belong to a group, return early with no error
953
+ // or response.
954
+ if group == nil || group .GroupKey == nil {
955
+ return fn .None [btcec.PublicKey ](), nil
956
+ }
957
+
958
+ // Store the result for future calls.
959
+ m .groupKeyLookupCache .Store (id , & group .GroupPubKey )
960
+
961
+ return fn .Some (group .GroupPubKey ), nil
962
+ }
963
+
964
+ // AssetMatchesSpecifier checks if the provided asset satisfies the provided
965
+ // specifier. If the specifier includes a group key, we will check if the asset
966
+ // belongs to that group.
967
+ func (m * Manager ) AssetMatchesSpecifier (ctx context.Context ,
968
+ specifier asset.Specifier , id asset.ID ) (bool , error ) {
969
+
970
+ switch {
971
+ case specifier .HasGroupPubKey ():
972
+ group , err := m .getAssetGroupKey (ctx , id )
973
+ if err != nil {
974
+ return false , err
975
+ }
976
+
977
+ if group .IsNone () {
978
+ return false , nil
979
+ }
980
+
981
+ specifierGK := specifier .UnwrapGroupKeyToPtr ()
982
+
983
+ return group .UnwrapToPtr ().IsEqual (specifierGK ), nil
984
+
985
+ case specifier .HasId ():
986
+ specifierID := specifier .UnwrapIdToPtr ()
987
+
988
+ return * specifierID == id , nil
989
+
990
+ default :
991
+ return false , fmt .Errorf ("specifier is empty" )
992
+ }
993
+ }
994
+
995
+ // ChannelCompatible checks a channel's assets against an asset specifier. If
996
+ // the specifier is an asset ID, then all assets must be of that specific ID,
997
+ // if the specifier is a group key, then all assets in the channel must belong
998
+ // to that group.
999
+ func (m * Manager ) ChannelCompatible (ctx context.Context ,
1000
+ jsonAssets []rfqmsg.JsonAssetChanInfo , specifier asset.Specifier ) (bool ,
1001
+ error ) {
1002
+
1003
+ for _ , chanAsset := range jsonAssets {
1004
+ gen := chanAsset .AssetInfo .AssetGenesis
1005
+ assetIDBytes , err := hex .DecodeString (
1006
+ gen .AssetID ,
1007
+ )
1008
+ if err != nil {
1009
+ return false , fmt .Errorf ("error decoding asset ID: %w" ,
1010
+ err )
1011
+ }
1012
+
1013
+ var assetID asset.ID
1014
+ copy (assetID [:], assetIDBytes )
1015
+
1016
+ match , err := m .AssetMatchesSpecifier (ctx , specifier , assetID )
1017
+ if err != nil {
1018
+ return false , err
1019
+ }
1020
+
1021
+ if ! match {
1022
+ return false , err
1023
+ }
1024
+ }
1025
+
1026
+ return true , nil
1027
+ }
1028
+
920
1029
// publishSubscriberEvent publishes an event to all subscribers.
921
1030
func (m * Manager ) publishSubscriberEvent (event fn.Event ) {
922
1031
// Iterate over the subscribers and deliver the event to each one.
0 commit comments