Skip to content

Commit

Permalink
Merge pull request #8735 from ellemouton/rb-receives
Browse files Browse the repository at this point in the history
[2/4] Route Blinding Receives: Receive and send to a single blinded path in an invoice.
  • Loading branch information
Roasbeef authored Jul 30, 2024
2 parents b7c59b3 + c490279 commit 9decf80
Show file tree
Hide file tree
Showing 49 changed files with 5,615 additions and 1,865 deletions.
58 changes: 53 additions & 5 deletions channeldb/payment_control.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,12 @@ var (

// ErrValueMismatch is returned if we try to register a non-MPP attempt
// with an amount that doesn't match the payment amount.
ErrValueMismatch = errors.New("attempted value doesn't match payment" +
ErrValueMismatch = errors.New("attempted value doesn't match payment " +
"amount")

// ErrValueExceedsAmt is returned if we try to register an attempt that
// would take the total sent amount above the payment amount.
ErrValueExceedsAmt = errors.New("attempted value exceeds payment" +
ErrValueExceedsAmt = errors.New("attempted value exceeds payment " +
"amount")

// ErrNonMPPayment is returned if we try to register an MPP attempt for
Expand All @@ -83,6 +83,17 @@ var (
// a payment that already has an MPP attempt registered.
ErrMPPayment = errors.New("payment has MPP attempts")

// ErrMPPRecordInBlindedPayment is returned if we try to register an
// attempt with an MPP record for a payment to a blinded path.
ErrMPPRecordInBlindedPayment = errors.New("blinded payment cannot " +
"contain MPP records")

// ErrBlindedPaymentTotalAmountMismatch is returned if we try to
// register an HTLC shard to a blinded route where the total amount
// doesn't match existing shards.
ErrBlindedPaymentTotalAmountMismatch = errors.New("blinded path " +
"total amount mismatch")

// ErrMPPPaymentAddrMismatch is returned if we try to register an MPP
// shard where the payment address doesn't match existing shards.
ErrMPPPaymentAddrMismatch = errors.New("payment address mismatch")
Expand All @@ -96,7 +107,7 @@ var (
// attempt to a payment that has at least one of its HTLCs settled.
ErrPaymentPendingSettled = errors.New("payment has settled htlcs")

// ErrPaymentAlreadyFailed is returned when we try to add a new attempt
// ErrPaymentPendingFailed is returned when we try to add a new attempt
// to a payment that already has a failure reason.
ErrPaymentPendingFailed = errors.New("payment has failure reason")

Expand Down Expand Up @@ -334,12 +345,48 @@ func (p *PaymentControl) RegisterAttempt(paymentHash lntypes.Hash,
return err
}

// If the final hop has encrypted data, then we know this is a
// blinded payment. In blinded payments, MPP records are not set
// for split payments and the recipient is responsible for using
// a consistent PathID across the various encrypted data
// payloads that we received from them for this payment. All we
// need to check is that the total amount field for each HTLC
// in the split payment is correct.
isBlinded := len(attempt.Route.FinalHop().EncryptedData) != 0

// Make sure any existing shards match the new one with regards
// to MPP options.
mpp := attempt.Route.FinalHop().MPP

// MPP records should not be set for attempts to blinded paths.
if isBlinded && mpp != nil {
return ErrMPPRecordInBlindedPayment
}

for _, h := range payment.InFlightHTLCs() {
hMpp := h.Route.FinalHop().MPP

// If this is a blinded payment, then no existing HTLCs
// should have MPP records.
if isBlinded && hMpp != nil {
return ErrMPPRecordInBlindedPayment
}

// If this is a blinded payment, then we just need to
// check that the TotalAmtMsat field for this shard
// is equal to that of any other shard in the same
// payment.
if isBlinded {
if attempt.Route.FinalHop().TotalAmtMsat !=
h.Route.FinalHop().TotalAmtMsat {

//nolint:lll
return ErrBlindedPaymentTotalAmountMismatch
}

continue
}

switch {
// We tried to register a non-MPP attempt for a MPP
// payment.
Expand Down Expand Up @@ -367,9 +414,10 @@ func (p *PaymentControl) RegisterAttempt(paymentHash lntypes.Hash,
}

// If this is a non-MPP attempt, it must match the total amount
// exactly.
// exactly. Note that a blinded payment is considered an MPP
// attempt.
amt := attempt.Route.ReceiverAmt()
if mpp == nil && amt != payment.Info.Value {
if !isBlinded && mpp == nil && amt != payment.Info.Value {
return ErrValueMismatch
}

Expand Down
14 changes: 14 additions & 0 deletions cmd/lncli/cmd_invoice.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@ var addInvoiceCommand = cli.Command{
Usage: "creates an AMP invoice. If true, preimage " +
"should not be set.",
},
cli.BoolFlag{
Name: "blind",
Usage: "creates an invoice that contains blinded " +
"paths. Note that invoices with blinded " +
"paths will be signed using a random " +
"ephemeral key so as not to reveal the real " +
"node ID of this node.",
},
},
Action: actionDecorator(addInvoice),
}
Expand Down Expand Up @@ -127,6 +135,11 @@ func addInvoice(ctx *cli.Context) error {
return fmt.Errorf("unable to parse description_hash: %w", err)
}

if ctx.IsSet("private") && ctx.IsSet("blind") {
return fmt.Errorf("cannot include both route hints and " +
"blinded paths in the same invoice")
}

invoice := &lnrpc.Invoice{
Memo: ctx.String("memo"),
RPreimage: preimage,
Expand All @@ -138,6 +151,7 @@ func addInvoice(ctx *cli.Context) error {
CltvExpiry: ctx.Uint64("cltv_expiry_delta"),
Private: ctx.Bool("private"),
IsAmp: ctx.Bool("amp"),
Blind: ctx.Bool("blind"),
}

resp, err := client.AddInvoice(ctxc, invoice)
Expand Down
23 changes: 11 additions & 12 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,15 @@ func DefaultConfig() Config {
Invoices: &lncfg.Invoices{
HoldExpiryDelta: lncfg.DefaultHoldInvoiceExpiryDelta,
},
Routing: &lncfg.Routing{
BlindedPaths: lncfg.BlindedPaths{
MinNumRealHops: lncfg.DefaultMinNumRealBlindedPathHops,
NumHops: lncfg.DefaultNumBlindedPathHops,
MaxNumPaths: lncfg.DefaultMaxNumBlindedPaths,
PolicyIncreaseMultiplier: lncfg.DefaultBlindedPathPolicyIncreaseMultiplier,
PolicyDecreaseMultiplier: lncfg.DefaultBlindedPathPolicyDecreaseMultiplier,
},
},
MaxOutgoingCltvExpiry: htlcswitch.DefaultMaxOutgoingCltvExpiry,
MaxChannelFeeAllocation: htlcswitch.DefaultMaxLinkFeeAllocation,
MaxCommitFeeRateAnchors: lnwallet.DefaultAnchorsCommitMaxFeeRateSatPerVByte,
Expand Down Expand Up @@ -1656,18 +1665,6 @@ func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser,
return nil, mkErr("error parsing gossip syncer: %v", err)
}

// Log a warning if our expiry delta is not greater than our incoming
// broadcast delta. We do not fail here because this value may be set
// to zero to intentionally keep lnd's behavior unchanged from when we
// didn't auto-cancel these invoices.
if cfg.Invoices.HoldExpiryDelta <= lncfg.DefaultIncomingBroadcastDelta {
ltndLog.Warnf("Invoice hold expiry delta: %v <= incoming "+
"delta: %v, accepted hold invoices will force close "+
"channels if they are not canceled manually",
cfg.Invoices.HoldExpiryDelta,
lncfg.DefaultIncomingBroadcastDelta)
}

// If the experimental protocol options specify any protocol messages
// that we want to handle as custom messages, set them now.
customMsg := cfg.ProtocolOptions.CustomMessageOverrides()
Expand All @@ -1690,6 +1687,8 @@ func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser,
cfg.RemoteSigner,
cfg.Sweeper,
cfg.Htlcswitch,
cfg.Invoices,
cfg.Routing,
)
if err != nil {
return nil, err
Expand Down
5 changes: 5 additions & 0 deletions docs/release-notes/release-notes-0.18.3.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@
* [Groundwork](https://github.com/lightningnetwork/lnd/pull/8752) in preparation
for implementing route blinding receives.

* [Generate and send to](https://github.com/lightningnetwork/lnd/pull/8735) an
invoice with blinded paths. With this, the `--blind` flag can be used with
the `lncli addinvoice` command to instruct LND to include blinded paths in the
invoice.

## Testing
## Database

Expand Down
3 changes: 3 additions & 0 deletions feature/default_sets.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,7 @@ var defaultSetDesc = setDesc{
SetInit: {}, // I
SetNodeAnn: {}, // N
},
lnwire.Bolt11BlindedPathsOptional: {
SetInvoice: {}, // I
},
}
4 changes: 2 additions & 2 deletions feature/deps.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ var deps = depDesc{
lnwire.RouteBlindingOptional: {
lnwire.TLVOnionPayloadOptional: {},
},
lnwire.RouteBlindingRequired: {
lnwire.TLVOnionPayloadRequired: {},
lnwire.Bolt11BlindedPathsOptional: {
lnwire.RouteBlindingOptional: {},
},
}

Expand Down
4 changes: 4 additions & 0 deletions feature/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ func newManager(cfg Config, desc setDesc) (*Manager, error) {
raw.Unset(lnwire.MPPRequired)
raw.Unset(lnwire.RouteBlindingOptional)
raw.Unset(lnwire.RouteBlindingRequired)
raw.Unset(lnwire.Bolt11BlindedPathsOptional)
raw.Unset(lnwire.Bolt11BlindedPathsRequired)
raw.Unset(lnwire.AMPOptional)
raw.Unset(lnwire.AMPRequired)
raw.Unset(lnwire.KeysendOptional)
Expand Down Expand Up @@ -187,6 +189,8 @@ func newManager(cfg Config, desc setDesc) (*Manager, error) {
if cfg.NoRouteBlinding {
raw.Unset(lnwire.RouteBlindingOptional)
raw.Unset(lnwire.RouteBlindingRequired)
raw.Unset(lnwire.Bolt11BlindedPathsOptional)
raw.Unset(lnwire.Bolt11BlindedPathsRequired)
}
for _, custom := range cfg.CustomFeatures[set] {
if custom > set.Maximum() {
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ require (
github.com/kkdai/bstream v1.0.0
github.com/lightninglabs/neutrino v0.16.1-0.20240425105051-602843d34ffd
github.com/lightninglabs/neutrino/cache v1.1.2
github.com/lightningnetwork/lightning-onion v1.2.1-0.20230823005744-06182b1d7d2f
github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb
github.com/lightningnetwork/lnd/cert v1.2.2
github.com/lightningnetwork/lnd/clock v1.1.1
github.com/lightningnetwork/lnd/fn v1.2.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -444,8 +444,8 @@ github.com/lightninglabs/neutrino/cache v1.1.2 h1:C9DY/DAPaPxbFC+xNNEI/z1SJY9GS3
github.com/lightninglabs/neutrino/cache v1.1.2/go.mod h1:XJNcgdOw1LQnanGjw8Vj44CvguYA25IMKjWFZczwZuo=
github.com/lightninglabs/protobuf-go-hex-display v1.30.0-hex-display h1:pRdza2wleRN1L2fJXd6ZoQ9ZegVFTAb2bOQfruJPKcY=
github.com/lightninglabs/protobuf-go-hex-display v1.30.0-hex-display/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
github.com/lightningnetwork/lightning-onion v1.2.1-0.20230823005744-06182b1d7d2f h1:Pua7+5TcFEJXIIZ1I2YAUapmbcttmLj4TTi786bIi3s=
github.com/lightningnetwork/lightning-onion v1.2.1-0.20230823005744-06182b1d7d2f/go.mod h1:c0kvRShutpj3l6B9WtTsNTBUtjSmjZXbJd9ZBRQOSKI=
github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb h1:yfM05S8DXKhuCBp5qSMZdtSwvJ+GFzl94KbXMNB1JDY=
github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb/go.mod h1:c0kvRShutpj3l6B9WtTsNTBUtjSmjZXbJd9ZBRQOSKI=
github.com/lightningnetwork/lnd/cert v1.2.2 h1:71YK6hogeJtxSxw2teq3eGeuy4rHGKcFf0d0Uy4qBjI=
github.com/lightningnetwork/lnd/cert v1.2.2/go.mod h1:jQmFn/Ez4zhDgq2hnYSw8r35bqGVxViXhX6Cd7HXM6U=
github.com/lightningnetwork/lnd/clock v1.1.1 h1:OfR3/zcJd2RhH0RU+zX/77c0ZiOnIMsDIBjgjWdZgA0=
Expand Down
3 changes: 1 addition & 2 deletions htlcswitch/circuit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (

"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
bitcoinCfg "github.com/btcsuite/btcd/chaincfg"
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/htlcswitch"
Expand Down Expand Up @@ -87,7 +86,7 @@ func initTestExtracter() {
func newOnionProcessor(t *testing.T) *hop.OnionProcessor {
sphinxRouter := sphinx.NewRouter(
&keychain.PrivKeyECDH{PrivKey: sphinxPrivKey},
&bitcoinCfg.SimNetParams, sphinx.NewMemoryReplayLog(),
sphinx.NewMemoryReplayLog(),
)

if err := sphinxRouter.Start(); err != nil {
Expand Down
6 changes: 6 additions & 0 deletions htlcswitch/hop/forwarding_info.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package hop

import (
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/lnwire"
)

Expand All @@ -27,4 +28,9 @@ type ForwardingInfo struct {
// node in UpdateAddHtlc. This field is set if the htlc is part of a
// blinded route.
NextBlinding lnwire.BlindingPointRecord

// PathID is a secret identifier that the creator of a blinded path
// sets for itself to ensure that the blinded path has been used in the
// correct context.
PathID *chainhash.Hash
}
Loading

0 comments on commit 9decf80

Please sign in to comment.