mirror of
https://github.com/lightningnetwork/lnd.git
synced 2026-08-13 12:32:48 +02:00
Merge pull request #11009 from lightningnetwork/backport-10942-to-v0.20.x-branch
[v0.20.x-branch] Backport #10942: htlcswitch: forward blinded payments addressed by node_id
This commit is contained in:
commit
97abb6a083
30 changed files with 2001 additions and 332 deletions
|
|
@ -83,7 +83,7 @@ func (h *htlcIncomingContestResolver) processFinalHtlcFail() error {
|
|||
func (h *htlcIncomingContestResolver) invalidFinalHtlc(
|
||||
payload *hop.Payload, height uint32) bool {
|
||||
|
||||
if payload.FwdInfo.NextHop != hop.Exit {
|
||||
if !payload.FwdInfo.IsExit() {
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
@ -311,7 +311,7 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
|
|||
hodlChan <-chan interface{}
|
||||
witnessUpdates <-chan lntypes.Preimage
|
||||
)
|
||||
if payload.FwdInfo.NextHop == hop.Exit {
|
||||
if payload.FwdInfo.IsExit() {
|
||||
// Create a buffered hodl chan to prevent deadlock.
|
||||
hodlQueue := queue.NewConcurrentQueue(10)
|
||||
hodlQueue.Start()
|
||||
|
|
@ -700,7 +700,7 @@ func (h *htlcIncomingContestResolver) findAndapplyPreimage() (bool, error) {
|
|||
|
||||
// Exit early if this is not the exit hop, which means we are not the
|
||||
// payment receiver and don't have the preimage.
|
||||
if payload.FwdInfo.NextHop != hop.Exit {
|
||||
if !payload.FwdInfo.IsExit() {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,10 @@
|
|||
|
||||
## RPC Additions
|
||||
|
||||
* The [HTLC interceptor](https://github.com/lightningnetwork/lnd/pull/10942) now
|
||||
exposes the next hop of a blinded route that identifies it by node ID
|
||||
(`next_node_id`) rather than by channel.
|
||||
|
||||
## lncli Additions
|
||||
|
||||
# Improvements
|
||||
|
|
@ -60,6 +64,15 @@
|
|||
|
||||
## RPC Updates
|
||||
|
||||
* `ForwardHtlcInterceptRequest.outgoing_requested_chan_id` now holds a reserved
|
||||
sentinel value (`18446744073709551615`, all bits set) when the
|
||||
[HTLC interceptor](https://github.com/lightningnetwork/lnd/pull/10942) reports
|
||||
a blinded forward that identifies the next hop by node ID. The sender of such
|
||||
a forward requests no channel, so a zero value here would make a client that
|
||||
detects the exit hop by a zero channel ID classify the forward as a final
|
||||
receive. Clients that switch on this field must handle the sentinel and read
|
||||
`outgoing_requested_node_id` for the next hop.
|
||||
|
||||
## lncli Updates
|
||||
|
||||
## Breaking Changes
|
||||
|
|
@ -71,6 +84,13 @@
|
|||
# Technical and Architectural Updates
|
||||
## BOLT Spec Updates
|
||||
|
||||
* [Fixed an issue](https://github.com/lightningnetwork/lnd/pull/10942) where an
|
||||
lnd node acting as a relaying node (including the introduction node) in a
|
||||
blinded path failed to forward the payment when the next hop was identified by
|
||||
node ID (`next_node_id`) rather than a short channel ID. The next hop's public
|
||||
key is now resolved to one of our channels with that peer using non-strict
|
||||
forwarding.
|
||||
|
||||
## Testing
|
||||
|
||||
## Database
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package hop
|
|||
|
||||
import (
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/lightningnetwork/lnd/fn/v2"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
||||
|
|
@ -11,10 +12,14 @@ import (
|
|||
// received within the incoming HTLC, to ensure that the prior hop didn't
|
||||
// tamper with the end-to-end routing information at all.
|
||||
type ForwardingInfo struct {
|
||||
// NextHop is the channel ID of the next hop. The received HTLC should
|
||||
// be forwarded to this particular channel in order to continue the
|
||||
// end-to-end route.
|
||||
NextHop lnwire.ShortChannelID
|
||||
// NextHop identifies the next hop the HTLC should be forwarded to. In
|
||||
// the common case it is a Left holding the short channel ID of the
|
||||
// outgoing channel. For a blinded route whose recipient identifies the
|
||||
// next hop by node ID (next_node_id) it is a Right holding the next
|
||||
// node's compressed public key, which the switch's non-strict
|
||||
// forwarding logic resolves to one of our channels with that peer. The
|
||||
// zero value is a Left equal to hop.Exit, which denotes the exit hop.
|
||||
NextHop fn.Either[lnwire.ShortChannelID, [33]byte]
|
||||
|
||||
// AmountToForward is the amount of milli-satoshis that the receiving
|
||||
// node should forward to the next hop.
|
||||
|
|
@ -35,6 +40,51 @@ type ForwardingInfo struct {
|
|||
PathID *chainhash.Hash
|
||||
}
|
||||
|
||||
// NewChannelNextHop returns a next-hop value that identifies the outgoing
|
||||
// channel by its short channel ID, which is the common case.
|
||||
func NewChannelNextHop(
|
||||
scid lnwire.ShortChannelID) fn.Either[lnwire.ShortChannelID, [33]byte] {
|
||||
|
||||
return fn.NewLeft[lnwire.ShortChannelID, [33]byte](scid)
|
||||
}
|
||||
|
||||
// NewNodeNextHop returns a next-hop value that identifies the next hop by the
|
||||
// next node's compressed public key, as used by blinded routes that set
|
||||
// next_node_id instead of a short channel ID.
|
||||
func NewNodeNextHop(
|
||||
nodeID [33]byte) fn.Either[lnwire.ShortChannelID, [33]byte] {
|
||||
|
||||
return fn.NewRight[lnwire.ShortChannelID, [33]byte](nodeID)
|
||||
}
|
||||
|
||||
// IsExit returns true if this forwarding info denotes the exit hop, i.e. we are
|
||||
// the final recipient of the HTLC. This is the case when the next hop is a
|
||||
// short channel ID equal to hop.Exit. A node-ID next hop (used by some blinded
|
||||
// routes) is always a forward, never the exit hop.
|
||||
func (f ForwardingInfo) IsExit() bool {
|
||||
var isExit bool
|
||||
f.NextHop.WhenLeft(func(scid lnwire.ShortChannelID) {
|
||||
isExit = scid == Exit
|
||||
})
|
||||
|
||||
return isExit
|
||||
}
|
||||
|
||||
// NextHopChannel returns the short channel ID of the outgoing channel when the
|
||||
// next hop is identified by channel ID (the common case). It returns None when
|
||||
// the next hop is identified by node ID instead, in which case the outgoing
|
||||
// channel is selected by the switch's non-strict forwarding.
|
||||
func (f ForwardingInfo) NextHopChannel() fn.Option[lnwire.ShortChannelID] {
|
||||
return f.NextHop.LeftToSome()
|
||||
}
|
||||
|
||||
// NextHopNode returns the next hop's compressed pubkey when it is identified by
|
||||
// node ID (blinded routes via next_node_id), or None when identified by
|
||||
// channel.
|
||||
func (f ForwardingInfo) NextHopNode() fn.Option[[33]byte] {
|
||||
return f.NextHop.RightToSome()
|
||||
}
|
||||
|
||||
// FinalHtlcValidationResult describes the result of checking a final-hop
|
||||
// HTLC against the onion payload and supported final-hop CLTV range.
|
||||
type FinalHtlcValidationResult uint8
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package hop
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/lightningnetwork/lnd/fn/v2"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
|
@ -21,7 +22,7 @@ func TestValidateFinalHtlc(t *testing.T) {
|
|||
fwdInfo := ForwardingInfo{
|
||||
AmountToForward: amount,
|
||||
OutgoingCLTV: expiry,
|
||||
NextHop: Exit,
|
||||
NextHop: NewChannelNextHop(Exit),
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
|
|
@ -115,7 +116,7 @@ func TestValidateFinalHtlc(t *testing.T) {
|
|||
fwdInfo: ForwardingInfo{
|
||||
AmountToForward: amount,
|
||||
OutgoingCLTV: expiry + maxCltvDelta + 2,
|
||||
NextHop: Exit,
|
||||
NextHop: NewChannelNextHop(Exit),
|
||||
},
|
||||
validateAmount: true,
|
||||
expected: FinalHtlcInvalidCltv,
|
||||
|
|
@ -135,3 +136,41 @@ func TestValidateFinalHtlc(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestForwardingInfoNextHop asserts the next-hop accessors for both the short
|
||||
// channel ID (Left) and node ID (Right) representations, including the
|
||||
// invariant that the zero-value ForwardingInfo denotes the exit hop.
|
||||
func TestForwardingInfoNextHop(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scid := lnwire.NewShortChanIDFromInt(12345)
|
||||
nodeID := [33]byte{0x02}
|
||||
|
||||
// The zero-value ForwardingInfo must denote the exit hop, since its
|
||||
// NextHop is a Left equal to hop.Exit. Callers rely on this to detect
|
||||
// that we are the final recipient.
|
||||
zero := ForwardingInfo{}
|
||||
require.True(t, zero.IsExit(), "zero value must be the exit hop")
|
||||
require.Equal(
|
||||
t, fn.Some(Exit), zero.NextHopChannel(),
|
||||
"zero value must expose the Exit channel",
|
||||
)
|
||||
|
||||
// An explicit channel next hop equal to Exit is likewise the exit hop.
|
||||
exit := ForwardingInfo{NextHop: NewChannelNextHop(Exit)}
|
||||
require.True(t, exit.IsExit())
|
||||
|
||||
// A channel next hop with a real SCID is a forward, and exposes that
|
||||
// SCID through NextHopChannel.
|
||||
channel := ForwardingInfo{NextHop: NewChannelNextHop(scid)}
|
||||
require.False(t, channel.IsExit())
|
||||
require.Equal(t, fn.Some(scid), channel.NextHopChannel())
|
||||
|
||||
// A node-ID next hop is always a forward and never exposes an outgoing
|
||||
// channel, since the switch selects one via non-strict forwarding.
|
||||
node := ForwardingInfo{NextHop: NewNodeNextHop(nodeID)}
|
||||
require.False(t, node.IsExit())
|
||||
require.Equal(
|
||||
t, fn.None[lnwire.ShortChannelID](), node.NextHopChannel(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ func hopFromPayload(p *Payload) (*route.Hop, uint64) {
|
|||
BlindingPoint: p.blindingPoint,
|
||||
CustomRecords: p.customRecords,
|
||||
TotalAmtMsat: p.totalAmtMsat,
|
||||
}, p.FwdInfo.NextHop.ToUint64()
|
||||
}, p.FwdInfo.NextHop.UnwrapLeftOr(Exit).ToUint64()
|
||||
}
|
||||
|
||||
// FuzzPayloadFinal fuzzes final hop payloads, providing the additional context
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
sphinx "github.com/lightningnetwork/lightning-onion"
|
||||
"github.com/lightningnetwork/lnd/fn/v2"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/record"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
|
|
@ -231,6 +232,13 @@ func parseAndValidateRecipientData(r *sphinxHopIterator, payload *Payload,
|
|||
return nil, routeRole, err
|
||||
}
|
||||
|
||||
// BOLT 4 requires a blinded hop to set exactly one of short_channel_id
|
||||
// or next_node_id. Reject a hop that sets both here.
|
||||
if routeData.ShortChannelID.IsSome() && routeData.NextNodeID.IsSome() {
|
||||
return nil, routeRole, fmt.Errorf("blinded hop sets both " +
|
||||
"short channel ID and next node ID")
|
||||
}
|
||||
|
||||
// This is the final node in the blinded route.
|
||||
if isFinal {
|
||||
return deriveBlindedRouteFinalHopForwardingInfo(
|
||||
|
|
@ -318,14 +326,35 @@ func deriveBlindedRouteForwardingInfo(r *sphinxHopIterator,
|
|||
)
|
||||
}
|
||||
|
||||
nextSCID, err := routeData.ShortChannelID.UnwrapOrErr(
|
||||
fmt.Errorf("next SCID not set for non-final blinded hop"),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, routeRole, err
|
||||
// Determine the next hop. The recipient identifies it either by a short
|
||||
// channel ID (the common case) or, as some implementations do for
|
||||
// blinded routes, by the next node's ID (next_node_id). Setting both is
|
||||
// already rejected upstream, and the dummy hop check above has handled
|
||||
// a next_node_id that points at us.
|
||||
var nextHop fn.Either[lnwire.ShortChannelID, [33]byte]
|
||||
switch {
|
||||
case routeData.ShortChannelID.IsSome():
|
||||
scid := routeData.ShortChannelID.UnwrapOr(
|
||||
routeData.ShortChannelID.Zero(),
|
||||
)
|
||||
nextHop = NewChannelNextHop(scid.Val)
|
||||
|
||||
case routeData.NextNodeID.IsSome():
|
||||
nodeID := routeData.NextNodeID.UnwrapOr(
|
||||
routeData.NextNodeID.Zero(),
|
||||
)
|
||||
var pubKey [33]byte
|
||||
copy(pubKey[:], nodeID.Val.SerializeCompressed())
|
||||
|
||||
nextHop = NewNodeNextHop(pubKey)
|
||||
|
||||
default:
|
||||
return nil, routeRole, fmt.Errorf("next hop not set for " +
|
||||
"non-final blinded hop")
|
||||
}
|
||||
|
||||
payload.FwdInfo = ForwardingInfo{
|
||||
NextHop: nextSCID.Val,
|
||||
NextHop: nextHop,
|
||||
AmountToForward: fwdAmt,
|
||||
OutgoingCLTV: r.blindingKit.IncomingCltv - uint32(
|
||||
relayInfo.Val.CltvExpiryDelta,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
sphinx "github.com/lightningnetwork/lightning-onion"
|
||||
"github.com/lightningnetwork/lnd/fn/v2"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/record"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
|
|
@ -33,7 +34,9 @@ func TestSphinxHopIteratorForwardingInstructions(t *testing.T) {
|
|||
// extract each type, no matter the payload type.
|
||||
nextAddrInt := binary.BigEndian.Uint64(hopData.NextAddress[:])
|
||||
expectedFwdInfo := ForwardingInfo{
|
||||
NextHop: lnwire.NewShortChanIDFromInt(nextAddrInt),
|
||||
NextHop: NewChannelNextHop(
|
||||
lnwire.NewShortChanIDFromInt(nextAddrInt),
|
||||
),
|
||||
AmountToForward: lnwire.MilliSatoshi(hopData.ForwardAmount),
|
||||
OutgoingCLTV: hopData.OutgoingCltv,
|
||||
}
|
||||
|
|
@ -303,3 +306,378 @@ func TestParseAndValidateRecipientData(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestDeriveBlindedRouteNextHop asserts how a non-final blinded hop's next hop
|
||||
// is derived from the recipient data: a short channel ID becomes a Left, a
|
||||
// next_node_id becomes a Right, having both set is rejected with an error, and
|
||||
// the absence of both is also an error.
|
||||
func TestDeriveBlindedRouteNextHop(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
nodeKey, err := btcec.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
nextNodeKey, err := btcec.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
nextNodePub := nextNodeKey.PubKey()
|
||||
|
||||
var nextNodeRaw [33]byte
|
||||
copy(nextNodeRaw[:], nextNodePub.SerializeCompressed())
|
||||
|
||||
scid := lnwire.NewShortChanIDFromInt(1500)
|
||||
|
||||
relayInfo := tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType10](
|
||||
record.PaymentRelayInfo{
|
||||
CltvExpiryDelta: 10,
|
||||
BaseFee: 100,
|
||||
FeeRate: 0,
|
||||
},
|
||||
))
|
||||
constraints := tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType12](
|
||||
record.PaymentConstraints{
|
||||
MaxCltvExpiry: 1000,
|
||||
HtlcMinimumMsat: lnwire.MilliSatoshi(1),
|
||||
},
|
||||
))
|
||||
scidRecord := tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType2](scid))
|
||||
nodeIDRecord := tlv.SomeRecordT(
|
||||
tlv.NewPrimitiveRecord[tlv.TlvType4](nextNodePub),
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
data *record.BlindedRouteData
|
||||
expectedHop fn.Either[lnwire.ShortChannelID, [33]byte]
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "short channel id only",
|
||||
data: &record.BlindedRouteData{
|
||||
ShortChannelID: scidRecord,
|
||||
RelayInfo: relayInfo,
|
||||
Constraints: constraints,
|
||||
},
|
||||
expectedHop: NewChannelNextHop(scid),
|
||||
},
|
||||
{
|
||||
name: "next node id only",
|
||||
data: &record.BlindedRouteData{
|
||||
NextNodeID: nodeIDRecord,
|
||||
RelayInfo: relayInfo,
|
||||
Constraints: constraints,
|
||||
},
|
||||
expectedHop: NewNodeNextHop(nextNodeRaw),
|
||||
},
|
||||
{
|
||||
// BOLT 4 requires a non-final blinded hop to set
|
||||
// exactly one of short_channel_id or next_node_id, so
|
||||
// setting both must be rejected.
|
||||
name: "both present is an error",
|
||||
data: &record.BlindedRouteData{
|
||||
ShortChannelID: scidRecord,
|
||||
NextNodeID: nodeIDRecord,
|
||||
RelayInfo: relayInfo,
|
||||
Constraints: constraints,
|
||||
},
|
||||
expectedErr: "both short channel ID and next node ID",
|
||||
},
|
||||
{
|
||||
name: "neither present",
|
||||
data: &record.BlindedRouteData{
|
||||
RelayInfo: relayInfo,
|
||||
Constraints: constraints,
|
||||
},
|
||||
expectedErr: "next hop not set",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range tests {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data, err := record.EncodeBlindedRouteData(
|
||||
testCase.data,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
kit := BlindingKit{
|
||||
Processor: &mockProcessor{},
|
||||
IncomingAmount: 10000,
|
||||
IncomingCltv: 500,
|
||||
UpdateAddBlinding: tlv.SomeRecordT(
|
||||
//nolint:ll
|
||||
tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType](&btcec.PublicKey{}),
|
||||
),
|
||||
}
|
||||
iterator := &sphinxHopIterator{
|
||||
blindingKit: kit,
|
||||
router: sphinx.NewRouter(
|
||||
&sphinx.PrivKeyECDH{PrivKey: nodeKey},
|
||||
sphinx.NewMemoryReplayLog(),
|
||||
),
|
||||
}
|
||||
|
||||
payload, _, err := parseAndValidateRecipientData(
|
||||
iterator, &Payload{encryptedData: data},
|
||||
false, RouteRoleCleartext,
|
||||
)
|
||||
|
||||
if testCase.expectedErr != "" {
|
||||
require.ErrorContains(
|
||||
t, err, testCase.expectedErr,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(
|
||||
t, testCase.expectedHop,
|
||||
payload.FwdInfo.NextHop,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestBlindedHopBothNextHopFieldsRejected asserts that a blinded hop setting
|
||||
// both short_channel_id and next_node_id is rejected for a final hop and for a
|
||||
// dummy hop (next_node_id == our own pubkey), not just an intermediate hop. The
|
||||
// mutual-exclusivity check runs before the final-hop and dummy-hop branches, so
|
||||
// none of them accept a hop that violates BOLT 4. The intermediate case is
|
||||
// already covered by TestDeriveBlindedRouteNextHop.
|
||||
func TestBlindedHopBothNextHopFieldsRejected(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
nodeKey, err := btcec.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
nodePub := nodeKey.PubKey()
|
||||
|
||||
// Route data that sets both short_channel_id and next_node_id. The node
|
||||
// ID is our own pubkey, which for a non-final hop would otherwise
|
||||
// signal a dummy hop; the both-set check must still fire first.
|
||||
bothData := &record.BlindedRouteData{
|
||||
ShortChannelID: tlv.SomeRecordT(
|
||||
tlv.NewRecordT[tlv.TlvType2](
|
||||
lnwire.NewShortChanIDFromInt(1500),
|
||||
),
|
||||
),
|
||||
NextNodeID: tlv.SomeRecordT(
|
||||
tlv.NewPrimitiveRecord[tlv.TlvType4](nodePub),
|
||||
),
|
||||
RelayInfo: tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType10](
|
||||
record.PaymentRelayInfo{
|
||||
CltvExpiryDelta: 10,
|
||||
BaseFee: 100,
|
||||
FeeRate: 0,
|
||||
},
|
||||
)),
|
||||
Constraints: tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType12](
|
||||
record.PaymentConstraints{
|
||||
MaxCltvExpiry: 1000,
|
||||
HtlcMinimumMsat: lnwire.MilliSatoshi(1),
|
||||
},
|
||||
)),
|
||||
}
|
||||
data, err := record.EncodeBlindedRouteData(bothData)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Both the dummy/forwarding path (isFinal=false, next_node_id points at
|
||||
// us) and the final path (isFinal=true) must reject the hop.
|
||||
for _, isFinal := range []bool{false, true} {
|
||||
name := "forwarding hop"
|
||||
if isFinal {
|
||||
name = "final hop"
|
||||
}
|
||||
|
||||
t.Run(name, func(t *testing.T) {
|
||||
kit := BlindingKit{
|
||||
Processor: &mockProcessor{},
|
||||
IncomingAmount: 10000,
|
||||
IncomingCltv: 500,
|
||||
UpdateAddBlinding: tlv.SomeRecordT(
|
||||
//nolint:ll
|
||||
tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType](&btcec.PublicKey{}),
|
||||
),
|
||||
}
|
||||
iterator := &sphinxHopIterator{
|
||||
blindingKit: kit,
|
||||
router: sphinx.NewRouter(
|
||||
&sphinx.PrivKeyECDH{PrivKey: nodeKey},
|
||||
sphinx.NewMemoryReplayLog(),
|
||||
),
|
||||
}
|
||||
|
||||
_, _, err := parseAndValidateRecipientData(
|
||||
iterator, &Payload{encryptedData: data},
|
||||
isFinal, RouteRoleCleartext,
|
||||
)
|
||||
require.ErrorContains(
|
||||
t, err,
|
||||
"both short channel ID and next node ID",
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestBlindedRouteDummyHopPeeledLocally asserts that a blinded route hop where
|
||||
// next_node_id is our own public key is recognized as a dummy hop and is peeled
|
||||
// locally rather than falling through to the generic next_node_id forwarding
|
||||
// branch.
|
||||
func TestBlindedRouteDummyHopPeeledLocally(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Construct a realistic onion packet that contains a blinded final hop.
|
||||
// We'll use this to test that we can peel a dummy hop locally and
|
||||
// extract the forwarding information from the decrypted final hop's
|
||||
// payload.
|
||||
nodeKey, err := btcec.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
nodePub := nodeKey.PubKey()
|
||||
|
||||
relayInfo := tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType10](
|
||||
record.PaymentRelayInfo{
|
||||
CltvExpiryDelta: 10,
|
||||
BaseFee: 100,
|
||||
FeeRate: 0,
|
||||
},
|
||||
))
|
||||
constraints := tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType12](
|
||||
record.PaymentConstraints{
|
||||
MaxCltvExpiry: 1000,
|
||||
HtlcMinimumMsat: lnwire.MilliSatoshi(1),
|
||||
},
|
||||
))
|
||||
|
||||
// Set next_node_id to our own public key. This signals a dummy hop.
|
||||
nodeIDRecord := tlv.SomeRecordT(
|
||||
tlv.NewPrimitiveRecord[tlv.TlvType4](nodePub),
|
||||
)
|
||||
|
||||
// We'll generate a valid, cryptographically blinded final hop's payload
|
||||
// using sphinx.BuildBlindedPath. This contains the PathID.
|
||||
secret := make([]byte, 32)
|
||||
secret[0] = 2
|
||||
finalHopData := &record.BlindedRouteData{
|
||||
PathID: tlv.SomeRecordT(
|
||||
tlv.NewPrimitiveRecord[tlv.TlvType6](secret),
|
||||
),
|
||||
}
|
||||
finalHopDataBytes, err := record.EncodeBlindedRouteData(finalHopData)
|
||||
require.NoError(t, err)
|
||||
|
||||
hopInfo := &sphinx.HopInfo{
|
||||
NodePub: nodePub,
|
||||
PlainText: finalHopDataBytes,
|
||||
}
|
||||
|
||||
blindingSessionKey, err := btcec.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
blindedPathInfo, err := sphinx.BuildBlindedPath(
|
||||
blindingSessionKey, []*sphinx.HopInfo{hopInfo},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Since we are peeling a dummy hop locally, we want the next blinding
|
||||
// override to be the blinding point generated for our blinded final
|
||||
// hop.
|
||||
dummyHopData := &record.BlindedRouteData{
|
||||
NextNodeID: nodeIDRecord,
|
||||
RelayInfo: relayInfo,
|
||||
Constraints: constraints,
|
||||
NextBlindingOverride: tlv.SomeRecordT(
|
||||
tlv.NewPrimitiveRecord[tlv.TlvType8](
|
||||
blindedPathInfo.Path.BlindingPoint,
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
data, err := record.EncodeBlindedRouteData(dummyHopData)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Encode a valid TLV payload for the next hop (which we will peel).
|
||||
var hop2Buffer bytes.Buffer
|
||||
amt := uint64(10000)
|
||||
cltv := uint32(500)
|
||||
encryptedDataRecord := record.NewEncryptedDataRecord(
|
||||
&blindedPathInfo.Path.BlindedHops[0].CipherText,
|
||||
)
|
||||
tlvRecords := []tlv.Record{
|
||||
record.NewAmtToFwdRecord(&amt),
|
||||
record.NewLockTimeRecord(&cltv),
|
||||
encryptedDataRecord,
|
||||
}
|
||||
tlvStream, err := tlv.NewStream(tlvRecords...)
|
||||
require.NoError(t, err)
|
||||
err = tlvStream.Encode(&hop2Buffer)
|
||||
require.NoError(t, err)
|
||||
|
||||
hopPayload, err := sphinx.NewTLVHopPayload(hop2Buffer.Bytes())
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a valid 1-hop onion path using our blinded public key.
|
||||
var paymentPath sphinx.PaymentPath
|
||||
paymentPath[0] = sphinx.OnionHop{
|
||||
NodePub: *blindedPathInfo.Path.BlindedHops[0].BlindedNodePub,
|
||||
HopPayload: hopPayload,
|
||||
}
|
||||
|
||||
sessionKey, err := btcec.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
rHash := [32]byte{1}
|
||||
|
||||
// Generate a cryptographically valid onion packet for this path.
|
||||
onionPacket, err := sphinx.NewOnionPacket(
|
||||
&paymentPath, sessionKey, rHash[:],
|
||||
sphinx.DeterministicPacketFiller,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Simulate an incoming HTLC with a blinding point and a valid onion
|
||||
// packet. The blinding point is used to decrypt the dummy hop's
|
||||
// payload, which contains the blinding point for the next hop (the
|
||||
// blinded final hop).
|
||||
kit := BlindingKit{
|
||||
Processor: &mockProcessor{},
|
||||
IncomingAmount: 12000,
|
||||
IncomingCltv: 510,
|
||||
UpdateAddBlinding: tlv.SomeRecordT(
|
||||
tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType](
|
||||
nodePub,
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
iterator := &sphinxHopIterator{
|
||||
blindingKit: kit,
|
||||
rHash: rHash[:],
|
||||
router: sphinx.NewRouter(
|
||||
&sphinx.PrivKeyECDH{PrivKey: nodeKey},
|
||||
sphinx.NewMemoryReplayLog(),
|
||||
),
|
||||
// Set our valid onion packet to be peeled.
|
||||
processedPacket: &sphinx.ProcessedPacket{
|
||||
NextPacket: onionPacket,
|
||||
},
|
||||
}
|
||||
|
||||
// When we parse and validate the recipient data, it should enter the
|
||||
// dummy-hop peeling path. Since our onion packet is valid and matches
|
||||
// our private key, it should be successfully peeled and parsed.
|
||||
pld, _, err := parseAndValidateRecipientData(
|
||||
iterator, &Payload{encryptedData: data},
|
||||
false, RouteRoleCleartext,
|
||||
)
|
||||
|
||||
// Assert that we successfully peeled the dummy hop and extracted the
|
||||
// decrypted final payload.
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, pld)
|
||||
|
||||
fwdInfo := pld.ForwardingInfo()
|
||||
require.Equal(t, lnwire.MilliSatoshi(0), fwdInfo.AmountToForward)
|
||||
require.Equal(t, uint32(0), fwdInfo.OutgoingCLTV)
|
||||
require.NotNil(t, fwdInfo.PathID)
|
||||
require.Equal(t, secret, fwdInfo.PathID[:])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,7 +126,9 @@ func NewLegacyPayload(f *sphinx.HopData) *Payload {
|
|||
|
||||
return &Payload{
|
||||
FwdInfo: ForwardingInfo{
|
||||
NextHop: lnwire.NewShortChanIDFromInt(nextHop),
|
||||
NextHop: NewChannelNextHop(
|
||||
lnwire.NewShortChanIDFromInt(nextHop),
|
||||
),
|
||||
AmountToForward: lnwire.MilliSatoshi(f.ForwardAmount),
|
||||
OutgoingCLTV: f.OutgoingCltv,
|
||||
},
|
||||
|
|
@ -201,7 +203,9 @@ func ParseTLVPayload(r io.Reader) (*Payload, map[tlv.Type][]byte, error) {
|
|||
|
||||
return &Payload{
|
||||
FwdInfo: ForwardingInfo{
|
||||
NextHop: lnwire.NewShortChanIDFromInt(cid),
|
||||
NextHop: NewChannelNextHop(
|
||||
lnwire.NewShortChanIDFromInt(cid),
|
||||
),
|
||||
AmountToForward: lnwire.MilliSatoshi(amt),
|
||||
OutgoingCLTV: cltv,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -466,6 +466,14 @@ func getEventType(pkt *htlcPacket) HtlcEventType {
|
|||
case pkt.incomingChanID == hop.Source:
|
||||
return HtlcEventTypeSend
|
||||
|
||||
// A node-ID (pubkey) next hop has no outgoing SCID until the switch
|
||||
// selects one, so outgoingChanID may still be hop.Exit on an early
|
||||
// failure. Such a hop is always a forward, never the exit, so classify
|
||||
// it before the hop.Exit check to avoid reporting a forward as a
|
||||
// receive.
|
||||
case pkt.outgoingHop.IsRight():
|
||||
return HtlcEventTypeForward
|
||||
|
||||
case pkt.outgoingChanID == hop.Exit:
|
||||
return HtlcEventTypeReceive
|
||||
|
||||
|
|
|
|||
139
htlcswitch/htlcnotifier_test.go
Normal file
139
htlcswitch/htlcnotifier_test.go
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
package htlcswitch
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/lightningnetwork/lnd/htlcswitch/hop"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestGetEventType asserts how getEventType classifies an htlcPacket as a send,
|
||||
// receive or forward event.
|
||||
func TestGetEventType(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var nodeID [33]byte
|
||||
nodeID[0] = 0x02
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
pkt *htlcPacket
|
||||
want HtlcEventType
|
||||
}{
|
||||
{
|
||||
name: "send",
|
||||
pkt: &htlcPacket{incomingChanID: hop.Source},
|
||||
want: HtlcEventTypeSend,
|
||||
},
|
||||
{
|
||||
name: "receive at exit hop",
|
||||
pkt: &htlcPacket{
|
||||
incomingChanID: lnwire.NewShortChanIDFromInt(1),
|
||||
outgoingChanID: hop.Exit,
|
||||
},
|
||||
want: HtlcEventTypeReceive,
|
||||
},
|
||||
{
|
||||
name: "forward by channel ID",
|
||||
pkt: &htlcPacket{
|
||||
incomingChanID: lnwire.NewShortChanIDFromInt(1),
|
||||
outgoingChanID: lnwire.NewShortChanIDFromInt(2),
|
||||
},
|
||||
want: HtlcEventTypeForward,
|
||||
},
|
||||
{
|
||||
// A node-ID forward that failed before channel
|
||||
// selection has outgoingChanID == hop.Exit but a Right
|
||||
// (pubkey) next hop, so it must classify as a forward.
|
||||
name: "forward by node ID before selection",
|
||||
pkt: &htlcPacket{
|
||||
incomingChanID: lnwire.NewShortChanIDFromInt(1),
|
||||
outgoingChanID: hop.Exit,
|
||||
outgoingHop: hop.NewNodeNextHop(nodeID),
|
||||
},
|
||||
want: HtlcEventTypeForward,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
require.Equal(t, tc.want, getEventType(tc.pkt))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetEventTypeNodeIDReconstructedPackets asserts that node-ID forward
|
||||
// packets reconstructed via failAddPacket and interceptedForward.resolve
|
||||
// preserve outgoingHop and are correctly classified as HtlcEventTypeForward by
|
||||
// getEventType.
|
||||
func TestGetEventTypeNodeIDReconstructedPackets(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var nodeID [33]byte
|
||||
nodeID[0] = 0x02
|
||||
|
||||
inChanID := lnwire.NewShortChanIDFromInt(1)
|
||||
chanID := lnwire.ChannelID{1}
|
||||
|
||||
// Create a switch with a mailOrchestrator and mailbox.
|
||||
s := &Switch{
|
||||
mailOrchestrator: newMailOrchestrator(&mailOrchConfig{}),
|
||||
}
|
||||
mailbox := s.mailOrchestrator.GetOrCreateMailBox(chanID, inChanID)
|
||||
s.mailOrchestrator.BindLiveShortChanID(mailbox, chanID, inChanID)
|
||||
|
||||
// 1. Verify failAddPacket reconstruction.
|
||||
origPkt := &htlcPacket{
|
||||
incomingChanID: inChanID,
|
||||
incomingHTLCID: 42,
|
||||
outgoingChanID: hop.Exit,
|
||||
outgoingHop: hop.NewNodeNextHop(nodeID),
|
||||
obfuscator: NewMockObfuscator(),
|
||||
}
|
||||
linkErr := NewLinkError(&lnwire.FailUnknownNextPeer{})
|
||||
|
||||
err := s.failAddPacket(origPkt, linkErr)
|
||||
require.Equal(t, linkErr, err)
|
||||
|
||||
select {
|
||||
case failPkt := <-mailbox.PacketOutBox():
|
||||
require.True(t, failPkt.outgoingHop.IsRight())
|
||||
require.Equal(
|
||||
t, HtlcEventTypeForward, getEventType(failPkt),
|
||||
"failAddPacket must classify as forward",
|
||||
)
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("failAddPacket did not deliver packet to mailbox")
|
||||
}
|
||||
|
||||
// 2. Verify interceptedForward.resolve reconstruction.
|
||||
resolvePkt := &htlcPacket{
|
||||
incomingChanID: inChanID,
|
||||
incomingHTLCID: 43,
|
||||
outgoingChanID: hop.Exit,
|
||||
outgoingHop: hop.NewNodeNextHop(nodeID),
|
||||
obfuscator: NewMockObfuscator(),
|
||||
}
|
||||
fwd := &interceptedForward{
|
||||
htlcSwitch: s,
|
||||
packet: resolvePkt,
|
||||
}
|
||||
|
||||
err = fwd.resolve(&lnwire.UpdateFailHTLC{})
|
||||
require.NoError(t, err)
|
||||
|
||||
select {
|
||||
case resPkt := <-mailbox.PacketOutBox():
|
||||
require.True(t, resPkt.outgoingHop.IsRight())
|
||||
require.Equal(
|
||||
t, HtlcEventTypeForward, getEventType(resPkt),
|
||||
"interceptedForward.resolve must classify as forward",
|
||||
)
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("resolve did not deliver packet to mailbox")
|
||||
}
|
||||
}
|
||||
|
|
@ -705,6 +705,7 @@ func (f *interceptedForward) Packet() InterceptedPacket {
|
|||
HtlcID: f.packet.incomingHTLCID,
|
||||
},
|
||||
OutgoingChanID: f.packet.outgoingChanID,
|
||||
OutgoingNodeID: f.packet.outgoingHop.RightToSome(),
|
||||
Hash: f.htlc.PaymentHash,
|
||||
OutgoingExpiry: f.htlc.Expiry,
|
||||
OutgoingAmount: f.htlc.Amount,
|
||||
|
|
@ -891,6 +892,7 @@ func (f *interceptedForward) resolve(message lnwire.Message) error {
|
|||
incomingChanID: f.packet.incomingChanID,
|
||||
incomingHTLCID: f.packet.incomingHTLCID,
|
||||
outgoingChanID: f.packet.outgoingChanID,
|
||||
outgoingHop: f.packet.outgoingHop,
|
||||
outgoingHTLCID: f.packet.outgoingHTLCID,
|
||||
isResolution: true,
|
||||
circuit: f.packet.circuit,
|
||||
|
|
|
|||
|
|
@ -381,6 +381,14 @@ type InterceptableHtlcForwarder interface {
|
|||
// and resolve it later or let the switch execute its default behavior.
|
||||
type ForwardInterceptor func(InterceptedPacket) error
|
||||
|
||||
// NodeIDForwardSCID is the sentinel outgoing SCID reported to HTLC interceptor
|
||||
// clients (at the RPC boundary) for a next hop identified by node ID (BOLT 4
|
||||
// next_node_id) rather than by channel. All bits are set, an out-of-range value
|
||||
// that can never match a real or alias channel, so a client switching on a zero
|
||||
// SCID to detect the exit hop does not read the forward as a final receive. The
|
||||
// pubkey is in InterceptedPacket.OutgoingNodeID.
|
||||
const NodeIDForwardSCID uint64 = ^uint64(0)
|
||||
|
||||
// InterceptedPacket contains the relevant information for the interceptor about
|
||||
// an HTLC.
|
||||
type InterceptedPacket struct {
|
||||
|
|
@ -388,9 +396,17 @@ type InterceptedPacket struct {
|
|||
// packet.
|
||||
IncomingCircuit models.CircuitKey
|
||||
|
||||
// OutgoingChanID is the destination channel for this packet.
|
||||
// OutgoingChanID is the destination channel for this packet. For a
|
||||
// node-ID next hop with no concrete channel known yet it is hop.Exit
|
||||
// and OutgoingNodeID holds the pubkey; the RPC layer maps that to the
|
||||
// NodeIDForwardSCID sentinel before reporting it to a client.
|
||||
OutgoingChanID lnwire.ShortChannelID
|
||||
|
||||
// OutgoingNodeID is the next hop's compressed pubkey for a blinded
|
||||
// route that identifies it by node ID (next_node_id). None in the
|
||||
// common channel-ID case.
|
||||
OutgoingNodeID fn.Option[[33]byte]
|
||||
|
||||
// Hash is the payment hash of the htlc.
|
||||
Hash lntypes.Hash
|
||||
|
||||
|
|
|
|||
|
|
@ -2635,7 +2635,10 @@ func (l *channelLink) canSendHtlc(policy models.ForwardingPolicy,
|
|||
htlcBlob = fn.Some(blob)
|
||||
}
|
||||
|
||||
return l.AuxBandwidth(amt, originalScid, htlcBlob, ts)
|
||||
// Check if this link can handle the traffic.
|
||||
return l.AuxBandwidth(
|
||||
amt, l.ShortChanID(), htlcBlob, ts,
|
||||
)
|
||||
},
|
||||
).Unpack()
|
||||
if externalErr != nil {
|
||||
|
|
@ -3153,8 +3156,8 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg) {
|
|||
continue
|
||||
}
|
||||
|
||||
switch fwdInfo.NextHop {
|
||||
case hop.Exit:
|
||||
switch {
|
||||
case fwdInfo.IsExit():
|
||||
err := l.processExitHop(
|
||||
add, sourceRef, obfuscator, fwdInfo,
|
||||
heightNow, pld,
|
||||
|
|
@ -3232,7 +3235,8 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg) {
|
|||
updatePacket := &htlcPacket{
|
||||
incomingChanID: l.ShortChanID(),
|
||||
incomingHTLCID: add.ID,
|
||||
outgoingChanID: fwdInfo.NextHop,
|
||||
outgoingChanID: fwdInfo.NextHopChannel().UnwrapOr(hop.Exit),
|
||||
outgoingHop: fwdInfo.NextHop,
|
||||
sourceRef: &sourceRef,
|
||||
incomingAmount: add.Amount,
|
||||
amount: outgoingAdd.Amount,
|
||||
|
|
@ -3309,7 +3313,8 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg) {
|
|||
updatePacket := &htlcPacket{
|
||||
incomingChanID: l.ShortChanID(),
|
||||
incomingHTLCID: add.ID,
|
||||
outgoingChanID: fwdInfo.NextHop,
|
||||
outgoingChanID: fwdInfo.NextHopChannel().UnwrapOr(hop.Exit),
|
||||
outgoingHop: fwdInfo.NextHop,
|
||||
sourceRef: &sourceRef,
|
||||
incomingAmount: add.Amount,
|
||||
amount: addMsg.Amount,
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import (
|
|||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/ticker"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
|
@ -776,8 +777,9 @@ func testChannelLinkInboundFee(t *testing.T, //nolint:thelper
|
|||
hops := []*hop.Payload{
|
||||
{
|
||||
FwdInfo: hop.ForwardingInfo{
|
||||
NextHop: n.carolChannelLink.
|
||||
ShortChanID(),
|
||||
NextHop: hop.NewChannelNextHop(
|
||||
n.carolChannelLink.ShortChanID(),
|
||||
),
|
||||
AmountToForward: 1_000_000,
|
||||
OutgoingCLTV: 106,
|
||||
},
|
||||
|
|
@ -6394,6 +6396,134 @@ func TestCheckHtlcForward(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
// recordingAuxShaper is a minimal AuxTrafficShaper that records the channel id
|
||||
// it is asked about and declines to handle the traffic, so the normal
|
||||
// forwarding path proceeds. Only the methods reached by CheckHtlcForward are
|
||||
// implemented; the rest are inherited from the embedded (nil) interface and
|
||||
// must never be called.
|
||||
type recordingAuxShaper struct {
|
||||
AuxTrafficShaper
|
||||
|
||||
gotCID lnwire.ShortChannelID
|
||||
}
|
||||
|
||||
// ShouldHandleTraffic records the short channel ID passed to the shaper.
|
||||
func (a *recordingAuxShaper) ShouldHandleTraffic(cid lnwire.ShortChannelID,
|
||||
_, _ fn.Option[tlv.Blob]) (bool, error) {
|
||||
|
||||
a.gotCID = cid
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// IsCustomHTLC returns false as recordingAuxShaper handles standard HTLCs.
|
||||
func (a *recordingAuxShaper) IsCustomHTLC(_ lnwire.CustomRecords) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// TestCheckHtlcForwardAuxShaperChannel asserts that during non-strict
|
||||
// forwarding the aux traffic shaper is keyed on the channel actually being
|
||||
// evaluated (the link's own SCID), not the sender-requested SCID, which fixes
|
||||
// both the node-ID/blinded path (where no SCID is requested) and pre-existing
|
||||
// parallel-channel forwarding. It also asserts the real SCID handed to the
|
||||
// shaper never leaks into the sender-facing channel_update, which continues to
|
||||
// reference the requested (alias) SCID.
|
||||
func TestCheckHtlcForwardAuxShaperChannel(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
chanScid = 42
|
||||
requestedScid = 99
|
||||
)
|
||||
|
||||
fetchLastChannelUpdate := func(lnwire.ShortChannelID) (
|
||||
*lnwire.ChannelUpdate1, error) {
|
||||
|
||||
return &lnwire.ChannelUpdate1{}, nil
|
||||
}
|
||||
|
||||
// Record the SCID used to build the returned channel_update on failure.
|
||||
var updateScid lnwire.ShortChannelID
|
||||
failAliasUpdate := func(sid lnwire.ShortChannelID,
|
||||
incoming bool) *lnwire.ChannelUpdate1 {
|
||||
|
||||
updateScid = sid
|
||||
|
||||
return &lnwire.ChannelUpdate1{
|
||||
ShortChannelID: sid,
|
||||
}
|
||||
}
|
||||
|
||||
testChannel, _, err := createTestChannel(
|
||||
t, alicePrivKey, bobPrivKey, 100000, 100000, 1000, 1000,
|
||||
lnwire.NewShortChanIDFromInt(chanScid),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
shaper := &recordingAuxShaper{}
|
||||
link := channelLink{
|
||||
cfg: ChannelLinkConfig{
|
||||
FwrdingPolicy: models.ForwardingPolicy{
|
||||
TimeLockDelta: 20,
|
||||
MinHTLCOut: 500,
|
||||
MaxHTLC: 1000,
|
||||
BaseFee: 10,
|
||||
},
|
||||
FetchLastChannelUpdate: fetchLastChannelUpdate,
|
||||
MaxOutgoingCltvExpiry: DefaultMaxOutgoingCltvExpiry,
|
||||
HtlcNotifier: &mockHTLCNotifier{},
|
||||
},
|
||||
log: log,
|
||||
channel: testChannel.channel,
|
||||
}
|
||||
link.cfg.AuxTrafficShaper = fn.Some[AuxTrafficShaper](shaper)
|
||||
link.attachFailAliasUpdate(failAliasUpdate)
|
||||
|
||||
require.Equal(
|
||||
t, lnwire.NewShortChanIDFromInt(chanScid), link.ShortChanID(),
|
||||
)
|
||||
|
||||
var hash [32]byte
|
||||
requested := lnwire.NewShortChanIDFromInt(requestedScid)
|
||||
|
||||
// A satisfiable forward: the shaper must be queried about the channel
|
||||
// being evaluated (the link's own SCID), not the requested SCID.
|
||||
result := link.CheckHtlcForward(
|
||||
hash, 1500, 1000, 200, 150, models.InboundFee{}, 0, requested,
|
||||
nil,
|
||||
)
|
||||
require.Nil(t, result, "expected policy to be satisfied")
|
||||
require.Equal(
|
||||
t, link.ShortChanID(), shaper.gotCID,
|
||||
"aux shaper must be keyed on the evaluated channel",
|
||||
)
|
||||
require.NotEqual(
|
||||
t, requested, shaper.gotCID,
|
||||
"aux shaper must not be keyed on the requested SCID",
|
||||
)
|
||||
|
||||
// A failing forward: the returned channel_update must reference the
|
||||
// requested (alias) SCID, never the real channel SCID handed to the
|
||||
// shaper.
|
||||
result = link.CheckHtlcForward(
|
||||
hash, 100, 50, 200, 150, models.InboundFee{}, 0, requested, nil,
|
||||
)
|
||||
require.NotNil(t, result)
|
||||
require.Equal(
|
||||
t, requested, updateScid,
|
||||
"channel_update must reference the requested SCID, not the "+
|
||||
"real channel SCID",
|
||||
)
|
||||
|
||||
wireErr := result.WireMessage()
|
||||
failAmt, ok := wireErr.(*lnwire.FailAmountBelowMinimum)
|
||||
require.True(t, ok, "expected FailAmountBelowMinimum failure")
|
||||
require.Equal(
|
||||
t, requested, failAmt.Update.ShortChannelID,
|
||||
"failure update must carry the requested SCID",
|
||||
)
|
||||
}
|
||||
|
||||
// TestChannelLinkCanceledInvoice in this test checks the interaction
|
||||
// between Alice and Bob for a canceled invoice.
|
||||
func TestChannelLinkCanceledInvoice(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -699,12 +699,18 @@ func (m *memoryMailBox) FailAdd(pkt *htlcPacket) {
|
|||
reason lnwire.OpaqueReason
|
||||
)
|
||||
|
||||
// Create a temporary channel failure which we will send back to our
|
||||
// peer if this is a forward, or report to the user if the failed
|
||||
// payment was locally initiated.
|
||||
failure := m.cfg.failMailboxUpdate(
|
||||
pkt.originalOutgoingChanID, m.cfg.shortChanID,
|
||||
)
|
||||
var failure lnwire.FailureMessage
|
||||
if pkt.outgoingHop.IsRight() {
|
||||
// A node-ID next hop has no requested outgoing channel.
|
||||
// Returning a channel_update could leak a private channel's
|
||||
// SCID if the failure reason is persisted before blinding
|
||||
// error processing or replayed during channel reestablishment.
|
||||
failure = &lnwire.FailUnknownNextPeer{}
|
||||
} else {
|
||||
failure = m.cfg.failMailboxUpdate(
|
||||
pkt.originalOutgoingChanID, m.cfg.shortChanID,
|
||||
)
|
||||
}
|
||||
|
||||
// If the payment was locally initiated (which is indicated by a nil
|
||||
// obfuscator), we do not need to encrypt it back to the sender.
|
||||
|
|
@ -737,6 +743,8 @@ func (m *memoryMailBox) FailAdd(pkt *htlcPacket) {
|
|||
failPkt := &htlcPacket{
|
||||
incomingChanID: pkt.incomingChanID,
|
||||
incomingHTLCID: pkt.incomingHTLCID,
|
||||
outgoingChanID: pkt.outgoingChanID,
|
||||
outgoingHop: pkt.outgoingHop,
|
||||
circuit: pkt.circuit,
|
||||
sourceRef: pkt.sourceRef,
|
||||
hasSource: true,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/clock"
|
||||
"github.com/lightningnetwork/lnd/fn/v2"
|
||||
"github.com/lightningnetwork/lnd/lnmock"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
|
|
@ -276,6 +277,17 @@ func (c *mailboxContext) sendAdds(start, num int) []*htlcPacket {
|
|||
ID: uint64(start + i),
|
||||
},
|
||||
}
|
||||
if i%2 == 0 {
|
||||
pkt.outgoingHop = fn.NewLeft[
|
||||
lnwire.ShortChannelID, [33]byte,
|
||||
](pkt.outgoingChanID)
|
||||
} else {
|
||||
var nodeID [33]byte
|
||||
prand.Read(nodeID[:])
|
||||
pkt.outgoingHop = fn.NewRight[
|
||||
lnwire.ShortChannelID, [33]byte,
|
||||
](nodeID)
|
||||
}
|
||||
sentPackets[i] = pkt
|
||||
|
||||
err := c.mailbox.AddPacket(pkt)
|
||||
|
|
@ -313,6 +325,14 @@ func (c *mailboxContext) checkFails(adds []*htlcPacket) {
|
|||
select {
|
||||
case fail := <-c.forwards:
|
||||
if add.inKey() == fail.inKey() {
|
||||
require.Equal(
|
||||
c.t, add.outgoingChanID,
|
||||
fail.outgoingChanID,
|
||||
)
|
||||
require.Equal(
|
||||
c.t, add.outgoingHop,
|
||||
fail.outgoingHop,
|
||||
)
|
||||
continue
|
||||
}
|
||||
c.t.Fatalf("inkey mismatch #%d, add: %v vs fail: %v",
|
||||
|
|
@ -828,3 +848,54 @@ func TestMailOrchestrator(t *testing.T) {
|
|||
spew.Sdump(sentPackets), spew.Sdump(recvdPackets))
|
||||
}
|
||||
}
|
||||
|
||||
// TestMailBoxFailAddNodeID asserts that FailAdd for a node-ID hop returns a
|
||||
// FailUnknownNextPeer failure without a channel update.
|
||||
func TestMailBoxFailAddNodeID(t *testing.T) {
|
||||
ctx := newMailboxContext(t, time.Now(), time.Minute)
|
||||
|
||||
var nodeID [33]byte
|
||||
nodeID[0] = 0x02
|
||||
|
||||
pkt := &htlcPacket{
|
||||
incomingChanID: lnwire.NewShortChanIDFromInt(1),
|
||||
incomingHTLCID: 1,
|
||||
outgoingHop: fn.NewRight[lnwire.ShortChannelID, [33]byte](
|
||||
nodeID,
|
||||
),
|
||||
htlc: &lnwire.UpdateAddHTLC{
|
||||
ID: 1,
|
||||
},
|
||||
}
|
||||
|
||||
require.NoError(t, ctx.mailbox.AddPacket(pkt))
|
||||
|
||||
// Pull packet from mailbox to simulate link delivery.
|
||||
select {
|
||||
case <-ctx.mailbox.PacketOutBox():
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
t.Fatal("timeout waiting for packet outbox")
|
||||
}
|
||||
|
||||
// Fail the packet via FailAdd.
|
||||
ctx.mailbox.FailAdd(pkt)
|
||||
|
||||
select {
|
||||
case pktResponse := <-ctx.forwards:
|
||||
require.Equal(t, pkt.incomingChanID, pktResponse.incomingChanID)
|
||||
require.Equal(t, pkt.incomingHTLCID, pktResponse.incomingHTLCID)
|
||||
require.Equal(t, pkt.outgoingChanID, pktResponse.outgoingChanID)
|
||||
require.Equal(t, pkt.outgoingHop, pktResponse.outgoingHop)
|
||||
require.NotNil(t, pktResponse.linkFailure)
|
||||
|
||||
var unknownNextPeer *lnwire.FailUnknownNextPeer
|
||||
require.ErrorAs(
|
||||
t, pktResponse.linkFailure.WireMessage(),
|
||||
&unknownNextPeer,
|
||||
"expected FailUnknownNextPeer for node-ID FailAdd",
|
||||
)
|
||||
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
t.Fatal("timeout waiting for packet response")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -367,7 +367,13 @@ func (r *mockHopIterator) EncodeNextHop(w io.Writer) error {
|
|||
}
|
||||
|
||||
func encodeFwdInfo(w io.Writer, f *hop.ForwardingInfo) error {
|
||||
if err := binary.Write(w, binary.BigEndian, f.NextHop); err != nil {
|
||||
if f.NextHop.IsRight() {
|
||||
return fmt.Errorf("mock serialization does not support " +
|
||||
"node-ID next hop")
|
||||
}
|
||||
|
||||
nextHop := f.NextHopChannel().UnwrapOr(hop.Exit)
|
||||
if err := binary.Write(w, binary.BigEndian, nextHop); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -509,7 +515,8 @@ func (p *mockIteratorDecoder) DecodeHopIterator(r io.Reader, rHash []byte,
|
|||
}
|
||||
|
||||
var nextHopBytes [8]byte
|
||||
binary.BigEndian.PutUint64(nextHopBytes[:], f.NextHop.ToUint64())
|
||||
scid := f.NextHopChannel().UnwrapOr(hop.Exit)
|
||||
binary.BigEndian.PutUint64(nextHopBytes[:], scid.ToUint64())
|
||||
|
||||
hops[i] = hop.NewLegacyPayload(&sphinx.HopData{
|
||||
Realm: [1]byte{}, // hop.BitcoinNetwork
|
||||
|
|
@ -562,9 +569,11 @@ func (p *mockIteratorDecoder) DecodeHopIterators(id []byte,
|
|||
}
|
||||
|
||||
func decodeFwdInfo(r io.Reader, f *hop.ForwardingInfo) error {
|
||||
if err := binary.Read(r, binary.BigEndian, &f.NextHop); err != nil {
|
||||
var nextHop lnwire.ShortChannelID
|
||||
if err := binary.Read(r, binary.BigEndian, &nextHop); err != nil {
|
||||
return err
|
||||
}
|
||||
f.NextHop = hop.NewChannelNextHop(nextHop)
|
||||
|
||||
if err := binary.Read(r, binary.BigEndian, &f.AmountToForward); err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/fn/v2"
|
||||
"github.com/lightningnetwork/lnd/graph/db/models"
|
||||
"github.com/lightningnetwork/lnd/htlcswitch/hop"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
|
|
@ -18,9 +19,23 @@ type htlcPacket struct {
|
|||
incomingChanID lnwire.ShortChannelID
|
||||
|
||||
// outgoingChanID is the ID of the channel that we have offered or will
|
||||
// offer an outgoing HTLC on.
|
||||
// offer an outgoing HTLC on. It is mutable and may remain zero
|
||||
// (hop.Exit) until non-strict forwarding resolves a node-ID next hop to
|
||||
// a concrete channel, or may differ from the requested SCID after
|
||||
// non-strict load-balancing. A zero outgoingChanID alone does not imply
|
||||
// an exit hop: if outgoingHop is a Right (node ID), the HTLC is a
|
||||
// forward whose outgoing channel has not yet been selected.
|
||||
outgoingChanID lnwire.ShortChannelID
|
||||
|
||||
// outgoingHop carries the immutable next-hop instruction decoded from
|
||||
// the onion payload, following the same encoding as
|
||||
// hop.ForwardingInfo.NextHop. The three possible cases are:
|
||||
// 1. Left(scid) where scid != Exit: a channel-addressed forward.
|
||||
// 2. Right(pubkey): a node-addressed forward for a blinded route,
|
||||
// resolved to an active link via non-strict forwarding.
|
||||
// 3. Left(Exit): a final receive at the destination/receiver node.
|
||||
outgoingHop fn.Either[lnwire.ShortChannelID, [33]byte]
|
||||
|
||||
// incomingHTLCID is the ID of the HTLC that we have received from the peer
|
||||
// on the incoming channel.
|
||||
incomingHTLCID uint64
|
||||
|
|
@ -104,11 +119,10 @@ type htlcPacket struct {
|
|||
// in the incoming update_add_htlc wire message.
|
||||
inWireCustomRecords lnwire.CustomRecords
|
||||
|
||||
// originalOutgoingChanID is used when sending back failure messages.
|
||||
// It is only used for forwarded Adds on option_scid_alias channels.
|
||||
// This is to avoid possible confusion if a payer uses the public SCID
|
||||
// but receives a channel_update with the alias SCID. Instead, the
|
||||
// payer should receive a channel_update with the public SCID.
|
||||
// originalOutgoingChanID is used when sending back failure messages. It
|
||||
// retains the original sender-facing requested SCID for forwarded Adds,
|
||||
// including option_scid_alias channels. This prevents exposing the
|
||||
// evaluated link's concrete SCID or alias in channel_update failures.
|
||||
originalOutgoingChanID lnwire.ShortChannelID
|
||||
|
||||
// inboundFee is the fee schedule of the incoming channel.
|
||||
|
|
|
|||
|
|
@ -1250,6 +1250,7 @@ func (s *Switch) failAddPacket(packet *htlcPacket, failure *LinkError) error {
|
|||
incomingChanID: packet.incomingChanID,
|
||||
incomingHTLCID: packet.incomingHTLCID,
|
||||
outgoingChanID: packet.outgoingChanID,
|
||||
outgoingHop: packet.outgoingHop,
|
||||
outgoingHTLCID: packet.outgoingHTLCID,
|
||||
incomingAmount: packet.incomingAmount,
|
||||
amount: packet.amount,
|
||||
|
|
@ -2862,41 +2863,94 @@ func (s *Switch) handlePacketAdd(packet *htlcPacket,
|
|||
return s.failAddPacket(packet, failure)
|
||||
}
|
||||
|
||||
// Before we attempt to find a non-strict forwarding path for this
|
||||
// htlc, check whether the htlc is being routed over the same incoming
|
||||
// and outgoing channel. If our node does not allow forwards of this
|
||||
// nature, we fail the htlc early. This check is in place to disallow
|
||||
// inefficiently routed htlcs from locking up our balance. With
|
||||
// channels where the option-scid-alias feature was negotiated, we also
|
||||
// have to be sure that the IDs aren't the same since one or both could
|
||||
// be an alias.
|
||||
linkErr := s.checkCircularForward(
|
||||
packet.incomingChanID, packet.outgoingChanID,
|
||||
s.cfg.AllowCircularRoute, htlc.PaymentHash,
|
||||
)
|
||||
if linkErr != nil {
|
||||
return s.failAddPacket(packet, linkErr)
|
||||
}
|
||||
// Collect the links that could carry this HTLC to the next hop.
|
||||
// Non-strict forwarding then load-balances across our channels to that
|
||||
// peer. A short channel ID maps to a link and its peer, while a blinded
|
||||
// node-ID next hop resolves the peer directly. A node-ID hop has no
|
||||
// sender-specified channel, so outgoingChanID stays hop.Exit until
|
||||
// selection.
|
||||
var interfaceLinks []ChannelLink
|
||||
if packet.outgoingHop.IsLeft() {
|
||||
// Before we attempt to find a non-strict forwarding path for
|
||||
// this htlc, check whether the htlc is being routed over the
|
||||
// same incoming and outgoing channel. If our node does not
|
||||
// allow forwards of this nature, we fail the htlc early. This
|
||||
// check is in place to disallow inefficiently routed htlcs from
|
||||
// locking up our balance. With channels where the
|
||||
// option-scid-alias feature was negotiated, we also have to be
|
||||
// sure that the IDs aren't the same since one or both could be
|
||||
// an alias.
|
||||
linkErr := s.checkCircularForward(
|
||||
packet.incomingChanID, packet.outgoingChanID,
|
||||
s.cfg.AllowCircularRoute, htlc.PaymentHash,
|
||||
)
|
||||
if linkErr != nil {
|
||||
return s.failAddPacket(packet, linkErr)
|
||||
}
|
||||
|
||||
s.indexMtx.RLock()
|
||||
targetLink, err := s.getLinkByMapping(packet)
|
||||
if err != nil {
|
||||
s.indexMtx.RLock()
|
||||
targetLink, err := s.getLinkByMapping(packet)
|
||||
if err != nil {
|
||||
s.indexMtx.RUnlock()
|
||||
|
||||
log.Debugf("unable to find link with "+
|
||||
"destination %v", packet.outgoingChanID)
|
||||
|
||||
// If packet was forwarded from another channel link
|
||||
// then we should notify this link that some error
|
||||
// occurred.
|
||||
linkError := NewLinkError(
|
||||
&lnwire.FailUnknownNextPeer{},
|
||||
)
|
||||
|
||||
return s.failAddPacket(packet, linkError)
|
||||
}
|
||||
|
||||
// NOTE: for the SCID path, we fetch all links to the target
|
||||
// peer. If parallel channels exist to the incoming peer, the
|
||||
// candidate set may include the incoming channel even when a
|
||||
// different SCID was requested.
|
||||
targetPeer := targetLink.PeerPubKey()
|
||||
interfaceLinks, _ = s.getLinks(targetPeer)
|
||||
s.indexMtx.RUnlock()
|
||||
} else {
|
||||
// A blinded node-ID next hop identifies the peer directly, so
|
||||
// resolve its links and let non-strict forwarding load-balance
|
||||
// across our channels to that peer.
|
||||
peerKey := packet.outgoingHop.UnwrapRightOr([33]byte{})
|
||||
|
||||
s.indexMtx.RLock()
|
||||
interfaceLinks, _ = s.getLinks(peerKey)
|
||||
s.indexMtx.RUnlock()
|
||||
|
||||
log.Debugf("unable to find link with "+
|
||||
"destination %v", packet.outgoingChanID)
|
||||
// Drop links that would form a disallowed circular route, so
|
||||
// selection can't later land on the incoming channel.
|
||||
var nonCircularLinks []ChannelLink
|
||||
for _, link := range interfaceLinks {
|
||||
linkErr := s.checkCircularForward(
|
||||
packet.incomingChanID, link.ShortChanID(),
|
||||
s.cfg.AllowCircularRoute, htlc.PaymentHash,
|
||||
)
|
||||
if linkErr == nil {
|
||||
nonCircularLinks = append(
|
||||
nonCircularLinks, link,
|
||||
)
|
||||
}
|
||||
}
|
||||
interfaceLinks = nonCircularLinks
|
||||
|
||||
// If packet was forwarded from another channel link than we
|
||||
// should notify this link that some error occurred.
|
||||
linkError := NewLinkError(
|
||||
&lnwire.FailUnknownNextPeer{},
|
||||
)
|
||||
// Without a usable link to the peer (none exist, or all would
|
||||
// be circular) we cannot forward. Fail as unknown next peer
|
||||
// rather than attributing it to a specific channel.
|
||||
if len(interfaceLinks) == 0 {
|
||||
log.Debugf("no usable link to peer %x for blinded "+
|
||||
"next hop", peerKey)
|
||||
|
||||
return s.failAddPacket(packet, linkError)
|
||||
return s.failAddPacket(packet, NewLinkError(
|
||||
&lnwire.FailUnknownNextPeer{},
|
||||
))
|
||||
}
|
||||
}
|
||||
targetPeerKey := targetLink.PeerPubKey()
|
||||
interfaceLinks, _ := s.getLinks(targetPeerKey)
|
||||
s.indexMtx.RUnlock()
|
||||
|
||||
// We'll keep track of any HTLC failures during the link selection
|
||||
// process. This way we can return the error for precise link that the
|
||||
|
|
@ -2943,6 +2997,18 @@ func (s *Switch) handlePacketAdd(packet *htlcPacket,
|
|||
// current policy, then we'll send back an error, but ensure we send
|
||||
// back the error sourced at the *target* link.
|
||||
if len(destinations) == 0 {
|
||||
// A node-ID next hop has no requested outgoing channel.
|
||||
// Returning a per-candidate failure could leak a private
|
||||
// channel via its channel_update (a probing vector), so fail
|
||||
// generically. Later errors don't include private data. Defense
|
||||
// in depth: route blinding error handling hides it too via
|
||||
// error conversion.
|
||||
if packet.outgoingHop.IsRight() {
|
||||
return s.failAddPacket(packet, NewLinkError(
|
||||
&lnwire.FailUnknownNextPeer{},
|
||||
))
|
||||
}
|
||||
|
||||
// At this point, some or all of the links rejected the HTLC so
|
||||
// we couldn't forward it. So we'll try to look up the error
|
||||
// that came from the source.
|
||||
|
|
|
|||
|
|
@ -1991,6 +1991,139 @@ func TestCircularForwards(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestNodeIDNonStrictRouting ensures that when a blinded route identifies the
|
||||
// next hop by node ID, non-strict forwarding deterministically selects a valid
|
||||
// outgoing channel to that peer and never fails the HTLC by landing on the
|
||||
// incoming channel.
|
||||
func TestNodeIDNonStrictRouting(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// bob is both the source of the incoming HTLC and the next hop
|
||||
// identified by node ID, so we have two channels with bob: the channel
|
||||
// the HTLC arrives on and a second, valid outgoing channel.
|
||||
bobPeer, err := newMockServer(
|
||||
t, "bob", testStartingHeight, nil, testDefaultDelta,
|
||||
)
|
||||
require.NoError(t, err, "unable to create bob server")
|
||||
|
||||
s, err := initSwitchWithTempDB(t, testStartingHeight)
|
||||
require.NoError(t, err, "unable to init switch")
|
||||
require.NoError(t, s.Start(), "unable to start switch")
|
||||
defer func() { _ = s.Stop() }()
|
||||
|
||||
// Disallow circular routes so that forwarding back over the incoming
|
||||
// channel is rejected.
|
||||
s.cfg.AllowCircularRoute = false
|
||||
|
||||
incomingChanID, incomingScid := genID()
|
||||
outgoingChanID, outgoingScid := genID()
|
||||
|
||||
incomingLink := newMockChannelLink(
|
||||
s, incomingChanID, incomingScid, emptyScid, bobPeer,
|
||||
true, false, false, false,
|
||||
)
|
||||
outgoingLink := newMockChannelLink(
|
||||
s, outgoingChanID, outgoingScid, emptyScid, bobPeer,
|
||||
true, false, false, false,
|
||||
)
|
||||
require.NoError(t, s.AddLink(incomingLink), "unable to add incoming")
|
||||
require.NoError(t, s.AddLink(outgoingLink), "unable to add outgoing")
|
||||
|
||||
// Forward many HTLCs so that random selection would almost certainly
|
||||
// land on the incoming channel, which will be sorted out by the switch.
|
||||
const numHTLCs = 20
|
||||
for i := 0; i < numHTLCs; i++ {
|
||||
var hash [sha256.Size]byte
|
||||
hash[0] = byte(i)
|
||||
|
||||
packet := &htlcPacket{
|
||||
incomingChanID: incomingLink.ShortChanID(),
|
||||
incomingHTLCID: uint64(i),
|
||||
outgoingHop: hop.NewNodeNextHop(bobPeer.PubKey()),
|
||||
htlc: &lnwire.UpdateAddHTLC{
|
||||
PaymentHash: hash,
|
||||
Amount: 1,
|
||||
},
|
||||
obfuscator: NewMockObfuscator(),
|
||||
}
|
||||
|
||||
require.NoError(t, s.ForwardPackets(nil, packet))
|
||||
|
||||
select {
|
||||
case p := <-outgoingLink.packets:
|
||||
require.Nil(t, p.linkFailure, "unexpected link failure")
|
||||
require.Equal(
|
||||
t, outgoingLink.ShortChanID(),
|
||||
p.outgoingChanID,
|
||||
"forwarded over wrong channel",
|
||||
)
|
||||
|
||||
case <-incomingLink.packets:
|
||||
t.Fatal("HTLC forwarded over incoming (circular) " +
|
||||
"channel")
|
||||
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("no timely reply from switch")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestNodeIDNonStrictRoutingAllLinksCircular ensures that when a blinded route
|
||||
// identifies the next hop by node ID, and the only channel we have with that
|
||||
// peer is the incoming channel (forming a circular route), the switch fails the
|
||||
// HTLC early upfront.
|
||||
func TestNodeIDNonStrictRoutingAllLinksCircular(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
bobPeer, err := newMockServer(
|
||||
t, "bob", testStartingHeight, nil, testDefaultDelta,
|
||||
)
|
||||
require.NoError(t, err, "unable to create bob server")
|
||||
|
||||
s, err := initSwitchWithTempDB(t, testStartingHeight)
|
||||
require.NoError(t, err, "unable to init switch")
|
||||
require.NoError(t, s.Start(), "unable to start switch")
|
||||
defer func() { _ = s.Stop() }()
|
||||
|
||||
// Disallow circular routes.
|
||||
s.cfg.AllowCircularRoute = false
|
||||
|
||||
incomingChanID, incomingScid := genID()
|
||||
incomingLink := newMockChannelLink(
|
||||
s, incomingChanID, incomingScid, emptyScid, bobPeer,
|
||||
true, false, false, false,
|
||||
)
|
||||
require.NoError(t, s.AddLink(incomingLink), "unable to add incoming")
|
||||
|
||||
packet := &htlcPacket{
|
||||
incomingChanID: incomingLink.ShortChanID(),
|
||||
incomingHTLCID: 1,
|
||||
outgoingHop: hop.NewNodeNextHop(bobPeer.PubKey()),
|
||||
htlc: &lnwire.UpdateAddHTLC{
|
||||
PaymentHash: [32]byte{1},
|
||||
Amount: 1,
|
||||
},
|
||||
obfuscator: NewMockObfuscator(),
|
||||
}
|
||||
|
||||
err = s.ForwardPackets(nil, packet)
|
||||
require.NoError(t, err, "unable to forward packets")
|
||||
|
||||
select {
|
||||
case p := <-incomingLink.packets:
|
||||
require.NotNil(t, p.linkFailure, "expected early link failure")
|
||||
wireErr := p.linkFailure.WireMessage()
|
||||
var unknownNextPeer *lnwire.FailUnknownNextPeer
|
||||
require.ErrorAs(
|
||||
t, wireErr, &unknownNextPeer,
|
||||
"expected FailUnknownNextPeer",
|
||||
)
|
||||
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("no timely reply from switch")
|
||||
}
|
||||
}
|
||||
|
||||
// TestCheckCircularForward tests the error returned by checkCircularForward
|
||||
// in cases where we allow and disallow same channel circular forwards.
|
||||
func TestCheckCircularForward(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -591,6 +591,18 @@ var allTestCases = []*lntest.TestCase{
|
|||
Name: "blinded payment htlc re-forward",
|
||||
TestFunc: testBlindedPaymentHTLCReForward,
|
||||
},
|
||||
{
|
||||
Name: "blinded route next node id",
|
||||
TestFunc: testBlindedRouteNextNodeID,
|
||||
},
|
||||
{
|
||||
Name: "blinded route next node id private channel",
|
||||
TestFunc: testBlindedRouteNextNodeIDPrivateChannel,
|
||||
},
|
||||
{
|
||||
Name: "blinded route next node id restart",
|
||||
TestFunc: testBlindedRouteNextNodeIDRestart,
|
||||
},
|
||||
{
|
||||
Name: "query blinded route",
|
||||
TestFunc: testQueryBlindedRoutes,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package itest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
|
|
@ -10,12 +11,16 @@ import (
|
|||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
sphinx "github.com/lightningnetwork/lightning-onion"
|
||||
"github.com/lightningnetwork/lnd/chainreg"
|
||||
"github.com/lightningnetwork/lnd/htlcswitch"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||
"github.com/lightningnetwork/lnd/lntest"
|
||||
"github.com/lightningnetwork/lnd/lntest/node"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/record"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
|
@ -383,6 +388,78 @@ func (b *blindedForwardTest) setupNetwork(ctx context.Context,
|
|||
}
|
||||
}
|
||||
|
||||
// setupNetworkPrivateMiddle sets up the same Alice -> Bob -> Carol -> Dave
|
||||
// network as setupNetwork (with an interceptor on Carol), except that the
|
||||
// Bob -> Carol channel is private. This is the channel the introduction node
|
||||
// (Bob) must resolve to from Carol's node ID, exercising resolution to an SCID
|
||||
// alias of an unadvertised channel.
|
||||
func (b *blindedForwardTest) setupNetworkPrivateMiddle(ctx context.Context) {
|
||||
carolArgs := []string{
|
||||
"--bitcoin.timelockdelta=24",
|
||||
fmt.Sprintf("--bitcoin.defaultremotedelay=%v", toLocalCSV),
|
||||
"--requireinterceptor",
|
||||
}
|
||||
daveArgs := []string{
|
||||
"--bitcoin.timelockdelta=24",
|
||||
fmt.Sprintf("--bitcoin.defaultremotedelay=%v", toLocalCSV),
|
||||
}
|
||||
|
||||
alice := b.ht.NewNode("Alice", nil)
|
||||
bob := b.ht.NewNode("Bob", nil)
|
||||
carol := b.ht.NewNode("Carol", carolArgs)
|
||||
dave := b.ht.NewNode("Dave", daveArgs)
|
||||
b.alice, b.bob, b.carol, b.dave = alice, bob, carol, dave
|
||||
|
||||
b.ht.EnsureConnected(alice, bob)
|
||||
b.ht.EnsureConnected(bob, carol)
|
||||
b.ht.EnsureConnected(carol, dave)
|
||||
|
||||
// Fund every node that opens a channel.
|
||||
const chanAmt = btcutil.Amount(100_000)
|
||||
b.ht.FundCoins(btcutil.SatoshiPerBitcoin, alice)
|
||||
b.ht.FundCoins(btcutil.SatoshiPerBitcoin, bob)
|
||||
b.ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
|
||||
|
||||
// Open Alice -> Bob and Carol -> Dave as public channels, but Bob ->
|
||||
// Carol (the hop the introduction node must resolve by node ID) as a
|
||||
// private channel, so it is only reachable via an SCID alias.
|
||||
reqs := []*lntest.OpenChannelRequest{
|
||||
{
|
||||
Local: alice,
|
||||
Remote: bob,
|
||||
Param: lntest.OpenChannelParams{Amt: chanAmt},
|
||||
},
|
||||
{
|
||||
Local: bob,
|
||||
Remote: carol,
|
||||
Param: lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
Private: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Local: carol,
|
||||
Remote: dave,
|
||||
Param: lntest.OpenChannelParams{Amt: chanAmt},
|
||||
},
|
||||
}
|
||||
b.channels = b.ht.OpenMultiChannelsAsync(reqs)
|
||||
|
||||
// Alice must know the public Alice -> Bob channel to build a route to
|
||||
// the introduction node, and Bob and Carol must both know the private
|
||||
// Bob -> Carol channel used for forwarding.
|
||||
b.ht.AssertChannelInGraph(alice, b.channels[0])
|
||||
b.ht.AssertChannelInGraph(bob, b.channels[0])
|
||||
b.ht.AssertChannelInGraph(bob, b.channels[1])
|
||||
b.ht.AssertChannelInGraph(carol, b.channels[1])
|
||||
b.ht.AssertChannelInGraph(carol, b.channels[2])
|
||||
b.ht.AssertChannelInGraph(dave, b.channels[2])
|
||||
|
||||
var err error
|
||||
b.carolInterceptor, err = b.carol.RPC.Router.HtlcInterceptor(ctx)
|
||||
require.NoError(b.ht, err, "interceptor")
|
||||
}
|
||||
|
||||
// buildBlindedPath returns a blinded route from Bob -> Carol -> Dave, with Bob
|
||||
// acting as the introduction point.
|
||||
func (b *blindedForwardTest) buildBlindedPath() *lnrpc.BlindedPaymentPath {
|
||||
|
|
@ -1421,6 +1498,349 @@ func testBlindedPaymentHTLCReForward(ht *lntest.HarnessTest) {
|
|||
}
|
||||
}
|
||||
|
||||
// nextNodeIDRouteData builds the recipient data for a non-final blinded hop
|
||||
// that identifies the next hop by its node ID (next_node_id) rather than a
|
||||
// short channel ID. This is the form of recipient data that a non-lnd
|
||||
// implementation may produce and that the forwarding node must resolve to one
|
||||
// of its active channels.
|
||||
func nextNodeIDRouteData(nextNode *btcec.PublicKey,
|
||||
relayInfo record.PaymentRelayInfo,
|
||||
constraints *record.PaymentConstraints) *record.BlindedRouteData {
|
||||
|
||||
return &record.BlindedRouteData{
|
||||
NextNodeID: tlv.SomeRecordT(
|
||||
tlv.NewPrimitiveRecord[tlv.TlvType4](nextNode),
|
||||
),
|
||||
RelayInfo: tlv.SomeRecordT(
|
||||
tlv.NewRecordT[tlv.TlvType10](relayInfo),
|
||||
),
|
||||
Constraints: tlv.SomeRecordT(
|
||||
tlv.NewRecordT[tlv.TlvType12](*constraints),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// buildBlindedPathWithNextNodeID constructs a Bob -> Carol -> Dave blinded path
|
||||
// in which the non-final hops (Bob and Carol) identify their next hop by node
|
||||
// ID instead of a short channel ID. Bob is the introduction node. The returned
|
||||
// path can be used to exercise an lnd forwarding node's ability to resolve a
|
||||
// next_node_id to one of its active channels.
|
||||
func (b *blindedForwardTest) buildBlindedPathWithNextNodeID(
|
||||
paymentAmt int64) *lnrpc.BlindedPaymentPath {
|
||||
|
||||
bobPub, err := btcec.ParsePubKey(b.bob.PubKey[:])
|
||||
require.NoError(b.ht, err)
|
||||
|
||||
carolPub, err := btcec.ParsePubKey(b.carol.PubKey[:])
|
||||
require.NoError(b.ht, err)
|
||||
|
||||
davePub, err := btcec.ParsePubKey(b.dave.PubKey[:])
|
||||
require.NoError(b.ht, err)
|
||||
|
||||
// Use zero fees so that the forwarded amount remains constant along the
|
||||
// path, keeping the route math trivial.
|
||||
const (
|
||||
hopCltvDelta uint16 = 144
|
||||
finalCltvDelta uint32 = 24
|
||||
)
|
||||
|
||||
// Set a generous max CLTV constraint so that the incoming expiry at
|
||||
// each hop never trips the payment constraints check.
|
||||
info := b.alice.RPC.GetInfo()
|
||||
constraints := &record.PaymentConstraints{
|
||||
MaxCltvExpiry: info.BlockHeight + 10_000,
|
||||
HtlcMinimumMsat: 0,
|
||||
}
|
||||
relayInfo := record.PaymentRelayInfo{
|
||||
CltvExpiryDelta: hopCltvDelta,
|
||||
FeeRate: 0,
|
||||
BaseFee: 0,
|
||||
}
|
||||
|
||||
// Bob (the introduction node) forwards to Carol and Carol forwards to
|
||||
// Dave, each identified purely by node ID. Dave is the final hop; its
|
||||
// path ID is arbitrary because the payment is settled at Carol via the
|
||||
// interceptor before it ever reaches Dave.
|
||||
hopData := []struct {
|
||||
pub *btcec.PublicKey
|
||||
data *record.BlindedRouteData
|
||||
}{
|
||||
{
|
||||
pub: bobPub,
|
||||
data: nextNodeIDRouteData(
|
||||
carolPub, relayInfo, constraints,
|
||||
),
|
||||
},
|
||||
{
|
||||
pub: carolPub,
|
||||
data: nextNodeIDRouteData(
|
||||
davePub, relayInfo, constraints,
|
||||
),
|
||||
},
|
||||
{
|
||||
pub: davePub,
|
||||
data: record.NewFinalHopBlindedRouteData(
|
||||
constraints, bytes.Repeat([]byte{1}, 32),
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
paymentPath := make([]*sphinx.HopInfo, len(hopData))
|
||||
for i, hop := range hopData {
|
||||
plainText, err := record.EncodeBlindedRouteData(hop.data)
|
||||
require.NoError(b.ht, err)
|
||||
|
||||
paymentPath[i] = &sphinx.HopInfo{
|
||||
NodePub: hop.pub,
|
||||
PlainText: plainText,
|
||||
}
|
||||
}
|
||||
|
||||
// Encrypt the per-hop data into a blinded path using a fresh session
|
||||
// key.
|
||||
sessionKey, err := btcec.NewPrivateKey()
|
||||
require.NoError(b.ht, err)
|
||||
|
||||
blindedPathInfo, err := sphinx.BuildBlindedPath(sessionKey, paymentPath)
|
||||
require.NoError(b.ht, err)
|
||||
blindedPath := blindedPathInfo.Path
|
||||
|
||||
// The introduction node is communicated in plaintext, so overwrite the
|
||||
// first hop's blinded pub key with the real introduction point.
|
||||
blindedPath.BlindedHops[0].BlindedNodePub =
|
||||
blindedPath.IntroductionPoint
|
||||
|
||||
blindedHops := make(
|
||||
[]*lnrpc.BlindedHop, len(blindedPath.BlindedHops),
|
||||
)
|
||||
for i, hop := range blindedPath.BlindedHops {
|
||||
blindedHops[i] = &lnrpc.BlindedHop{
|
||||
BlindedNode: hop.BlindedNodePub.SerializeCompressed(),
|
||||
EncryptedData: hop.CipherText,
|
||||
}
|
||||
}
|
||||
|
||||
return &lnrpc.BlindedPaymentPath{
|
||||
BlindedPath: &lnrpc.BlindedPath{
|
||||
IntroductionNode: b.bob.PubKey[:],
|
||||
BlindingPoint: blindedPath.BlindingPoint.
|
||||
SerializeCompressed(),
|
||||
BlindedHops: blindedHops,
|
||||
},
|
||||
BaseFeeMsat: 0,
|
||||
TotalCltvDelta: 2*uint32(hopCltvDelta) + finalCltvDelta,
|
||||
HtlcMinMsat: 0,
|
||||
HtlcMaxMsat: uint64(paymentAmt) * 2,
|
||||
}
|
||||
}
|
||||
|
||||
// testBlindedRouteNextNodeID tests that an lnd node acting as the introduction
|
||||
// node of a blinded path can forward a payment when the recipient identifies
|
||||
// the next hop by its node ID (next_node_id) rather than a short channel ID.
|
||||
// The introduction node must resolve the node ID to one of its active channels
|
||||
// with that peer.
|
||||
func testBlindedRouteNextNodeID(ht *lntest.HarnessTest) {
|
||||
ctx, testCase := newBlindedForwardTest(ht)
|
||||
defer testCase.cleanup()
|
||||
|
||||
// Set up the Alice -> Bob -> Carol -> Dave network with an interceptor
|
||||
// on Carol. Bob is the introduction node whose node ID resolution we
|
||||
// want to exercise, and Carol's interceptor lets us deterministically
|
||||
// observe that Bob successfully resolved and forwarded the HTLC.
|
||||
testCase.setupNetwork(ctx, true)
|
||||
|
||||
testCase.runNextNodeIDForward(ctx, nil)
|
||||
}
|
||||
|
||||
// testBlindedRouteNextNodeIDPrivateChannel is like testBlindedRouteNextNodeID,
|
||||
// but the Bob -> Carol channel that the introduction node must resolve by node
|
||||
// ID is private. This exercises the introduction node's ability to resolve the
|
||||
// next node's ID to an SCID alias of an unadvertised channel (option-scid-alias
|
||||
// channels are not forwardable by their confirmed SCID).
|
||||
func testBlindedRouteNextNodeIDPrivateChannel(ht *lntest.HarnessTest) {
|
||||
ctx, testCase := newBlindedForwardTest(ht)
|
||||
defer testCase.cleanup()
|
||||
|
||||
// Set up Alice -> Bob -> Carol -> Dave where the Bob -> Carol channel
|
||||
// is private, so Bob must resolve Carol's node ID to that channel's
|
||||
// alias.
|
||||
testCase.setupNetworkPrivateMiddle(ctx)
|
||||
|
||||
testCase.runNextNodeIDForward(ctx, nil)
|
||||
}
|
||||
|
||||
// testBlindedRouteNextNodeIDRestart tests that a blinded payment forwarded by
|
||||
// node ID survives a restart of the introduction node. The HTLC is held at the
|
||||
// receiver's interceptor after the introduction node (Bob) has resolved the
|
||||
// next node's ID and forwarded it. Bob is then restarted, forcing it to replay
|
||||
// its forwarding package and re-decode the node-ID blinded hop, after which the
|
||||
// in-flight HTLC must remain intact and the payment must still settle.
|
||||
func testBlindedRouteNextNodeIDRestart(ht *lntest.HarnessTest) {
|
||||
ctx, testCase := newBlindedForwardTest(ht)
|
||||
defer testCase.cleanup()
|
||||
|
||||
testCase.setupNetwork(ctx, true)
|
||||
|
||||
// Open a second, parallel Bob -> Carol channel with zero fees, matching
|
||||
// the zero-fee policy runNextNodeIDForward sets on channels[1]. The
|
||||
// blinded path identifies the hop by Carol's node ID, so both Bob ->
|
||||
// Carol channels are valid candidates and Bob's non-strict forwarding
|
||||
// picks one at random. We use this to prove that replaying the
|
||||
// forwarding package after a restart re-pins the same randomly selected
|
||||
// channel and does not duplicate the HTLC onto the other one.
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, testCase.bob)
|
||||
parallel := ht.OpenChannel(
|
||||
testCase.bob, testCase.carol,
|
||||
lntest.OpenChannelParams{Amt: chanAmt},
|
||||
)
|
||||
testCase.bob.RPC.UpdateChannelPolicy(&lnrpc.PolicyUpdateRequest{
|
||||
Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{
|
||||
ChanPoint: parallel,
|
||||
},
|
||||
BaseFeeMsat: 0,
|
||||
FeeRatePpm: 0,
|
||||
TimeLockDelta: 80,
|
||||
})
|
||||
|
||||
testCase.runNextNodeIDForward(ctx, func() {
|
||||
hash := sha256.Sum256(testCase.preimage[:])
|
||||
|
||||
// Non-strict forwarding picked one of the two Bob -> Carol
|
||||
// channels at random. Find which one currently carries the
|
||||
// outgoing HTLC so we can assert it stays there across the
|
||||
// restart.
|
||||
chosen, other := testCase.channels[1], parallel
|
||||
if channelHasHTLC(ht, testCase.bob, parallel, hash[:]) {
|
||||
chosen, other = parallel, testCase.channels[1]
|
||||
}
|
||||
|
||||
// Restart the introduction node while the HTLC is held at
|
||||
// Carol's interceptor. On startup Bob replays its forwarding
|
||||
// package and must re-decode the node-ID blinded hop without
|
||||
// disturbing the already forwarded HTLC.
|
||||
ht.RestartNode(testCase.bob)
|
||||
ht.EnsureConnected(testCase.alice, testCase.bob)
|
||||
ht.EnsureConnected(testCase.bob, testCase.carol)
|
||||
|
||||
// After replaying its forwarding package, the in-flight HTLC
|
||||
// must still be on the originally selected channel and must not
|
||||
// have been duplicated onto the other Bob -> Carol channel. Bob
|
||||
// therefore holds exactly two active HTLCs: the incoming one
|
||||
// from Alice and the single outgoing one to Carol.
|
||||
ht.AssertOutgoingHTLCActive(testCase.bob, chosen, hash[:])
|
||||
ht.AssertHTLCNotActive(testCase.bob, other, hash[:])
|
||||
ht.AssertNumActiveHtlcs(testCase.bob, 2)
|
||||
})
|
||||
}
|
||||
|
||||
// channelHasHTLC reports whether the given channel currently has a pending
|
||||
// HTLC locked in for the provided payment hash.
|
||||
func channelHasHTLC(ht *lntest.HarnessTest, hn *node.HarnessNode,
|
||||
cp *lnrpc.ChannelPoint, hash []byte) bool {
|
||||
|
||||
channel := ht.GetChannelByChanPoint(hn, cp)
|
||||
for _, htlc := range channel.PendingHtlcs {
|
||||
if bytes.Equal(htlc.HashLock, hash) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// runNextNodeIDForward drives a payment along a blinded path whose non-final
|
||||
// hops identify the next hop by node ID, asserting that the lnd introduction
|
||||
// node (Bob) resolves the node ID to one of its channels and forwards the HTLC
|
||||
// to Carol, who settles it via her interceptor. If midFlight is non-nil it is
|
||||
// invoked while the HTLC is held at Carol's interceptor, before it is settled,
|
||||
// letting callers exercise behaviour such as restarting the introduction node.
|
||||
func (b *blindedForwardTest) runNextNodeIDForward(ctx context.Context,
|
||||
midFlight func()) {
|
||||
|
||||
ht := b.ht
|
||||
|
||||
// Since buildBlindedPathWithNextNodeID constructs a path with zero
|
||||
// fees to keep routing math trivial, we must update Bob's outgoing
|
||||
// channel policy to have zero fees so that forwarding is not rejected
|
||||
// with FeeInsufficient.
|
||||
bobUpdateReq := &lnrpc.PolicyUpdateRequest{
|
||||
Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{
|
||||
ChanPoint: b.channels[1],
|
||||
},
|
||||
BaseFeeMsat: 0,
|
||||
FeeRatePpm: 0,
|
||||
TimeLockDelta: 80,
|
||||
}
|
||||
b.bob.RPC.UpdateChannelPolicy(bobUpdateReq)
|
||||
|
||||
const paymentAmt = 10_000_000
|
||||
blindedPath := b.buildBlindedPathWithNextNodeID(paymentAmt)
|
||||
route := b.createRouteToBlinded(paymentAmt, blindedPath)
|
||||
|
||||
hash := sha256.Sum256(b.preimage[:])
|
||||
sendReq := &routerrpc.SendToRouteRequest{
|
||||
PaymentHash: hash[:],
|
||||
Route: route,
|
||||
}
|
||||
|
||||
// Dispatch the payment in the background since the HTLC will be held by
|
||||
// Carol's interceptor until we resolve it.
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
|
||||
htlcAttempt, err := b.alice.RPC.Router.SendToRouteV2(
|
||||
ctx, sendReq,
|
||||
)
|
||||
require.NoError(ht, err)
|
||||
require.Equal(
|
||||
ht, lnrpc.HTLCAttempt_SUCCEEDED, htlcAttempt.Status,
|
||||
)
|
||||
}()
|
||||
|
||||
// Bob holding two active HTLCs (one incoming from Alice, one outgoing
|
||||
// to Carol) demonstrates that Bob (the lnd introduction node) resolved
|
||||
// Carol's node ID and forwarded the HTLC onwards. We assert on the
|
||||
// count rather than a specific Bob -> Carol channel because non-strict
|
||||
// forwarding may pick any of Bob's channels to Carol.
|
||||
ht.AssertOutgoingHTLCActive(b.alice, b.channels[0], hash[:])
|
||||
ht.AssertNumActiveHtlcs(b.bob, 2)
|
||||
|
||||
// Carol intercepts the forwarded HTLC, confirming that the introduction
|
||||
// node's resolution and forwarding succeeded. Settle it with the
|
||||
// preimage so that Alice's payment completes successfully.
|
||||
interceptor := b.carolInterceptor
|
||||
carolHTLC, err := interceptor.Recv()
|
||||
require.NoError(ht, err)
|
||||
|
||||
// Carol's own onward hop to Dave is also identified by node ID, so her
|
||||
// intercept request must expose Dave's pubkey and flag the node-ID
|
||||
// forward with the sentinel outgoing channel rather than a zero SCID.
|
||||
require.Equal(
|
||||
ht, htlcswitch.NodeIDForwardSCID,
|
||||
carolHTLC.OutgoingRequestedChanId,
|
||||
)
|
||||
require.Equal(ht, b.dave.PubKey[:], carolHTLC.OutgoingRequestedNodeId)
|
||||
|
||||
// Run any caller-supplied step while the HTLC is held mid-flight.
|
||||
if midFlight != nil {
|
||||
midFlight()
|
||||
}
|
||||
|
||||
err = interceptor.Send(&routerrpc.ForwardHtlcInterceptResponse{
|
||||
IncomingCircuitKey: carolHTLC.IncomingCircuitKey,
|
||||
Action: routerrpc.ResolveHoldForwardAction_SETTLE,
|
||||
Preimage: b.preimage[:],
|
||||
})
|
||||
require.NoError(ht, err)
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(defaultTimeout):
|
||||
require.Fail(ht, "timeout waiting for payment to complete")
|
||||
}
|
||||
}
|
||||
|
||||
// testPartiallySpecifiedBlindedPath tests lnd's ability to:
|
||||
// - Assert the error when attempting to create a blinded payment with an
|
||||
// invalid partially specified path.
|
||||
|
|
|
|||
|
|
@ -100,6 +100,17 @@ func (r *forwardInterceptor) onIntercept(
|
|||
InWireCustomRecords: htlc.InWireCustomRecords,
|
||||
}
|
||||
|
||||
// A node-ID forward has no requested outgoing channel. Expose the
|
||||
// requested pubkey and report the reserved NodeIDForwardSCID sentinel
|
||||
// rather than a zero SCID. Older un-upgraded protobuf clients do not
|
||||
// know about outgoing_requested_node_id and would otherwise interpret
|
||||
// a zero SCID as an exit hop.
|
||||
htlc.OutgoingNodeID.WhenSome(func(nodeID [33]byte) {
|
||||
interceptionRequest.OutgoingRequestedNodeId = nodeID[:]
|
||||
interceptionRequest.OutgoingRequestedChanId =
|
||||
htlcswitch.NodeIDForwardSCID
|
||||
})
|
||||
|
||||
return r.stream.Send(interceptionRequest)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3088,7 +3088,8 @@ type ForwardHtlcInterceptRequest struct {
|
|||
// The requested outgoing channel id for this forwarded htlc. Because of
|
||||
// non-strict forwarding, this isn't necessarily the channel over which the
|
||||
// packet will be forwarded eventually. A different channel to the same peer
|
||||
// may be selected as well.
|
||||
// may be selected as well. This is set to a sentinel value (all bits set)
|
||||
// if the outgoing_requested_node_id is specified for blinded routes.
|
||||
OutgoingRequestedChanId uint64 `protobuf:"varint,7,opt,name=outgoing_requested_chan_id,json=outgoingRequestedChanId,proto3" json:"outgoing_requested_chan_id,omitempty"`
|
||||
// The outgoing htlc amount.
|
||||
OutgoingAmountMsat uint64 `protobuf:"varint,3,opt,name=outgoing_amount_msat,json=outgoingAmountMsat,proto3" json:"outgoing_amount_msat,omitempty"`
|
||||
|
|
@ -3104,6 +3105,19 @@ type ForwardHtlcInterceptRequest struct {
|
|||
AutoFailHeight int32 `protobuf:"varint,10,opt,name=auto_fail_height,json=autoFailHeight,proto3" json:"auto_fail_height,omitempty"`
|
||||
// The custom records of the peer's incoming p2p wire message.
|
||||
InWireCustomRecords map[uint64][]byte `protobuf:"bytes,11,rep,name=in_wire_custom_records,json=inWireCustomRecords,proto3" json:"in_wire_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||
// The requested outgoing node for a blinded forward. When non-empty, this
|
||||
// field contains exactly one 33-byte compressed public key and
|
||||
// outgoing_requested_chan_id is set to 18446744073709551615
|
||||
// (0xffffffffffffffff). Clients MUST NOT interpret that value as an actual
|
||||
// channel ID; the presence of this field identifies a node-addressed
|
||||
// forward.
|
||||
//
|
||||
// The possible next-hop representations are:
|
||||
//
|
||||
// node ID empty, channel ID 0: final receive;
|
||||
// node ID empty, ordinary channel ID: channel-addressed forward;
|
||||
// node ID present, channel ID MaxUint64: node-addressed forward.
|
||||
OutgoingRequestedNodeId []byte `protobuf:"bytes,12,opt,name=outgoing_requested_node_id,json=outgoingRequestedNodeId,proto3" json:"outgoing_requested_node_id,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ForwardHtlcInterceptRequest) Reset() {
|
||||
|
|
@ -3215,6 +3229,13 @@ func (x *ForwardHtlcInterceptRequest) GetInWireCustomRecords() map[uint64][]byte
|
|||
return nil
|
||||
}
|
||||
|
||||
func (x *ForwardHtlcInterceptRequest) GetOutgoingRequestedNodeId() []byte {
|
||||
if x != nil {
|
||||
return x.OutgoingRequestedNodeId
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// *
|
||||
// ForwardHtlcInterceptResponse enables the caller to resolve a previously hold
|
||||
// forward. The caller can choose either to:
|
||||
|
|
@ -4132,7 +4153,7 @@ var file_routerrpc_router_proto_rawDesc = []byte{
|
|||
0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52,
|
||||
0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x68, 0x74, 0x6c, 0x63, 0x5f,
|
||||
0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x68, 0x74, 0x6c, 0x63, 0x49, 0x64,
|
||||
0x22, 0xa7, 0x06, 0x0a, 0x1b, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63,
|
||||
0x22, 0xe4, 0x06, 0x0a, 0x1b, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63,
|
||||
0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x12, 0x47, 0x0a, 0x14, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x69, 0x72,
|
||||
0x63, 0x75, 0x69, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15,
|
||||
|
|
@ -4174,257 +4195,260 @@ var file_routerrpc_router_proto_rawDesc = []byte{
|
|||
0x73, 0x74, 0x2e, 0x49, 0x6e, 0x57, 0x69, 0x72, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52,
|
||||
0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x13, 0x69, 0x6e, 0x57,
|
||||
0x69, 0x72, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73,
|
||||
0x1a, 0x40, 0x0a, 0x12, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64,
|
||||
0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
|
||||
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02,
|
||||
0x38, 0x01, 0x1a, 0x46, 0x0a, 0x18, 0x49, 0x6e, 0x57, 0x69, 0x72, 0x65, 0x43, 0x75, 0x73, 0x74,
|
||||
0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10,
|
||||
0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79,
|
||||
0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52,
|
||||
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb9, 0x04, 0x0a, 0x1c, 0x46,
|
||||
0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63,
|
||||
0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x14, 0x69,
|
||||
0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x5f,
|
||||
0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x6f, 0x75, 0x74,
|
||||
0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79,
|
||||
0x52, 0x12, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x43, 0x69, 0x72, 0x63, 0x75, 0x69,
|
||||
0x74, 0x4b, 0x65, 0x79, 0x12, 0x3b, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02,
|
||||
0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63,
|
||||
0x2e, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x48, 0x6f, 0x6c, 0x64, 0x46, 0x6f, 0x72, 0x77,
|
||||
0x61, 0x72, 0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20,
|
||||
0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x27, 0x0a,
|
||||
0x0f, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
|
||||
0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x4d,
|
||||
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x3d, 0x0a, 0x0c, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72,
|
||||
0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6c,
|
||||
0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x46, 0x61, 0x69,
|
||||
0x6c, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x0b, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72,
|
||||
0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75,
|
||||
0x6e, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x69,
|
||||
0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6f,
|
||||
0x75, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x07,
|
||||
0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x6f, 0x75, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d,
|
||||
0x73, 0x61, 0x74, 0x12, 0x78, 0x0a, 0x17, 0x6f, 0x75, 0x74, 0x5f, 0x77, 0x69, 0x72, 0x65, 0x5f,
|
||||
0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x08,
|
||||
0x20, 0x03, 0x28, 0x0b, 0x32, 0x41, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63,
|
||||
0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65,
|
||||
0x72, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4f, 0x75,
|
||||
0x12, 0x3b, 0x0a, 0x1a, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x0c,
|
||||
0x20, 0x01, 0x28, 0x0c, 0x52, 0x17, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x64, 0x1a, 0x40, 0x0a,
|
||||
0x12, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e,
|
||||
0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04,
|
||||
0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02,
|
||||
0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a,
|
||||
0x46, 0x0a, 0x18, 0x49, 0x6e, 0x57, 0x69, 0x72, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52,
|
||||
0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b,
|
||||
0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a,
|
||||
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb9, 0x04, 0x0a, 0x1c, 0x46, 0x6f, 0x72, 0x77,
|
||||
0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74,
|
||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x14, 0x69, 0x6e, 0x63, 0x6f,
|
||||
0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x5f, 0x6b, 0x65, 0x79,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72,
|
||||
0x70, 0x63, 0x2e, 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x52, 0x12, 0x69,
|
||||
0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65,
|
||||
0x79, 0x12, 0x3b, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
0x0e, 0x32, 0x23, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65,
|
||||
0x73, 0x6f, 0x6c, 0x76, 0x65, 0x48, 0x6f, 0x6c, 0x64, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64,
|
||||
0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a,
|
||||
0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c,
|
||||
0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x66, 0x61,
|
||||
0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20,
|
||||
0x01, 0x28, 0x0c, 0x52, 0x0e, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x4d, 0x65, 0x73, 0x73,
|
||||
0x61, 0x67, 0x65, 0x12, 0x3d, 0x0a, 0x0c, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x63,
|
||||
0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70,
|
||||
0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72,
|
||||
0x65, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x0b, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x43, 0x6f,
|
||||
0x64, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f,
|
||||
0x6d, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x69, 0x6e, 0x41, 0x6d,
|
||||
0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6f, 0x75, 0x74, 0x5f,
|
||||
0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28,
|
||||
0x04, 0x52, 0x0d, 0x6f, 0x75, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x73, 0x61, 0x74,
|
||||
0x12, 0x78, 0x0a, 0x17, 0x6f, 0x75, 0x74, 0x5f, 0x77, 0x69, 0x72, 0x65, 0x5f, 0x63, 0x75, 0x73,
|
||||
0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28,
|
||||
0x0b, 0x32, 0x41, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f,
|
||||
0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65,
|
||||
0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4f, 0x75, 0x74, 0x57, 0x69,
|
||||
0x72, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45,
|
||||
0x6e, 0x74, 0x72, 0x79, 0x52, 0x14, 0x6f, 0x75, 0x74, 0x57, 0x69, 0x72, 0x65, 0x43, 0x75, 0x73,
|
||||
0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x1a, 0x47, 0x0a, 0x19, 0x4f, 0x75,
|
||||
0x74, 0x57, 0x69, 0x72, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72,
|
||||
0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x14, 0x6f, 0x75, 0x74, 0x57, 0x69, 0x72, 0x65,
|
||||
0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x1a, 0x47, 0x0a,
|
||||
0x19, 0x4f, 0x75, 0x74, 0x57, 0x69, 0x72, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65,
|
||||
0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65,
|
||||
0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05,
|
||||
0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c,
|
||||
0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x82, 0x01, 0x0a, 0x17, 0x55, 0x70, 0x64, 0x61, 0x74,
|
||||
0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43,
|
||||
0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, 0x63, 0x68, 0x61,
|
||||
0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x33, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72,
|
||||
0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x41, 0x63, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x1a, 0x0a, 0x18, 0x55,
|
||||
0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x43, 0x0a, 0x11, 0x41, 0x64, 0x64, 0x41, 0x6c,
|
||||
0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x0a,
|
||||
0x61, 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
|
||||
0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61,
|
||||
0x70, 0x52, 0x09, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, 0x70, 0x73, 0x22, 0x44, 0x0a, 0x12,
|
||||
0x41, 0x64, 0x64, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x12, 0x2e, 0x0a, 0x0a, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x73,
|
||||
0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41,
|
||||
0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, 0x70, 0x52, 0x09, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61,
|
||||
0x70, 0x73, 0x22, 0x46, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x69, 0x61,
|
||||
0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x0a, 0x61, 0x6c,
|
||||
0x69, 0x61, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f,
|
||||
0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, 0x70, 0x52,
|
||||
0x09, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, 0x70, 0x73, 0x22, 0x47, 0x0a, 0x15, 0x44, 0x65,
|
||||
0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x0a, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x6d, 0x61, 0x70,
|
||||
0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e,
|
||||
0x41, 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, 0x70, 0x52, 0x09, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x4d,
|
||||
0x61, 0x70, 0x73, 0x22, 0x2c, 0x0a, 0x14, 0x46, 0x69, 0x6e, 0x64, 0x42, 0x61, 0x73, 0x65, 0x41,
|
||||
0x6c, 0x69, 0x61, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x61,
|
||||
0x6c, 0x69, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x61, 0x6c, 0x69, 0x61,
|
||||
0x73, 0x22, 0x2b, 0x0a, 0x15, 0x46, 0x69, 0x6e, 0x64, 0x42, 0x61, 0x73, 0x65, 0x41, 0x6c, 0x69,
|
||||
0x61, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x61,
|
||||
0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x2a, 0x81,
|
||||
0x04, 0x0a, 0x0d, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c,
|
||||
0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0d, 0x0a,
|
||||
0x09, 0x4e, 0x4f, 0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c,
|
||||
0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x43, 0x4f, 0x44, 0x45, 0x10, 0x02, 0x12, 0x15,
|
||||
0x0a, 0x11, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x45, 0x4c, 0x49, 0x47, 0x49,
|
||||
0x42, 0x4c, 0x45, 0x10, 0x03, 0x12, 0x14, 0x0a, 0x10, 0x4f, 0x4e, 0x5f, 0x43, 0x48, 0x41, 0x49,
|
||||
0x4e, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x04, 0x12, 0x14, 0x0a, 0x10, 0x48,
|
||||
0x54, 0x4c, 0x43, 0x5f, 0x45, 0x58, 0x43, 0x45, 0x45, 0x44, 0x53, 0x5f, 0x4d, 0x41, 0x58, 0x10,
|
||||
0x05, 0x12, 0x18, 0x0a, 0x14, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e,
|
||||
0x54, 0x5f, 0x42, 0x41, 0x4c, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x06, 0x12, 0x16, 0x0a, 0x12, 0x49,
|
||||
0x4e, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x46, 0x4f, 0x52, 0x57, 0x41, 0x52,
|
||||
0x44, 0x10, 0x07, 0x12, 0x13, 0x0a, 0x0f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x44, 0x44, 0x5f,
|
||||
0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x08, 0x12, 0x15, 0x0a, 0x11, 0x46, 0x4f, 0x52, 0x57,
|
||||
0x41, 0x52, 0x44, 0x53, 0x5f, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x44, 0x10, 0x09, 0x12,
|
||||
0x14, 0x0a, 0x10, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45,
|
||||
0x4c, 0x45, 0x44, 0x10, 0x0a, 0x12, 0x15, 0x0a, 0x11, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45,
|
||||
0x5f, 0x55, 0x4e, 0x44, 0x45, 0x52, 0x50, 0x41, 0x49, 0x44, 0x10, 0x0b, 0x12, 0x1b, 0x0a, 0x17,
|
||||
0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, 0x5f, 0x54,
|
||||
0x4f, 0x4f, 0x5f, 0x53, 0x4f, 0x4f, 0x4e, 0x10, 0x0c, 0x12, 0x14, 0x0a, 0x10, 0x49, 0x4e, 0x56,
|
||||
0x4f, 0x49, 0x43, 0x45, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x4f, 0x50, 0x45, 0x4e, 0x10, 0x0d, 0x12,
|
||||
0x17, 0x0a, 0x13, 0x4d, 0x50, 0x50, 0x5f, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x54,
|
||||
0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x0e, 0x12, 0x14, 0x0a, 0x10, 0x41, 0x44, 0x44, 0x52,
|
||||
0x45, 0x53, 0x53, 0x5f, 0x4d, 0x49, 0x53, 0x4d, 0x41, 0x54, 0x43, 0x48, 0x10, 0x0f, 0x12, 0x16,
|
||||
0x0a, 0x12, 0x53, 0x45, 0x54, 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, 0x5f, 0x4d, 0x49, 0x53, 0x4d,
|
||||
0x41, 0x54, 0x43, 0x48, 0x10, 0x10, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x45, 0x54, 0x5f, 0x54, 0x4f,
|
||||
0x54, 0x41, 0x4c, 0x5f, 0x54, 0x4f, 0x4f, 0x5f, 0x4c, 0x4f, 0x57, 0x10, 0x11, 0x12, 0x10, 0x0a,
|
||||
0x0c, 0x53, 0x45, 0x54, 0x5f, 0x4f, 0x56, 0x45, 0x52, 0x50, 0x41, 0x49, 0x44, 0x10, 0x12, 0x12,
|
||||
0x13, 0x0a, 0x0f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x49, 0x4e, 0x56, 0x4f, 0x49,
|
||||
0x43, 0x45, 0x10, 0x13, 0x12, 0x13, 0x0a, 0x0f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f,
|
||||
0x4b, 0x45, 0x59, 0x53, 0x45, 0x4e, 0x44, 0x10, 0x14, 0x12, 0x13, 0x0a, 0x0f, 0x4d, 0x50, 0x50,
|
||||
0x5f, 0x49, 0x4e, 0x5f, 0x50, 0x52, 0x4f, 0x47, 0x52, 0x45, 0x53, 0x53, 0x10, 0x15, 0x12, 0x12,
|
||||
0x0a, 0x0e, 0x43, 0x49, 0x52, 0x43, 0x55, 0x4c, 0x41, 0x52, 0x5f, 0x52, 0x4f, 0x55, 0x54, 0x45,
|
||||
0x10, 0x16, 0x2a, 0xae, 0x01, 0x0a, 0x0c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74,
|
||||
0x61, 0x74, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x49, 0x4e, 0x5f, 0x46, 0x4c, 0x49, 0x47, 0x48, 0x54,
|
||||
0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x55, 0x43, 0x43, 0x45, 0x45, 0x44, 0x45, 0x44, 0x10,
|
||||
0x01, 0x12, 0x12, 0x0a, 0x0e, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45,
|
||||
0x4f, 0x55, 0x54, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f,
|
||||
0x4e, 0x4f, 0x5f, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x10, 0x03, 0x12, 0x10, 0x0a, 0x0c, 0x46, 0x41,
|
||||
0x49, 0x4c, 0x45, 0x44, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x12, 0x24, 0x0a, 0x20,
|
||||
0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54,
|
||||
0x5f, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x53,
|
||||
0x10, 0x05, 0x12, 0x1f, 0x0a, 0x1b, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x49, 0x4e, 0x53,
|
||||
0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x42, 0x41, 0x4c, 0x41, 0x4e, 0x43,
|
||||
0x45, 0x10, 0x06, 0x2a, 0x51, 0x0a, 0x18, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x48, 0x6f,
|
||||
0x6c, 0x64, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12,
|
||||
0x0a, 0x0a, 0x06, 0x53, 0x45, 0x54, 0x54, 0x4c, 0x45, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x46,
|
||||
0x41, 0x49, 0x4c, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x52, 0x45, 0x53, 0x55, 0x4d, 0x45, 0x10,
|
||||
0x02, 0x12, 0x13, 0x0a, 0x0f, 0x52, 0x45, 0x53, 0x55, 0x4d, 0x45, 0x5f, 0x4d, 0x4f, 0x44, 0x49,
|
||||
0x46, 0x49, 0x45, 0x44, 0x10, 0x03, 0x2a, 0x35, 0x0a, 0x10, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74,
|
||||
0x61, 0x74, 0x75, 0x73, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x45, 0x4e,
|
||||
0x41, 0x42, 0x4c, 0x45, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c,
|
||||
0x45, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x55, 0x54, 0x4f, 0x10, 0x02, 0x32, 0xc6, 0x0e,
|
||||
0x0a, 0x06, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x12, 0x40, 0x0a, 0x0d, 0x53, 0x65, 0x6e, 0x64,
|
||||
0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x32, 0x12, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74,
|
||||
0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e,
|
||||
0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
|
||||
0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0e, 0x54, 0x72,
|
||||
0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x32, 0x12, 0x1e, 0x2e, 0x72,
|
||||
0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c,
|
||||
0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a,
|
||||
0x02, 0x38, 0x01, 0x22, 0x82, 0x01, 0x0a, 0x17, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68,
|
||||
0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
|
||||
0x32, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e,
|
||||
0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f,
|
||||
0x69, 0x6e, 0x74, 0x12, 0x33, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e,
|
||||
0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x1a, 0x0a, 0x18, 0x55, 0x70, 0x64, 0x61,
|
||||
0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x43, 0x0a, 0x11, 0x41, 0x64, 0x64, 0x41, 0x6c, 0x69, 0x61, 0x73,
|
||||
0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x0a, 0x61, 0x6c, 0x69,
|
||||
0x61, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e,
|
||||
0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, 0x70, 0x52, 0x09,
|
||||
0x61, 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, 0x70, 0x73, 0x22, 0x44, 0x0a, 0x12, 0x41, 0x64, 0x64,
|
||||
0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
|
||||
0x2e, 0x0a, 0x0a, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x73, 0x18, 0x01, 0x20,
|
||||
0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x6c, 0x69, 0x61,
|
||||
0x73, 0x4d, 0x61, 0x70, 0x52, 0x09, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, 0x70, 0x73, 0x22,
|
||||
0x46, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x0a, 0x61, 0x6c, 0x69, 0x61, 0x73,
|
||||
0x5f, 0x6d, 0x61, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e,
|
||||
0x72, 0x70, 0x63, 0x2e, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, 0x70, 0x52, 0x09, 0x61, 0x6c,
|
||||
0x69, 0x61, 0x73, 0x4d, 0x61, 0x70, 0x73, 0x22, 0x47, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74,
|
||||
0x65, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x12, 0x2e, 0x0a, 0x0a, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x73, 0x18, 0x01,
|
||||
0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x6c, 0x69,
|
||||
0x61, 0x73, 0x4d, 0x61, 0x70, 0x52, 0x09, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, 0x70, 0x73,
|
||||
0x22, 0x2c, 0x0a, 0x14, 0x46, 0x69, 0x6e, 0x64, 0x42, 0x61, 0x73, 0x65, 0x41, 0x6c, 0x69, 0x61,
|
||||
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x6c, 0x69, 0x61,
|
||||
0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x22, 0x2b,
|
||||
0x0a, 0x15, 0x46, 0x69, 0x6e, 0x64, 0x42, 0x61, 0x73, 0x65, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x2a, 0x81, 0x04, 0x0a, 0x0d,
|
||||
0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x12, 0x0b, 0x0a,
|
||||
0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x4f,
|
||||
0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x4f, 0x4e, 0x49,
|
||||
0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x43, 0x4f, 0x44, 0x45, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x4c,
|
||||
0x49, 0x4e, 0x4b, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x45, 0x4c, 0x49, 0x47, 0x49, 0x42, 0x4c, 0x45,
|
||||
0x10, 0x03, 0x12, 0x14, 0x0a, 0x10, 0x4f, 0x4e, 0x5f, 0x43, 0x48, 0x41, 0x49, 0x4e, 0x5f, 0x54,
|
||||
0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x04, 0x12, 0x14, 0x0a, 0x10, 0x48, 0x54, 0x4c, 0x43,
|
||||
0x5f, 0x45, 0x58, 0x43, 0x45, 0x45, 0x44, 0x53, 0x5f, 0x4d, 0x41, 0x58, 0x10, 0x05, 0x12, 0x18,
|
||||
0x0a, 0x14, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x42,
|
||||
0x41, 0x4c, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x06, 0x12, 0x16, 0x0a, 0x12, 0x49, 0x4e, 0x43, 0x4f,
|
||||
0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x46, 0x4f, 0x52, 0x57, 0x41, 0x52, 0x44, 0x10, 0x07,
|
||||
0x12, 0x13, 0x0a, 0x0f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x44, 0x44, 0x5f, 0x46, 0x41, 0x49,
|
||||
0x4c, 0x45, 0x44, 0x10, 0x08, 0x12, 0x15, 0x0a, 0x11, 0x46, 0x4f, 0x52, 0x57, 0x41, 0x52, 0x44,
|
||||
0x53, 0x5f, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x44, 0x10, 0x09, 0x12, 0x14, 0x0a, 0x10,
|
||||
0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x45, 0x44,
|
||||
0x10, 0x0a, 0x12, 0x15, 0x0a, 0x11, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x55, 0x4e,
|
||||
0x44, 0x45, 0x52, 0x50, 0x41, 0x49, 0x44, 0x10, 0x0b, 0x12, 0x1b, 0x0a, 0x17, 0x49, 0x4e, 0x56,
|
||||
0x4f, 0x49, 0x43, 0x45, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, 0x5f, 0x54, 0x4f, 0x4f, 0x5f,
|
||||
0x53, 0x4f, 0x4f, 0x4e, 0x10, 0x0c, 0x12, 0x14, 0x0a, 0x10, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43,
|
||||
0x45, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x4f, 0x50, 0x45, 0x4e, 0x10, 0x0d, 0x12, 0x17, 0x0a, 0x13,
|
||||
0x4d, 0x50, 0x50, 0x5f, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45,
|
||||
0x4f, 0x55, 0x54, 0x10, 0x0e, 0x12, 0x14, 0x0a, 0x10, 0x41, 0x44, 0x44, 0x52, 0x45, 0x53, 0x53,
|
||||
0x5f, 0x4d, 0x49, 0x53, 0x4d, 0x41, 0x54, 0x43, 0x48, 0x10, 0x0f, 0x12, 0x16, 0x0a, 0x12, 0x53,
|
||||
0x45, 0x54, 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, 0x5f, 0x4d, 0x49, 0x53, 0x4d, 0x41, 0x54, 0x43,
|
||||
0x48, 0x10, 0x10, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x45, 0x54, 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c,
|
||||
0x5f, 0x54, 0x4f, 0x4f, 0x5f, 0x4c, 0x4f, 0x57, 0x10, 0x11, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x45,
|
||||
0x54, 0x5f, 0x4f, 0x56, 0x45, 0x52, 0x50, 0x41, 0x49, 0x44, 0x10, 0x12, 0x12, 0x13, 0x0a, 0x0f,
|
||||
0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x10,
|
||||
0x13, 0x12, 0x13, 0x0a, 0x0f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4b, 0x45, 0x59,
|
||||
0x53, 0x45, 0x4e, 0x44, 0x10, 0x14, 0x12, 0x13, 0x0a, 0x0f, 0x4d, 0x50, 0x50, 0x5f, 0x49, 0x4e,
|
||||
0x5f, 0x50, 0x52, 0x4f, 0x47, 0x52, 0x45, 0x53, 0x53, 0x10, 0x15, 0x12, 0x12, 0x0a, 0x0e, 0x43,
|
||||
0x49, 0x52, 0x43, 0x55, 0x4c, 0x41, 0x52, 0x5f, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x10, 0x16, 0x2a,
|
||||
0xae, 0x01, 0x0a, 0x0c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65,
|
||||
0x12, 0x0d, 0x0a, 0x09, 0x49, 0x4e, 0x5f, 0x46, 0x4c, 0x49, 0x47, 0x48, 0x54, 0x10, 0x00, 0x12,
|
||||
0x0d, 0x0a, 0x09, 0x53, 0x55, 0x43, 0x43, 0x45, 0x45, 0x44, 0x45, 0x44, 0x10, 0x01, 0x12, 0x12,
|
||||
0x0a, 0x0e, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54,
|
||||
0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x4e, 0x4f, 0x5f,
|
||||
0x52, 0x4f, 0x55, 0x54, 0x45, 0x10, 0x03, 0x12, 0x10, 0x0a, 0x0c, 0x46, 0x41, 0x49, 0x4c, 0x45,
|
||||
0x44, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x12, 0x24, 0x0a, 0x20, 0x46, 0x41, 0x49,
|
||||
0x4c, 0x45, 0x44, 0x5f, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x50, 0x41,
|
||||
0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x53, 0x10, 0x05, 0x12,
|
||||
0x1f, 0x0a, 0x1b, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46,
|
||||
0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x42, 0x41, 0x4c, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x06,
|
||||
0x2a, 0x51, 0x0a, 0x18, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x48, 0x6f, 0x6c, 0x64, 0x46,
|
||||
0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06,
|
||||
0x53, 0x45, 0x54, 0x54, 0x4c, 0x45, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x46, 0x41, 0x49, 0x4c,
|
||||
0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x52, 0x45, 0x53, 0x55, 0x4d, 0x45, 0x10, 0x02, 0x12, 0x13,
|
||||
0x0a, 0x0f, 0x52, 0x45, 0x53, 0x55, 0x4d, 0x45, 0x5f, 0x4d, 0x4f, 0x44, 0x49, 0x46, 0x49, 0x45,
|
||||
0x44, 0x10, 0x03, 0x2a, 0x35, 0x0a, 0x10, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75,
|
||||
0x73, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x45, 0x4e, 0x41, 0x42, 0x4c,
|
||||
0x45, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x01,
|
||||
0x12, 0x08, 0x0a, 0x04, 0x41, 0x55, 0x54, 0x4f, 0x10, 0x02, 0x32, 0xc6, 0x0e, 0x0a, 0x06, 0x52,
|
||||
0x6f, 0x75, 0x74, 0x65, 0x72, 0x12, 0x40, 0x0a, 0x0d, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79,
|
||||
0x6d, 0x65, 0x6e, 0x74, 0x56, 0x32, 0x12, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72,
|
||||
0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61,
|
||||
0x79, 0x6d, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0e, 0x54, 0x72, 0x61, 0x63, 0x6b,
|
||||
0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x32, 0x12, 0x1e, 0x2e, 0x72, 0x6f, 0x75, 0x74,
|
||||
0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65,
|
||||
0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70,
|
||||
0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0d, 0x54,
|
||||
0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x2e, 0x72,
|
||||
0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61,
|
||||
0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x6c,
|
||||
0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x42,
|
||||
0x0a, 0x0d, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12,
|
||||
0x1f, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x63,
|
||||
0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74,
|
||||
0x30, 0x01, 0x12, 0x4b, 0x0a, 0x10, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x52, 0x6f,
|
||||
0x75, 0x74, 0x65, 0x46, 0x65, 0x65, 0x12, 0x1a, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72,
|
||||
0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x52,
|
||||
0x6f, 0x75, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
|
||||
0x51, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x1d,
|
||||
0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54,
|
||||
0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e,
|
||||
0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f,
|
||||
0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x88,
|
||||
0x02, 0x01, 0x12, 0x42, 0x0a, 0x0d, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74,
|
||||
0x65, 0x56, 0x32, 0x12, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e,
|
||||
0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x54, 0x4c, 0x43, 0x41,
|
||||
0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x12, 0x64, 0x0a, 0x13, 0x52, 0x65, 0x73, 0x65, 0x74, 0x4d,
|
||||
0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, 0x25, 0x2e,
|
||||
0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x74, 0x4d,
|
||||
0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63,
|
||||
0x2e, 0x52, 0x65, 0x73, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e,
|
||||
0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, 0x0a, 0x13,
|
||||
0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74,
|
||||
0x72, 0x6f, 0x6c, 0x12, 0x25, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e,
|
||||
0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74,
|
||||
0x72, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x72, 0x6f, 0x75,
|
||||
0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x69, 0x73, 0x73,
|
||||
0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x12, 0x6a, 0x0a, 0x15, 0x58, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4d, 0x69, 0x73,
|
||||
0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, 0x27, 0x2e, 0x72, 0x6f,
|
||||
0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x58, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4d,
|
||||
0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63,
|
||||
0x2e, 0x58, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43,
|
||||
0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x70,
|
||||
0x0a, 0x17, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74,
|
||||
0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x29, 0x2e, 0x72, 0x6f, 0x75, 0x74,
|
||||
0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e,
|
||||
0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63,
|
||||
0x2e, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72,
|
||||
0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x12, 0x70, 0x0a, 0x17, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f,
|
||||
0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x29, 0x2e, 0x72, 0x6f,
|
||||
0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69,
|
||||
0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72,
|
||||
0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e,
|
||||
0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x12, 0x5b, 0x0a, 0x10, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x62, 0x61,
|
||||
0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x22, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72,
|
||||
0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c,
|
||||
0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x72, 0x6f, 0x75,
|
||||
0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x62,
|
||||
0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
|
||||
0x49, 0x0a, 0x0a, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x1c, 0x2e,
|
||||
0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52,
|
||||
0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x6f,
|
||||
0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x6f, 0x75,
|
||||
0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x13, 0x53, 0x75,
|
||||
0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74,
|
||||
0x73, 0x12, 0x25, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75,
|
||||
0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74,
|
||||
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65,
|
||||
0x72, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01,
|
||||
0x12, 0x4d, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12,
|
||||
0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64,
|
||||
0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18,
|
||||
0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65,
|
||||
0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x03, 0x88, 0x02, 0x01, 0x30, 0x01, 0x12,
|
||||
0x4f, 0x0a, 0x0c, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12,
|
||||
0x1e, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x63,
|
||||
0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||
0x18, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d,
|
||||
0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x03, 0x88, 0x02, 0x01, 0x30, 0x01,
|
||||
0x12, 0x66, 0x0a, 0x0f, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70,
|
||||
0x74, 0x6f, 0x72, 0x12, 0x27, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e,
|
||||
0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72,
|
||||
0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x26, 0x2e, 0x72,
|
||||
0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64,
|
||||
0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, 0x5b, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61,
|
||||
0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x22, 0x2e, 0x72,
|
||||
0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43,
|
||||
0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x1a, 0x23, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64,
|
||||
0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x14, 0x58, 0x41, 0x64, 0x64, 0x4c, 0x6f, 0x63,
|
||||
0x61, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x12, 0x1c, 0x2e,
|
||||
0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x41, 0x6c, 0x69,
|
||||
0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x6f,
|
||||
0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x41, 0x6c, 0x69, 0x61, 0x73,
|
||||
0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5c, 0x0a, 0x17, 0x58, 0x44,
|
||||
0x65, 0x6c, 0x65, 0x74, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x41, 0x6c,
|
||||
0x69, 0x61, 0x73, 0x65, 0x73, 0x12, 0x1f, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70,
|
||||
0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72,
|
||||
0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73,
|
||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5c, 0x0a, 0x17, 0x58, 0x46, 0x69, 0x6e,
|
||||
0x64, 0x42, 0x61, 0x73, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x41, 0x6c,
|
||||
0x69, 0x61, 0x73, 0x12, 0x1f, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e,
|
||||
0x46, 0x69, 0x6e, 0x64, 0x42, 0x61, 0x73, 0x65, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63,
|
||||
0x2e, 0x46, 0x69, 0x6e, 0x64, 0x42, 0x61, 0x73, 0x65, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
|
||||
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65,
|
||||
0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f,
|
||||
0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x33,
|
||||
0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e,
|
||||
0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12,
|
||||
0x4b, 0x0a, 0x10, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65,
|
||||
0x46, 0x65, 0x65, 0x12, 0x1a, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e,
|
||||
0x52, 0x6f, 0x75, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||
0x1b, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74,
|
||||
0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51, 0x0a, 0x0b,
|
||||
0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x1d, 0x2e, 0x72, 0x6f,
|
||||
0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f,
|
||||
0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x72, 0x6f, 0x75,
|
||||
0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75,
|
||||
0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x12,
|
||||
0x42, 0x0a, 0x0d, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x56, 0x32,
|
||||
0x12, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e,
|
||||
0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||
0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x54, 0x4c, 0x43, 0x41, 0x74, 0x74, 0x65,
|
||||
0x6d, 0x70, 0x74, 0x12, 0x64, 0x0a, 0x13, 0x52, 0x65, 0x73, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73,
|
||||
0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, 0x25, 0x2e, 0x72, 0x6f, 0x75,
|
||||
0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73,
|
||||
0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x1a, 0x26, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65,
|
||||
0x73, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
|
||||
0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, 0x0a, 0x13, 0x51, 0x75, 0x65,
|
||||
0x72, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
|
||||
0x12, 0x25, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65,
|
||||
0x72, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72,
|
||||
0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e,
|
||||
0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
|
||||
0x6a, 0x0a, 0x15, 0x58, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f,
|
||||
0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, 0x27, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65,
|
||||
0x72, 0x72, 0x70, 0x63, 0x2e, 0x58, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4d, 0x69, 0x73, 0x73,
|
||||
0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x1a, 0x28, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x58, 0x49,
|
||||
0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74,
|
||||
0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x70, 0x0a, 0x17, 0x47,
|
||||
0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
|
||||
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x29, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72,
|
||||
0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e,
|
||||
0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x1a, 0x2a, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65,
|
||||
0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43,
|
||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x70, 0x0a,
|
||||
0x17, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72,
|
||||
0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x29, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65,
|
||||
0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43,
|
||||
0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75,
|
||||
0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e,
|
||||
0x53, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
|
||||
0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
|
||||
0x5b, 0x0a, 0x10, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c,
|
||||
0x69, 0x74, 0x79, 0x12, 0x22, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e,
|
||||
0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72,
|
||||
0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69,
|
||||
0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0a,
|
||||
0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x1c, 0x2e, 0x72, 0x6f, 0x75,
|
||||
0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x6f, 0x75, 0x74,
|
||||
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65,
|
||||
0x72, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x13, 0x53, 0x75, 0x62, 0x73, 0x63,
|
||||
0x72, 0x69, 0x62, 0x65, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x25,
|
||||
0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63,
|
||||
0x72, 0x69, 0x62, 0x65, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70,
|
||||
0x63, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x4d, 0x0a,
|
||||
0x0b, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1d, 0x2e, 0x72,
|
||||
0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79,
|
||||
0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x6f,
|
||||
0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53,
|
||||
0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x03, 0x88, 0x02, 0x01, 0x30, 0x01, 0x12, 0x4f, 0x0a, 0x0c,
|
||||
0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1e, 0x2e, 0x72,
|
||||
0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61,
|
||||
0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72,
|
||||
0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74,
|
||||
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x03, 0x88, 0x02, 0x01, 0x30, 0x01, 0x12, 0x66, 0x0a,
|
||||
0x0f, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72,
|
||||
0x12, 0x27, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72,
|
||||
0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70,
|
||||
0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x26, 0x2e, 0x72, 0x6f, 0x75, 0x74,
|
||||
0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c,
|
||||
0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x28, 0x01, 0x30, 0x01, 0x12, 0x5b, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43,
|
||||
0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x22, 0x2e, 0x72, 0x6f, 0x75, 0x74,
|
||||
0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e,
|
||||
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e,
|
||||
0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65,
|
||||
0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x12, 0x53, 0x0a, 0x14, 0x58, 0x41, 0x64, 0x64, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x43,
|
||||
0x68, 0x61, 0x6e, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x72, 0x6f, 0x75,
|
||||
0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65,
|
||||
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65,
|
||||
0x72, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5c, 0x0a, 0x17, 0x58, 0x44, 0x65, 0x6c, 0x65,
|
||||
0x74, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x41, 0x6c, 0x69, 0x61, 0x73,
|
||||
0x65, 0x73, 0x12, 0x1f, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x44,
|
||||
0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75,
|
||||
0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e,
|
||||
0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5c, 0x0a, 0x17, 0x58, 0x46, 0x69, 0x6e, 0x64, 0x42, 0x61,
|
||||
0x73, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x41, 0x6c, 0x69, 0x61, 0x73,
|
||||
0x12, 0x1f, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x69, 0x6e,
|
||||
0x64, 0x42, 0x61, 0x73, 0x65, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x1a, 0x20, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x69,
|
||||
0x6e, 0x64, 0x42, 0x61, 0x73, 0x65, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
|
||||
0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f,
|
||||
0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x72, 0x6f, 0x75,
|
||||
0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
|
|||
|
|
@ -1003,7 +1003,8 @@ message ForwardHtlcInterceptRequest {
|
|||
// The requested outgoing channel id for this forwarded htlc. Because of
|
||||
// non-strict forwarding, this isn't necessarily the channel over which the
|
||||
// packet will be forwarded eventually. A different channel to the same peer
|
||||
// may be selected as well.
|
||||
// may be selected as well. This is set to a sentinel value (all bits set)
|
||||
// if the outgoing_requested_node_id is specified for blinded routes.
|
||||
uint64 outgoing_requested_chan_id = 7;
|
||||
|
||||
// The outgoing htlc amount.
|
||||
|
|
@ -1025,6 +1026,19 @@ message ForwardHtlcInterceptRequest {
|
|||
|
||||
// The custom records of the peer's incoming p2p wire message.
|
||||
map<uint64, bytes> in_wire_custom_records = 11;
|
||||
|
||||
// The requested outgoing node for a blinded forward. When non-empty, this
|
||||
// field contains exactly one 33-byte compressed public key and
|
||||
// outgoing_requested_chan_id is set to 18446744073709551615
|
||||
// (0xffffffffffffffff). Clients MUST NOT interpret that value as an actual
|
||||
// channel ID; the presence of this field identifies a node-addressed
|
||||
// forward.
|
||||
//
|
||||
// The possible next-hop representations are:
|
||||
// node ID empty, channel ID 0: final receive;
|
||||
// node ID empty, ordinary channel ID: channel-addressed forward;
|
||||
// node ID present, channel ID MaxUint64: node-addressed forward.
|
||||
bytes outgoing_requested_node_id = 12;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1501,7 +1501,7 @@
|
|||
"outgoing_requested_chan_id": {
|
||||
"type": "string",
|
||||
"format": "uint64",
|
||||
"description": "The requested outgoing channel id for this forwarded htlc. Because of\nnon-strict forwarding, this isn't necessarily the channel over which the\npacket will be forwarded eventually. A different channel to the same peer\nmay be selected as well."
|
||||
"description": "The requested outgoing channel id for this forwarded htlc. Because of\nnon-strict forwarding, this isn't necessarily the channel over which the\npacket will be forwarded eventually. A different channel to the same peer\nmay be selected as well. This is set to a sentinel value (all bits set)\nif the outgoing_requested_node_id is specified for blinded routes."
|
||||
},
|
||||
"outgoing_amount_msat": {
|
||||
"type": "string",
|
||||
|
|
@ -1538,6 +1538,11 @@
|
|||
"format": "byte"
|
||||
},
|
||||
"description": "The custom records of the peer's incoming p2p wire message."
|
||||
},
|
||||
"outgoing_requested_node_id": {
|
||||
"type": "string",
|
||||
"format": "byte",
|
||||
"description": "The requested outgoing node for a blinded forward. When non-empty, this\nfield contains exactly one 33-byte compressed public key and\noutgoing_requested_chan_id is set to 18446744073709551615\n(0xffffffffffffffff). Clients MUST NOT interpret that value as an actual\nchannel ID; the presence of this field identifies a node-addressed\nforward.\n\nThe possible next-hop representations are:\n node ID empty, channel ID 0: final receive;\n node ID empty, ordinary channel ID: channel-addressed forward;\n node ID present, channel ID MaxUint64: node-addressed forward."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -31,7 +31,9 @@ type BlindedRouteData struct {
|
|||
|
||||
// NextNodeID is the node ID of the next node on the path. In the
|
||||
// context of blinded path payments, this is used to indicate the
|
||||
// presence of dummy hops that need to be peeled from the onion.
|
||||
// presence of dummy hops that need to be peeled from the onion, or to
|
||||
// identify a real next-node forwarding target when the public key is
|
||||
// not ours.
|
||||
NextNodeID tlv.OptionalRecordT[tlv.TlvType4, *btcec.PublicKey]
|
||||
|
||||
// PathID is a secret set of bytes that the blinded path creator will
|
||||
|
|
|
|||
|
|
@ -1170,7 +1170,9 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc
|
|||
|
||||
require.Equal(
|
||||
t, route.Hops[i+1].ChannelID,
|
||||
payload.FwdInfo.NextHop.ToUint64(),
|
||||
payload.FwdInfo.NextHopChannel().UnwrapOr(
|
||||
switchhop.Exit,
|
||||
).ToUint64(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -1183,7 +1185,11 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc
|
|||
|
||||
// The final hop should have a next hop value of all zeroes in order
|
||||
// to indicate it's the exit hop.
|
||||
require.Zero(t, payload.FwdInfo.NextHop.ToUint64())
|
||||
require.Zero(
|
||||
t, payload.FwdInfo.NextHopChannel().UnwrapOr(
|
||||
switchhop.Exit,
|
||||
).ToUint64(),
|
||||
)
|
||||
|
||||
var expectedTotalFee lnwire.MilliSatoshi
|
||||
for i := 0; i < expectedHopCount; i++ {
|
||||
|
|
|
|||
|
|
@ -106,14 +106,26 @@ func (p *preimageBeacon) SubscribeUpdates(
|
|||
},
|
||||
}
|
||||
|
||||
// Report the forwarding next hop to the interceptor. A channel-ID next
|
||||
// hop is reported directly; a node-ID next hop has no outgoing channel
|
||||
// of its own, so outgoingChanID is hop.Exit and the requested node ID
|
||||
// is exposed separately, exactly as the off-chain interceptor does.
|
||||
// This is the requested next hop, not the channel that non-strict
|
||||
// forwarding eventually selects, so we deliberately do not resolve it
|
||||
// against the circuit map. The RPC boundary maps a node-ID hop to the
|
||||
// NodeIDForwardSCID sentinel for the client.
|
||||
//
|
||||
// Notify the htlc interceptor. There may be a client connected
|
||||
// and willing to supply a preimage.
|
||||
packet := &htlcswitch.InterceptedPacket{
|
||||
Hash: htlc.RHash,
|
||||
IncomingExpiry: htlc.RefundTimeout,
|
||||
IncomingAmount: htlc.Amt,
|
||||
IncomingCircuit: inKey,
|
||||
OutgoingChanID: payload.FwdInfo.NextHop,
|
||||
Hash: htlc.RHash,
|
||||
IncomingExpiry: htlc.RefundTimeout,
|
||||
IncomingAmount: htlc.Amt,
|
||||
IncomingCircuit: inKey,
|
||||
OutgoingChanID: payload.FwdInfo.NextHopChannel().UnwrapOr(
|
||||
hop.Exit,
|
||||
),
|
||||
OutgoingNodeID: payload.FwdInfo.NextHopNode(),
|
||||
OutgoingExpiry: payload.FwdInfo.OutgoingCLTV,
|
||||
OutgoingAmount: payload.FwdInfo.AmountToForward,
|
||||
InOnionCustomRecords: payload.CustomRecords(),
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/fn/v2"
|
||||
"github.com/lightningnetwork/lnd/graph/db/models"
|
||||
"github.com/lightningnetwork/lnd/htlcswitch"
|
||||
"github.com/lightningnetwork/lnd/htlcswitch/hop"
|
||||
|
|
@ -97,6 +98,47 @@ func TestWitnessBeaconInterceptErrorCancels(t *testing.T) {
|
|||
p.RUnlock()
|
||||
}
|
||||
|
||||
// TestWitnessBeaconInterceptNodeID asserts that for a node-ID next hop the
|
||||
// on-chain interceptor reports the exit-hop SCID (hop.Exit) together with the
|
||||
// requested next node's public key, matching the off-chain interceptor. The
|
||||
// next hop is not resolved against the circuit map; the RPC boundary maps
|
||||
// hop.Exit to the sentinel.
|
||||
func TestWitnessBeaconInterceptNodeID(t *testing.T) {
|
||||
var interceptedFwd htlcswitch.InterceptedForward
|
||||
interceptor := func(fwd htlcswitch.InterceptedForward) error {
|
||||
interceptedFwd = fwd
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
p := newPreimageBeacon(
|
||||
&mockWitnessCache{}, interceptor,
|
||||
func(models.CircuitKey) error {
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
var nodeID [33]byte
|
||||
nodeID[0] = 0x02
|
||||
|
||||
payload := &hop.Payload{
|
||||
FwdInfo: hop.ForwardingInfo{
|
||||
NextHop: hop.NewNodeNextHop(nodeID),
|
||||
},
|
||||
}
|
||||
|
||||
_, err := p.SubscribeUpdates(
|
||||
lnwire.NewShortChanIDFromInt(1),
|
||||
&channeldb.HTLC{RHash: lntypes.Hash{1}},
|
||||
payload, []byte{2},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
packet := interceptedFwd.Packet()
|
||||
require.Equal(t, hop.Exit, packet.OutgoingChanID)
|
||||
require.Equal(t, fn.Some(nodeID), packet.OutgoingNodeID)
|
||||
}
|
||||
|
||||
type mockWitnessCache struct {
|
||||
witnessCache
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue