rpcserver+invoicesrpc: alias-aware AddInvoice,AddHoldInvoice rpc

AddInvoice,AddHoldInvoice now issue invoices that include our
peer's aliases. Some extra sanity checks are included to ensure we
don't leak our confirmed SCID for a private channel.
This commit is contained in:
eugene
2022-04-04 16:49:14 -04:00
parent 1aa9626606
commit 0ba67015da
6 changed files with 117 additions and 17 deletions

View File

@@ -72,6 +72,10 @@ type AddInvoiceConfig struct {
// GenAmpInvoiceFeatures returns a feature containing feature bits that // GenAmpInvoiceFeatures returns a feature containing feature bits that
// should be advertised on freshly generated AMP invoices. // should be advertised on freshly generated AMP invoices.
GenAmpInvoiceFeatures func() *lnwire.FeatureVector 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. // AddInvoiceData contains the required data to create a new invoice.
@@ -387,9 +391,27 @@ func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig,
continue 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( chanID := lnwire.NewChanIDFromOutPoint(
&c.FundingOutpoint, &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) isActive := cfg.IsChannelActive(chanID)
hopHintInfo := newHopHintInfo(c, isActive) hopHintInfo := newHopHintInfo(c, isActive)
@@ -529,10 +551,17 @@ func chanCanBeHopHint(channel *HopHintInfo, cfg *SelectHopHintsCfg) (
// Fetch the policies for each end of the channel. // Fetch the policies for each end of the channel.
info, p1, p2, err := cfg.FetchChannelEdgesByID(channel.ShortChannelID) info, p1, p2, err := cfg.FetchChannelEdgesByID(channel.ShortChannelID)
if err != nil { if err != nil {
log.Errorf("Unable to fetch the routing "+ // In the case of zero-conf channels, it may be the case that
"policies for the edges of the channel "+ // the alias SCID was deleted from the graph, and replaced by
"%v: %v", channel.ShortChannelID, err) // the confirmed SCID. Check the Graph for the confirmed SCID.
return nil, false 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 // 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. // addHopHint creates a hop hint out of the passed channel and channel policy.
// The new hop hint is appended to the passed slice. // The new hop hint is appended to the passed slice.
func addHopHint(hopHints *[][]zpay32.HopHint, func addHopHint(hopHints *[][]zpay32.HopHint,
channel *HopHintInfo, chanPolicy *channeldb.ChannelEdgePolicy) { channel *HopHintInfo, chanPolicy *channeldb.ChannelEdgePolicy,
aliasScid lnwire.ShortChannelID) {
hopHint := zpay32.HopHint{ hopHint := zpay32.HopHint{
NodeID: channel.RemotePubkey, NodeID: channel.RemotePubkey,
@@ -562,6 +592,11 @@ func addHopHint(hopHints *[][]zpay32.HopHint,
CLTVExpiryDelta: chanPolicy.TimeLockDelta, CLTVExpiryDelta: chanPolicy.TimeLockDelta,
} }
var defaultScid lnwire.ShortChannelID
if aliasScid != defaultScid {
hopHint.ChannelID = aliasScid.ToUint64()
}
*hopHints = append(*hopHints, []zpay32.HopHint{hopHint}) *hopHints = append(*hopHints, []zpay32.HopHint{hopHint})
} }
@@ -587,18 +622,28 @@ type HopHintInfo struct {
// ShortChannelID is the short channel ID of the channel. // ShortChannelID is the short channel ID of the channel.
ShortChannelID uint64 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 { func newHopHintInfo(c *channeldb.OpenChannel, isActive bool) *HopHintInfo {
isPublic := c.ChannelFlags&lnwire.FFAnnounceChannel != 0 isPublic := c.ChannelFlags&lnwire.FFAnnounceChannel != 0
return &HopHintInfo{ return &HopHintInfo{
IsPublic: isPublic, IsPublic: isPublic,
IsActive: isActive, IsActive: isActive,
FundingOutpoint: c.FundingOutpoint, FundingOutpoint: c.FundingOutpoint,
RemotePubkey: c.IdentityPub, RemotePubkey: c.IdentityPub,
RemoteBalance: c.LocalCommitment.RemoteBalance, RemoteBalance: c.LocalCommitment.RemoteBalance,
ShortChannelID: c.ShortChannelID.ToUint64(), 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, FetchChannelEdgesByID func(chanID uint64) (*channeldb.ChannelEdgeInfo,
*channeldb.ChannelEdgePolicy, *channeldb.ChannelEdgePolicy, *channeldb.ChannelEdgePolicy, *channeldb.ChannelEdgePolicy,
error) 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 { func newSelectHopHintsCfg(invoicesCfg *AddInvoiceConfig) *SelectHopHintsCfg {
return &SelectHopHintsCfg{ return &SelectHopHintsCfg{
IsPublicNode: invoicesCfg.Graph.IsPublicNode, IsPublicNode: invoicesCfg.Graph.IsPublicNode,
FetchChannelEdgesByID: invoicesCfg.Graph.FetchChannelEdgesByID, FetchChannelEdgesByID: invoicesCfg.Graph.FetchChannelEdgesByID,
GetAlias: invoicesCfg.GetAlias,
} }
} }
@@ -693,9 +743,26 @@ func SelectHopHints(amtMSat lnwire.MilliSatoshi, cfg *SelectHopHintsCfg,
continue 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 // Now that we now this channel use usable, add it as a hop
// hint and the indexes we'll use later. // hint and the indexes we'll use later.
addHopHint(&hopHints, channel, edgePolicy) addHopHint(&hopHints, channel, edgePolicy, alias)
hopHintChans[channel.FundingOutpoint] = struct{}{} hopHintChans[channel.FundingOutpoint] = struct{}{}
totalHintBandwidth += channel.RemoteBalance totalHintBandwidth += channel.RemoteBalance
@@ -733,9 +800,26 @@ func SelectHopHints(amtMSat lnwire.MilliSatoshi, cfg *SelectHopHintsCfg,
continue 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 // Include the route hint in our set of options that will be
// used when creating the invoice. // 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 // As we've just added a new hop hint, we'll accumulate it's
// available balance now to update our tally. // available balance now to update our tally.

View File

@@ -274,11 +274,11 @@ func TestSelectHopHints(t *testing.T) {
// hop hint. // hop hint.
h.Mock.On( h.Mock.On(
"FetchChannelEdgesByID", "FetchChannelEdgesByID",
private1ShortID, mock.Anything,
).Return( ).Return(
nil, nil, nil, nil, nil, nil,
errors.New("no edge"), errors.New("no edge"),
) ).Times(4)
}, },
amount: 100, amount: 100,
channels: []*HopHintInfo{ 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 { for _, test := range tests {
test := test test := test
@@ -548,6 +552,7 @@ func TestSelectHopHints(t *testing.T) {
cfg := &SelectHopHintsCfg{ cfg := &SelectHopHintsCfg{
IsPublicNode: mock.IsPublicNode, IsPublicNode: mock.IsPublicNode,
FetchChannelEdgesByID: mock.FetchChannelEdgesByID, FetchChannelEdgesByID: mock.FetchChannelEdgesByID,
GetAlias: getAlias,
} }
hints := SelectHopHints( hints := SelectHopHints(

View File

@@ -60,4 +60,8 @@ type Config struct {
// GenAmpInvoiceFeatures returns a feature containing feature bits that // GenAmpInvoiceFeatures returns a feature containing feature bits that
// should be advertised on freshly generated AMP invoices. // should be advertised on freshly generated AMP invoices.
GenAmpInvoiceFeatures func() *lnwire.FeatureVector 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)
} }

View File

@@ -331,6 +331,7 @@ func (s *Server) AddHoldInvoice(ctx context.Context,
Graph: s.cfg.GraphDB, Graph: s.cfg.GraphDB,
GenInvoiceFeatures: s.cfg.GenInvoiceFeatures, GenInvoiceFeatures: s.cfg.GenInvoiceFeatures,
GenAmpInvoiceFeatures: s.cfg.GenAmpInvoiceFeatures, GenAmpInvoiceFeatures: s.cfg.GenAmpInvoiceFeatures,
GetAlias: s.cfg.GetAlias,
} }
hash, err := lntypes.MakeHash(invoice.Hash) hash, err := lntypes.MakeHash(invoice.Hash)

View File

@@ -756,6 +756,7 @@ func (r *rpcServer) addDeps(s *server, macService *macaroons.Service,
r.cfg.net.ResolveTCPAddr, genInvoiceFeatures, r.cfg.net.ResolveTCPAddr, genInvoiceFeatures,
genAmpInvoiceFeatures, getNodeAnnouncement, genAmpInvoiceFeatures, getNodeAnnouncement,
s.updateAndBrodcastSelfNode, parseAddr, rpcsLog, s.updateAndBrodcastSelfNode, parseAddr, rpcsLog,
s.aliasMgr.GetPeerAlias,
) )
if err != nil { if err != nil {
return err return err
@@ -3039,7 +3040,7 @@ func (r *rpcServer) SubscribePeerEvents(req *lnrpc.PeerEventSubscription,
// confirmed unspent outputs and all unconfirmed unspent outputs under control // confirmed unspent outputs and all unconfirmed unspent outputs under control
// by the wallet. This method can be modified by having the request specify // by the wallet. This method can be modified by having the request specify
// only witness outputs should be factored into the final output sum. // 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, func (r *rpcServer) WalletBalance(ctx context.Context,
in *lnrpc.WalletBalanceRequest) (*lnrpc.WalletBalanceResponse, error) { in *lnrpc.WalletBalanceRequest) (*lnrpc.WalletBalanceResponse, error) {
@@ -5260,6 +5261,7 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
GenAmpInvoiceFeatures: func() *lnwire.FeatureVector { GenAmpInvoiceFeatures: func() *lnwire.FeatureVector {
return r.server.featureMgr.Get(feature.SetInvoiceAmp) return r.server.featureMgr.Get(feature.SetInvoiceAmp)
}, },
GetAlias: r.server.aliasMgr.GetPeerAlias,
} }
value, err := lnrpc.UnmarshallAmt(invoice.Value, invoice.ValueMsat) value, err := lnrpc.UnmarshallAmt(invoice.Value, invoice.ValueMsat)

View File

@@ -121,7 +121,8 @@ func (s *subRPCServerConfigs) PopulateDependencies(cfg *Config,
getNodeAnnouncement func() (lnwire.NodeAnnouncement, error), getNodeAnnouncement func() (lnwire.NodeAnnouncement, error),
updateNodeAnnouncement func(modifiers ...netann.NodeAnnModifier) error, updateNodeAnnouncement func(modifiers ...netann.NodeAnnModifier) error,
parseAddr func(addr string) (net.Addr, 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 // First, we'll use reflect to obtain a version of the config struct
// that allows us to programmatically inspect its fields. // that allows us to programmatically inspect its fields.
@@ -257,6 +258,9 @@ func (s *subRPCServerConfigs) PopulateDependencies(cfg *Config,
subCfgValue.FieldByName("GenAmpInvoiceFeatures").Set( subCfgValue.FieldByName("GenAmpInvoiceFeatures").Set(
reflect.ValueOf(genAmpInvoiceFeatures), reflect.ValueOf(genAmpInvoiceFeatures),
) )
subCfgValue.FieldByName("GetAlias").Set(
reflect.ValueOf(getAlias),
)
case *neutrinorpc.Config: case *neutrinorpc.Config:
subCfgValue := extractReflectValue(subCfg) subCfgValue := extractReflectValue(subCfg)