@@ -2,6 +2,7 @@ package rfq
22
33import (
44 "context"
5+ "encoding/hex"
56 "encoding/json"
67 "fmt"
78 "sync"
@@ -61,6 +62,14 @@ type (
6162 SellAcceptMap map [SerialisedScid ]rfqmsg.SellAccept
6263)
6364
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+
6473// ManagerCfg is a struct that holds the configuration parameters for the RFQ
6574// manager.
6675type ManagerCfg struct {
@@ -84,6 +93,10 @@ type ManagerCfg struct {
8493 // determine the available channels for routing.
8594 ChannelLister ChannelLister
8695
96+ // GroupLookup is an interface that helps us querry asset groups by
97+ // asset IDs.
98+ GroupLookup GroupLookup
99+
87100 // AliasManager is the SCID alias manager. This component is injected
88101 // into the manager once lnd and tapd are hooked together.
89102 AliasManager ScidAliasManager
@@ -165,6 +178,12 @@ type Manager struct {
165178 SerialisedScid , rfqmsg.SellAccept ,
166179 ]
167180
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+
168187 // subscribers is a map of components that want to be notified on new
169188 // events, keyed by their subscription ID.
170189 subscribers lnutils.SyncMap [uint64 , * fn.EventReceiver [fn.Event ]]
@@ -539,18 +558,7 @@ func (m *Manager) addScidAlias(scidAlias uint64, assetSpecifier asset.Specifier,
539558 return c .PubKeyBytes == peer
540559 }, localChans )
541560
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
554562 for _ , localChan := range peerChannels {
555563 if len (localChan .CustomChannelData ) == 0 {
556564 continue
@@ -564,12 +572,20 @@ func (m *Manager) addScidAlias(scidAlias uint64, assetSpecifier asset.Specifier,
564572 continue
565573 }
566574
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
573589 }
574590 }
575591
@@ -583,8 +599,8 @@ func (m *Manager) addScidAlias(scidAlias uint64, assetSpecifier asset.Specifier,
583599 // At this point, if the base SCID is still not found, we return an
584600 // error. We can't map the SCID alias to a base SCID.
585601 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 )
588604 }
589605
590606 log .Debugf ("Adding SCID alias %d for base SCID %d" , scidAlias , baseSCID )
@@ -917,6 +933,99 @@ func (m *Manager) RemoveSubscriber(
917933 return nil
918934}
919935
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+
9201029// publishSubscriberEvent publishes an event to all subscribers.
9211030func (m * Manager ) publishSubscriberEvent (event fn.Event ) {
9221031 // Iterate over the subscribers and deliver the event to each one.
0 commit comments