diff --git a/lnrpc/invoicesrpc/addinvoice.go b/lnrpc/invoicesrpc/addinvoice.go index e0e0242dd..6b749918d 100644 --- a/lnrpc/invoicesrpc/addinvoice.go +++ b/lnrpc/invoicesrpc/addinvoice.go @@ -72,6 +72,10 @@ type AddInvoiceConfig struct { // GenAmpInvoiceFeatures returns a feature containing feature bits that // should be advertised on freshly generated AMP invoices. GenAmpInvoiceFeatures func() *lnwire.FeatureVector + + // GetAlias allows the peer's alias SCID to be retrieved for private + // option_scid_alias channels. + GetAlias func(lnwire.ChannelID) (lnwire.ShortChannelID, error) } // AddInvoiceData contains the required data to create a new invoice. @@ -387,9 +391,27 @@ func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig, continue } + // If this is a zero-conf channel, check if the + // confirmed SCID was used in forcedHints. + realScid := c.ZeroConfRealScid().ToUint64() + if c.IsZeroConf() { + if _, ok := forcedHints[realScid]; ok { + continue + } + } + chanID := lnwire.NewChanIDFromOutPoint( &c.FundingOutpoint, ) + + // Check whether the the peer's alias was + // provided in forcedHints. + peerAlias, _ := cfg.GetAlias(chanID) + peerScid := peerAlias.ToUint64() + if _, ok := forcedHints[peerScid]; ok { + continue + } + isActive := cfg.IsChannelActive(chanID) hopHintInfo := newHopHintInfo(c, isActive) @@ -529,10 +551,17 @@ func chanCanBeHopHint(channel *HopHintInfo, cfg *SelectHopHintsCfg) ( // Fetch the policies for each end of the channel. info, p1, p2, err := cfg.FetchChannelEdgesByID(channel.ShortChannelID) if err != nil { - log.Errorf("Unable to fetch the routing "+ - "policies for the edges of the channel "+ - "%v: %v", channel.ShortChannelID, err) - return nil, false + // In the case of zero-conf channels, it may be the case that + // the alias SCID was deleted from the graph, and replaced by + // the confirmed SCID. Check the Graph for the confirmed SCID. + confirmedScid := channel.ConfirmedScidZC + info, p1, p2, err = cfg.FetchChannelEdgesByID(confirmedScid) + if err != nil { + log.Errorf("Unable to fetch the routing policies for "+ + "the edges of the channel %v: %v", + channel.ShortChannelID, err) + return nil, false + } } // Now, we'll need to determine which is the correct policy for HTLCs @@ -550,7 +579,8 @@ func chanCanBeHopHint(channel *HopHintInfo, cfg *SelectHopHintsCfg) ( // addHopHint creates a hop hint out of the passed channel and channel policy. // The new hop hint is appended to the passed slice. func addHopHint(hopHints *[][]zpay32.HopHint, - channel *HopHintInfo, chanPolicy *channeldb.ChannelEdgePolicy) { + channel *HopHintInfo, chanPolicy *channeldb.ChannelEdgePolicy, + aliasScid lnwire.ShortChannelID) { hopHint := zpay32.HopHint{ NodeID: channel.RemotePubkey, @@ -562,6 +592,11 @@ func addHopHint(hopHints *[][]zpay32.HopHint, CLTVExpiryDelta: chanPolicy.TimeLockDelta, } + var defaultScid lnwire.ShortChannelID + if aliasScid != defaultScid { + hopHint.ChannelID = aliasScid.ToUint64() + } + *hopHints = append(*hopHints, []zpay32.HopHint{hopHint}) } @@ -587,18 +622,28 @@ type HopHintInfo struct { // ShortChannelID is the short channel ID of the channel. ShortChannelID uint64 + + // ConfirmedScidZC is the confirmed SCID of a zero-conf channel. This + // may be used for looking up a channel in the graph. + ConfirmedScidZC uint64 + + // ScidAliasFeature denotes whether the channel has negotiated the + // option-scid-alias feature bit. + ScidAliasFeature bool } func newHopHintInfo(c *channeldb.OpenChannel, isActive bool) *HopHintInfo { isPublic := c.ChannelFlags&lnwire.FFAnnounceChannel != 0 return &HopHintInfo{ - IsPublic: isPublic, - IsActive: isActive, - FundingOutpoint: c.FundingOutpoint, - RemotePubkey: c.IdentityPub, - RemoteBalance: c.LocalCommitment.RemoteBalance, - ShortChannelID: c.ShortChannelID.ToUint64(), + IsPublic: isPublic, + IsActive: isActive, + FundingOutpoint: c.FundingOutpoint, + RemotePubkey: c.IdentityPub, + RemoteBalance: c.LocalCommitment.RemoteBalance, + ShortChannelID: c.ShortChannelID.ToUint64(), + ConfirmedScidZC: c.ZeroConfRealScid().ToUint64(), + ScidAliasFeature: c.ChanType.HasScidAliasFeature(), } } @@ -615,12 +660,17 @@ type SelectHopHintsCfg struct { FetchChannelEdgesByID func(chanID uint64) (*channeldb.ChannelEdgeInfo, *channeldb.ChannelEdgePolicy, *channeldb.ChannelEdgePolicy, error) + + // GetAlias allows the peer's alias SCID to be retrieved for private + // option_scid_alias channels. + GetAlias func(lnwire.ChannelID) (lnwire.ShortChannelID, error) } func newSelectHopHintsCfg(invoicesCfg *AddInvoiceConfig) *SelectHopHintsCfg { return &SelectHopHintsCfg{ IsPublicNode: invoicesCfg.Graph.IsPublicNode, FetchChannelEdgesByID: invoicesCfg.Graph.FetchChannelEdgesByID, + GetAlias: invoicesCfg.GetAlias, } } @@ -693,9 +743,26 @@ func SelectHopHints(amtMSat lnwire.MilliSatoshi, cfg *SelectHopHintsCfg, continue } + // Lookup and see if there is an alias SCID that exists. + chanID := lnwire.NewChanIDFromOutPoint( + &channel.FundingOutpoint, + ) + alias, _ := cfg.GetAlias(chanID) + + // If this is a channel where the option-scid-alias feature bit + // was negotiated and the alias is not yet assigned, we cannot + // issue an invoice. Doing so might expose the confirmed SCID + // of a private channel. + if channel.ScidAliasFeature { + var defaultScid lnwire.ShortChannelID + if alias == defaultScid { + continue + } + } + // Now that we now this channel use usable, add it as a hop // hint and the indexes we'll use later. - addHopHint(&hopHints, channel, edgePolicy) + addHopHint(&hopHints, channel, edgePolicy, alias) hopHintChans[channel.FundingOutpoint] = struct{}{} totalHintBandwidth += channel.RemoteBalance @@ -733,9 +800,26 @@ func SelectHopHints(amtMSat lnwire.MilliSatoshi, cfg *SelectHopHintsCfg, continue } + // Lookup and see if there's an alias SCID that exists. + chanID := lnwire.NewChanIDFromOutPoint( + &channel.FundingOutpoint, + ) + alias, _ := cfg.GetAlias(chanID) + + // If this is a channel where the option-scid-alias feature bit + // was negotiated and the alias is not yet assigned, we cannot + // issue an invoice. Doing so might expose the confirmed SCID + // of a private channel. + if channel.ScidAliasFeature { + var defaultScid lnwire.ShortChannelID + if alias == defaultScid { + continue + } + } + // Include the route hint in our set of options that will be // used when creating the invoice. - addHopHint(&hopHints, channel, remotePolicy) + addHopHint(&hopHints, channel, remotePolicy, alias) // As we've just added a new hop hint, we'll accumulate it's // available balance now to update our tally. diff --git a/lnrpc/invoicesrpc/addinvoice_test.go b/lnrpc/invoicesrpc/addinvoice_test.go index 38fab71f2..4720f7ad0 100644 --- a/lnrpc/invoicesrpc/addinvoice_test.go +++ b/lnrpc/invoicesrpc/addinvoice_test.go @@ -274,11 +274,11 @@ func TestSelectHopHints(t *testing.T) { // hop hint. h.Mock.On( "FetchChannelEdgesByID", - private1ShortID, + mock.Anything, ).Return( nil, nil, nil, errors.New("no edge"), - ) + ).Times(4) }, amount: 100, channels: []*HopHintInfo{ @@ -536,6 +536,10 @@ func TestSelectHopHints(t *testing.T) { }, } + getAlias := func(lnwire.ChannelID) (lnwire.ShortChannelID, error) { + return lnwire.ShortChannelID{}, nil + } + for _, test := range tests { test := test @@ -548,6 +552,7 @@ func TestSelectHopHints(t *testing.T) { cfg := &SelectHopHintsCfg{ IsPublicNode: mock.IsPublicNode, FetchChannelEdgesByID: mock.FetchChannelEdgesByID, + GetAlias: getAlias, } hints := SelectHopHints( diff --git a/lnrpc/invoicesrpc/config_active.go b/lnrpc/invoicesrpc/config_active.go index c70f228a9..a5b29c32a 100644 --- a/lnrpc/invoicesrpc/config_active.go +++ b/lnrpc/invoicesrpc/config_active.go @@ -60,4 +60,8 @@ type Config struct { // GenAmpInvoiceFeatures returns a feature containing feature bits that // should be advertised on freshly generated AMP invoices. GenAmpInvoiceFeatures func() *lnwire.FeatureVector + + // GetAlias returns the peer's alias SCID if it exists given the + // 32-byte ChannelID. + GetAlias func(lnwire.ChannelID) (lnwire.ShortChannelID, error) } diff --git a/lnrpc/invoicesrpc/invoices_server.go b/lnrpc/invoicesrpc/invoices_server.go index 5c0d71ac4..60459682d 100644 --- a/lnrpc/invoicesrpc/invoices_server.go +++ b/lnrpc/invoicesrpc/invoices_server.go @@ -331,6 +331,7 @@ func (s *Server) AddHoldInvoice(ctx context.Context, Graph: s.cfg.GraphDB, GenInvoiceFeatures: s.cfg.GenInvoiceFeatures, GenAmpInvoiceFeatures: s.cfg.GenAmpInvoiceFeatures, + GetAlias: s.cfg.GetAlias, } hash, err := lntypes.MakeHash(invoice.Hash) diff --git a/rpcserver.go b/rpcserver.go index ee32238c0..ebb2c9365 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -756,6 +756,7 @@ func (r *rpcServer) addDeps(s *server, macService *macaroons.Service, r.cfg.net.ResolveTCPAddr, genInvoiceFeatures, genAmpInvoiceFeatures, getNodeAnnouncement, s.updateAndBrodcastSelfNode, parseAddr, rpcsLog, + s.aliasMgr.GetPeerAlias, ) if err != nil { return err @@ -3039,7 +3040,7 @@ func (r *rpcServer) SubscribePeerEvents(req *lnrpc.PeerEventSubscription, // confirmed unspent outputs and all unconfirmed unspent outputs under control // by the wallet. This method can be modified by having the request specify // only witness outputs should be factored into the final output sum. -// TODO(roasbeef): add async hooks into wallet balance changes +// TODO(roasbeef): add async hooks into wallet balance changes. func (r *rpcServer) WalletBalance(ctx context.Context, in *lnrpc.WalletBalanceRequest) (*lnrpc.WalletBalanceResponse, error) { @@ -5260,6 +5261,7 @@ func (r *rpcServer) AddInvoice(ctx context.Context, GenAmpInvoiceFeatures: func() *lnwire.FeatureVector { return r.server.featureMgr.Get(feature.SetInvoiceAmp) }, + GetAlias: r.server.aliasMgr.GetPeerAlias, } value, err := lnrpc.UnmarshallAmt(invoice.Value, invoice.ValueMsat) diff --git a/subrpcserver_config.go b/subrpcserver_config.go index b22c83195..8911d1eb3 100644 --- a/subrpcserver_config.go +++ b/subrpcserver_config.go @@ -121,7 +121,8 @@ func (s *subRPCServerConfigs) PopulateDependencies(cfg *Config, getNodeAnnouncement func() (lnwire.NodeAnnouncement, error), updateNodeAnnouncement func(modifiers ...netann.NodeAnnModifier) error, parseAddr func(addr string) (net.Addr, error), - rpcLogger btclog.Logger) error { + rpcLogger btclog.Logger, + getAlias func(lnwire.ChannelID) (lnwire.ShortChannelID, error)) error { // First, we'll use reflect to obtain a version of the config struct // that allows us to programmatically inspect its fields. @@ -257,6 +258,9 @@ func (s *subRPCServerConfigs) PopulateDependencies(cfg *Config, subCfgValue.FieldByName("GenAmpInvoiceFeatures").Set( reflect.ValueOf(genAmpInvoiceFeatures), ) + subCfgValue.FieldByName("GetAlias").Set( + reflect.ValueOf(getAlias), + ) case *neutrinorpc.Config: subCfgValue := extractReflectValue(subCfg)