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
No known key found for this signature in database
GPG key ID: 118759E83439A9B1
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
// 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.

View file

@ -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(

View file

@ -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)
}

View file

@ -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)

View file

@ -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)

View file

@ -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)