Merge pull request #4018 from breez/intercept-forward-htlc

Intercept forward htlc
This commit is contained in:
Joost Jager 2020-06-22 10:18:14 +02:00 committed by GitHub
commit 8f2a2fc5da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1749 additions and 283 deletions

@ -0,0 +1,170 @@
package htlcswitch
import (
"fmt"
"sync"
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
)
var (
// ErrFwdNotExists is an error returned when the caller tries to resolve
// a forward that doesn't exist anymore.
ErrFwdNotExists = errors.New("forward does not exist")
)
// InterceptableSwitch is an implementation of ForwardingSwitch interface.
// This implementation is used like a proxy that wraps the switch and
// intercepts forward requests. A reference to the Switch is held in order
// to communicate back the interception result where the options are:
// Resume - forwards the original request to the switch as is.
// Settle - routes UpdateFulfillHTLC to the originating link.
// Fail - routes UpdateFailHTLC to the originating link.
type InterceptableSwitch struct {
sync.RWMutex
// htlcSwitch is the underline switch
htlcSwitch *Switch
// fwdInterceptor is the callback that is called for each forward of
// an incoming htlc. It should return true if it is interested in handling
// it.
fwdInterceptor ForwardInterceptor
}
// NewInterceptableSwitch returns an instance of InterceptableSwitch.
func NewInterceptableSwitch(s *Switch) *InterceptableSwitch {
return &InterceptableSwitch{htlcSwitch: s}
}
// SetInterceptor sets the ForwardInterceptor to be used.
func (s *InterceptableSwitch) SetInterceptor(
interceptor ForwardInterceptor) {
s.Lock()
defer s.Unlock()
s.fwdInterceptor = interceptor
}
// ForwardPackets attempts to forward the batch of htlcs through the
// switch, any failed packets will be returned to the provided
// ChannelLink. The link's quit signal should be provided to allow
// cancellation of forwarding during link shutdown.
func (s *InterceptableSwitch) ForwardPackets(linkQuit chan struct{},
packets ...*htlcPacket) error {
var interceptor ForwardInterceptor
s.Lock()
interceptor = s.fwdInterceptor
s.Unlock()
// Optimize for the case we don't have an interceptor.
if interceptor == nil {
return s.htlcSwitch.ForwardPackets(linkQuit, packets...)
}
var notIntercepted []*htlcPacket
for _, p := range packets {
if !s.interceptForward(p, interceptor, linkQuit) {
notIntercepted = append(notIntercepted, p)
}
}
return s.htlcSwitch.ForwardPackets(linkQuit, notIntercepted...)
}
// interceptForward checks if there is any external interceptor interested in
// this packet. Currently only htlc type of UpdateAddHTLC that are forwarded
// are being checked for interception. It can be extended in the future given
// the right use case.
func (s *InterceptableSwitch) interceptForward(packet *htlcPacket,
interceptor ForwardInterceptor, linkQuit chan struct{}) bool {
switch htlc := packet.htlc.(type) {
case *lnwire.UpdateAddHTLC:
// We are not interested in intercepting initated payments.
if packet.incomingChanID == hop.Source {
return false
}
intercepted := &interceptedForward{
linkQuit: linkQuit,
htlc: htlc,
packet: packet,
htlcSwitch: s.htlcSwitch,
}
// If this htlc was intercepted, don't handle the forward.
return interceptor(intercepted)
default:
return false
}
}
// interceptedForward implements the InterceptedForward interface.
// It is passed from the switch to external interceptors that are interested
// in holding forwards and resolve them manually.
type interceptedForward struct {
linkQuit chan struct{}
htlc *lnwire.UpdateAddHTLC
packet *htlcPacket
htlcSwitch *Switch
}
// Packet returns the intercepted htlc packet.
func (f *interceptedForward) Packet() lnwire.UpdateAddHTLC {
return *f.htlc
}
// CircuitKey returns the circuit key for the intercepted htlc.
func (f *interceptedForward) CircuitKey() channeldb.CircuitKey {
return channeldb.CircuitKey{
ChanID: f.packet.incomingChanID,
HtlcID: f.packet.incomingHTLCID,
}
}
// Resume resumes the default behavior as if the packet was not intercepted.
func (f *interceptedForward) Resume() error {
return f.htlcSwitch.ForwardPackets(f.linkQuit, f.packet)
}
// Fail forward a failed packet to the switch.
func (f *interceptedForward) Fail() error {
reason, err := f.packet.obfuscator.EncryptFirstHop(lnwire.NewTemporaryChannelFailure(nil))
if err != nil {
return fmt.Errorf("failed to encrypt failure reason %v", err)
}
return f.resolve(&lnwire.UpdateFailHTLC{
Reason: reason,
})
}
// Settle forwards a settled packet to the switch.
func (f *interceptedForward) Settle(preimage lntypes.Preimage) error {
if !preimage.Matches(f.htlc.PaymentHash) {
return errors.New("preimage does not match hash")
}
return f.resolve(&lnwire.UpdateFulfillHTLC{
PaymentPreimage: preimage,
})
}
// resolve is used for both Settle and Fail and forwards the message to the
// switch.
func (f *interceptedForward) resolve(message lnwire.Message) error {
pkt := &htlcPacket{
incomingChanID: f.packet.incomingChanID,
incomingHTLCID: f.packet.incomingHTLCID,
outgoingChanID: f.packet.outgoingChanID,
outgoingHTLCID: f.packet.outgoingHTLCID,
isResolution: true,
circuit: f.packet.circuit,
htlc: message,
obfuscator: f.packet.obfuscator,
}
return f.htlcSwitch.mailOrchestrator.Deliver(pkt.incomingChanID, pkt)
}

@ -185,6 +185,46 @@ type TowerClient interface {
BackupState(*lnwire.ChannelID, *lnwallet.BreachRetribution, bool) error
}
// InterceptableHtlcForwarder is the interface to set the interceptor
// implementation that intercepts htlc forwards.
type InterceptableHtlcForwarder interface {
// SetInterceptor sets a ForwardInterceptor.
SetInterceptor(interceptor ForwardInterceptor)
}
// ForwardInterceptor is a function that is invoked from the switch for every
// incoming htlc that is intended to be forwarded. It is passed with the
// InterceptedForward that contains the information about the packet and a way
// to resolve it manually later in case it is held.
// The return value indicates if this handler will take control of this forward
// and resolve it later or let the switch execute its default behavior.
type ForwardInterceptor func(InterceptedForward) bool
// InterceptedForward is passed to the ForwardInterceptor for every forwarded
// htlc. It contains all the information about the packet which accordingly
// the interceptor decides if to hold or not.
// In addition this interface allows a later resolution by calling either
// Resume, Settle or Fail.
type InterceptedForward interface {
// CircuitKey returns the intercepted packet.
CircuitKey() channeldb.CircuitKey
// Packet returns the intercepted packet.
Packet() lnwire.UpdateAddHTLC
// Resume notifies the intention to resume an existing hold forward. This
// basically means the caller wants to resume with the default behavior for
// this htlc which usually means forward it.
Resume() error
// Settle notifies the intention to settle an existing hold
// forward with a given preimage.
Settle(lntypes.Preimage) error
// Fails notifies the intention to fail an existing hold forward
Fail() error
}
// htlcNotifier is an interface which represents the input side of the
// HtlcNotifier which htlc events are piped through. This interface is intended
// to allow for mocking of the htlcNotifier in tests, so is unexported because

@ -135,10 +135,10 @@ type ChannelLinkConfig struct {
Switch *Switch
// ForwardPackets attempts to forward the batch of htlcs through the
// switch, any failed packets will be returned to the provided
// ChannelLink. The link's quit signal should be provided to allow
// switch. The function returns and error in case it fails to send one or
// more packets. The link's quit signal should be provided to allow
// cancellation of forwarding during link shutdown.
ForwardPackets func(chan struct{}, ...*htlcPacket) chan error
ForwardPackets func(chan struct{}, ...*htlcPacket) error
// DecodeHopIterators facilitates batched decoding of HTLC Sphinx onion
// blobs, which are then used to inform how to forward an HTLC.
@ -2971,8 +2971,10 @@ func (l *channelLink) forwardBatch(packets ...*htlcPacket) {
filteredPkts = append(filteredPkts, pkt)
}
errChan := l.cfg.ForwardPackets(l.quit, filteredPkts...)
go handleBatchFwdErrs(errChan, l.log)
if err := l.cfg.ForwardPackets(l.quit, filteredPkts...); err != nil {
log.Errorf("Unhandled error while reforwarding htlc "+
"settle/fail over htlcswitch: %v", err)
}
}
// sendHTLCError functions cancels HTLC and send cancel message back to the

@ -86,7 +86,7 @@ type mailBoxConfig struct {
// forwardPackets send a varidic number of htlcPackets to the switch to
// be routed. A quit channel should be provided so that the call can
// properly exit during shutdown.
forwardPackets func(chan struct{}, ...*htlcPacket) chan error
forwardPackets func(chan struct{}, ...*htlcPacket) error
// clock is a time source for the mailbox.
clock clock.Clock
@ -680,8 +680,10 @@ func (m *memoryMailBox) FailAdd(pkt *htlcPacket) {
},
}
errChan := m.cfg.forwardPackets(m.quit, failPkt)
go handleBatchFwdErrs(errChan, log)
if err := m.cfg.forwardPackets(m.quit, failPkt); err != nil {
log.Errorf("Unhandled error while reforwarding packets "+
"settle/fail over htlcswitch: %v", err)
}
}
// MessageOutBox returns a channel that any new messages ready for delivery
@ -734,7 +736,7 @@ type mailOrchConfig struct {
// forwardPackets send a varidic number of htlcPackets to the switch to
// be routed. A quit channel should be provided so that the call can
// properly exit during shutdown.
forwardPackets func(chan struct{}, ...*htlcPacket) chan error
forwardPackets func(chan struct{}, ...*htlcPacket) error
// fetchUpdate retreives the most recent channel update for the channel
// this mailbox belongs to.

@ -218,16 +218,13 @@ func newMailboxContext(t *testing.T, startTime time.Time,
}
func (c *mailboxContext) forward(_ chan struct{},
pkts ...*htlcPacket) chan error {
pkts ...*htlcPacket) error {
for _, pkt := range pkts {
c.forwards <- pkt
}
errChan := make(chan error)
close(errChan)
return errChan
return nil
}
func (c *mailboxContext) sendAdds(start, num int) []*htlcPacket {
@ -555,12 +552,8 @@ func TestMailOrchestrator(t *testing.T) {
}, nil
},
forwardPackets: func(_ chan struct{},
pkts ...*htlcPacket) chan error {
// Close the channel immediately so the goroutine
// logging errors can exit.
errChan := make(chan error)
close(errChan)
return errChan
pkts ...*htlcPacket) error {
return nil
},
clock: clock.NewTestClock(time.Now()),
expiry: testExpiry,

@ -9,7 +9,6 @@ import (
"time"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btclog"
"github.com/btcsuite/btcutil"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/chainntnfs"
@ -548,24 +547,14 @@ func (s *Switch) IsForwardedHTLC(chanID lnwire.ShortChannelID,
// given to forward them through the router. The sending link's quit channel is
// used to prevent deadlocks when the switch stops a link in the midst of
// forwarding.
//
// NOTE: This method guarantees that the returned err chan will eventually be
// closed. The receiver should read on the channel until receiving such a
// signal.
func (s *Switch) ForwardPackets(linkQuit chan struct{},
packets ...*htlcPacket) chan error {
packets ...*htlcPacket) error {
var (
// fwdChan is a buffered channel used to receive err msgs from
// the htlcPlex when forwarding this batch.
fwdChan = make(chan error, len(packets))
// errChan is a buffered channel returned to the caller, that is
// proxied by the fwdChan. This method guarantees that errChan
// will be closed eventually to alert the receiver that it can
// stop reading from the channel.
errChan = make(chan error, len(packets))
// numSent keeps a running count of how many packets are
// forwarded to the switch, which determines how many responses
// we will wait for on the fwdChan..
@ -574,8 +563,7 @@ func (s *Switch) ForwardPackets(linkQuit chan struct{},
// No packets, nothing to do.
if len(packets) == 0 {
close(errChan)
return errChan
return nil
}
// Setup a barrier to prevent the background tasks from processing
@ -590,18 +578,13 @@ func (s *Switch) ForwardPackets(linkQuit chan struct{},
// it is already in the process of shutting down.
select {
case <-linkQuit:
close(errChan)
return errChan
return nil
case <-s.quit:
close(errChan)
return errChan
return nil
default:
// Spawn a goroutine the proxy the errs back to the returned err
// chan. This is done to ensure the err chan returned to the
// caller closed properly, alerting the receiver of completion
// or shutdown.
// Spawn a goroutine to log the errors returned from failed packets.
s.wg.Add(1)
go s.proxyFwdErrs(&numSent, &wg, fwdChan, errChan)
go s.logFwdErrs(&numSent, &wg, fwdChan)
}
// Make a first pass over the packets, forwarding any settles or fails.
@ -619,7 +602,7 @@ func (s *Switch) ForwardPackets(linkQuit chan struct{},
default:
err := s.routeAsync(packet, fwdChan, linkQuit)
if err != nil {
return errChan
return fmt.Errorf("failed to forward packet %v", err)
}
numSent++
}
@ -628,7 +611,7 @@ func (s *Switch) ForwardPackets(linkQuit chan struct{},
// If this batch did not contain any circuits to commit, we can return
// early.
if len(circuits) == 0 {
return errChan
return nil
}
// Write any circuits that we found to disk.
@ -664,7 +647,7 @@ func (s *Switch) ForwardPackets(linkQuit chan struct{},
for _, packet := range addedPackets {
err := s.routeAsync(packet, fwdChan, linkQuit)
if err != nil {
return errChan
return fmt.Errorf("failed to forward packet %v", err)
}
numSent++
}
@ -693,21 +676,12 @@ func (s *Switch) ForwardPackets(linkQuit chan struct{},
}
}
return errChan
return nil
}
// proxyFwdErrs transmits any errors received on `fwdChan` back to `errChan`,
// and guarantees that the `errChan` will be closed after 1) all errors have
// been sent, or 2) the switch has received a shutdown. The `errChan` should be
// buffered with at least the value of `num` after the barrier has been
// released.
//
// NOTE: The receiver of `errChan` should read until the channel closed, since
// this proxying guarantees that the close will happen.
func (s *Switch) proxyFwdErrs(num *int, wg *sync.WaitGroup,
fwdChan, errChan chan error) {
// logFwdErrs logs any errors received on `fwdChan`
func (s *Switch) logFwdErrs(num *int, wg *sync.WaitGroup, fwdChan chan error) {
defer s.wg.Done()
defer close(errChan)
// Wait here until the outer function has finished persisting
// and routing the packets. This guarantees we don't read from num until
@ -718,7 +692,10 @@ func (s *Switch) proxyFwdErrs(num *int, wg *sync.WaitGroup,
for i := 0; i < numSent; i++ {
select {
case err := <-fwdChan:
errChan <- err
if err != nil {
log.Errorf("Unhandled error while reforwarding htlc "+
"settle/fail over htlcswitch: %v", err)
}
case <-s.quit:
log.Errorf("unable to forward htlc packet " +
"htlc switch was stopped")
@ -1925,28 +1902,10 @@ func (s *Switch) reforwardSettleFails(fwdPkgs []*channeldb.FwdPkg) {
// Since this send isn't tied to a specific link, we pass a nil
// link quit channel, meaning the send will fail only if the
// switch receives a shutdown request.
errChan := s.ForwardPackets(nil, switchPackets...)
go handleBatchFwdErrs(errChan, log)
}
}
// handleBatchFwdErrs waits on the given errChan until it is closed, logging the
// errors returned from any unsuccessful forwarding attempts.
func handleBatchFwdErrs(errChan chan error, l btclog.Logger) {
for {
err, ok := <-errChan
if !ok {
// Err chan has been drained or switch is shutting down.
// Either way, return.
return
if err := s.ForwardPackets(nil, switchPackets...); err != nil {
log.Errorf("Unhandled error while reforwarding packets "+
"settle/fail over htlcswitch: %v", err)
}
if err == nil {
continue
}
l.Errorf("Unhandled error while reforwarding htlc "+
"settle/fail over htlcswitch: %v", err)
}
}

@ -11,6 +11,7 @@ import (
"time"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/fastsha256"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
@ -172,6 +173,13 @@ func TestSwitchSendPending(t *testing.T) {
t.Fatalf("unable to create alice server: %v", err)
}
bobPeer, err := newMockServer(
t, "bob", testStartingHeight, nil, testDefaultDelta,
)
if err != nil {
t.Fatalf("unable to create bob server: %v", err)
}
s, err := initSwitchWithDB(testStartingHeight, nil)
if err != nil {
t.Fatalf("unable to init switch: %v", err)
@ -181,7 +189,7 @@ func TestSwitchSendPending(t *testing.T) {
}
defer s.Stop()
chanID1, _, aliceChanID, bobChanID := genIDs()
chanID1, chanID2, aliceChanID, bobChanID := genIDs()
pendingChanID := lnwire.ShortChannelID{}
@ -192,6 +200,13 @@ func TestSwitchSendPending(t *testing.T) {
t.Fatalf("unable to add alice link: %v", err)
}
bobChannelLink := newMockChannelLink(
s, chanID2, bobChanID, bobPeer, true,
)
if err := s.AddLink(bobChannelLink); err != nil {
t.Fatalf("unable to add bob link: %v", err)
}
// Create request which should is being forwarded from Bob channel
// link to Alice channel link.
preimage, err := genPreimage()
@ -212,7 +227,17 @@ func TestSwitchSendPending(t *testing.T) {
// Send the ADD packet, this should not be forwarded out to the link
// since there are no eligible links.
err = forwardPackets(t, s, packet)
if err = s.ForwardPackets(nil, packet); err != nil {
t.Fatal(err)
}
select {
case p := <-bobChannelLink.packets:
if p.linkFailure != nil {
err = p.linkFailure
}
case <-time.After(time.Second):
t.Fatal("no timely reply from switch")
}
linkErr, ok := err.(*LinkError)
if !ok {
t.Fatalf("expected link error, got: %T", err)
@ -248,7 +273,7 @@ func TestSwitchSendPending(t *testing.T) {
packet.incomingHTLCID++
// Handle the request and checks that bob channel link received it.
if err := forwardPackets(t, s, packet); err != nil {
if err := s.ForwardPackets(nil, packet); err != nil {
t.Fatalf("unexpected forward failure: %v", err)
}
@ -321,7 +346,7 @@ func TestSwitchForward(t *testing.T) {
}
// Handle the request and checks that bob channel link received it.
if err := forwardPackets(t, s, packet); err != nil {
if err := s.ForwardPackets(nil, packet); err != nil {
t.Fatal(err)
}
@ -355,7 +380,7 @@ func TestSwitchForward(t *testing.T) {
}
// Handle the request and checks that payment circuit works properly.
if err := forwardPackets(t, s, packet); err != nil {
if err := s.ForwardPackets(nil, packet); err != nil {
t.Fatal(err)
}
@ -450,7 +475,7 @@ func TestSwitchForwardFailAfterFullAdd(t *testing.T) {
}
// Handle the request and checks that bob channel link received it.
if err := forwardPackets(t, s, ogPacket); err != nil {
if err := s.ForwardPackets(nil, ogPacket); err != nil {
t.Fatal(err)
}
@ -538,7 +563,7 @@ func TestSwitchForwardFailAfterFullAdd(t *testing.T) {
}
// Send the fail packet from the remote peer through the switch.
if err := <-s2.ForwardPackets(nil, fail); err != nil {
if err := s2.ForwardPackets(nil, fail); err != nil {
t.Fatalf(err.Error())
}
@ -562,9 +587,13 @@ func TestSwitchForwardFailAfterFullAdd(t *testing.T) {
}
// Send the fail packet from the remote peer through the switch.
if err := <-s2.ForwardPackets(nil, fail); err == nil {
t.Fatalf("expected failure when sending duplicate fail " +
"with no pending circuit")
if err := s.ForwardPackets(nil, fail); err != nil {
t.Fatal(err)
}
select {
case <-aliceChannelLink.packets:
t.Fatalf("expected duplicate fail to not arrive at the destination")
case <-time.After(time.Second):
}
}
@ -645,7 +674,7 @@ func TestSwitchForwardSettleAfterFullAdd(t *testing.T) {
}
// Handle the request and checks that bob channel link received it.
if err := forwardPackets(t, s, ogPacket); err != nil {
if err := s.ForwardPackets(nil, ogPacket); err != nil {
t.Fatal(err)
}
@ -735,7 +764,7 @@ func TestSwitchForwardSettleAfterFullAdd(t *testing.T) {
}
// Send the settle packet from the remote peer through the switch.
if err := <-s2.ForwardPackets(nil, settle); err != nil {
if err := s2.ForwardPackets(nil, settle); err != nil {
t.Fatalf(err.Error())
}
@ -759,10 +788,14 @@ func TestSwitchForwardSettleAfterFullAdd(t *testing.T) {
t.Fatalf("wrong amount of circuits")
}
// Send the settle packet again, which should fail.
if err := <-s2.ForwardPackets(nil, settle); err != nil {
t.Fatalf("expected success when sending duplicate settle " +
"with no pending circuit")
// Send the settle packet again, which not arrive at destination.
if err := s2.ForwardPackets(nil, settle); err != nil {
t.Fatal(err)
}
select {
case <-bobChannelLink.packets:
t.Fatalf("expected duplicate fail to not arrive at the destination")
case <-time.After(time.Second):
}
}
@ -843,7 +876,7 @@ func TestSwitchForwardDropAfterFullAdd(t *testing.T) {
}
// Handle the request and checks that bob channel link received it.
if err := forwardPackets(t, s, ogPacket); err != nil {
if err := s.ForwardPackets(nil, ogPacket); err != nil {
t.Fatal(err)
}
@ -916,7 +949,7 @@ func TestSwitchForwardDropAfterFullAdd(t *testing.T) {
// Resend the failed htlc. The packet will be dropped silently since the
// switch will detect that it has been half added previously.
if err := <-s2.ForwardPackets(nil, ogPacket); err != nil {
if err := s2.ForwardPackets(nil, ogPacket); err != nil {
t.Fatal(err)
}
@ -1008,7 +1041,7 @@ func TestSwitchForwardFailAfterHalfAdd(t *testing.T) {
}
// Handle the request and checks that bob channel link received it.
if err := forwardPackets(t, s, ogPacket); err != nil {
if err := s.ForwardPackets(nil, ogPacket); err != nil {
t.Fatal(err)
}
@ -1076,7 +1109,7 @@ func TestSwitchForwardFailAfterHalfAdd(t *testing.T) {
// Resend the failed htlc, it should be returned to alice since the
// switch will detect that it has been half added previously.
err = <-s2.ForwardPackets(nil, ogPacket)
err = s2.ForwardPackets(nil, ogPacket)
if err != nil {
t.Fatal(err)
}
@ -1174,7 +1207,7 @@ func TestSwitchForwardCircuitPersistence(t *testing.T) {
}
// Handle the request and checks that bob channel link received it.
if err := forwardPackets(t, s, ogPacket); err != nil {
if err := s.ForwardPackets(nil, ogPacket); err != nil {
t.Fatal(err)
}
@ -1264,7 +1297,7 @@ func TestSwitchForwardCircuitPersistence(t *testing.T) {
}
// Handle the request and checks that payment circuit works properly.
if err := <-s2.ForwardPackets(nil, ogPacket); err != nil {
if err := s2.ForwardPackets(nil, ogPacket); err != nil {
t.Fatal(err)
}
@ -1414,7 +1447,17 @@ func TestCircularForwards(t *testing.T) {
// Attempt to forward the packet and check for the expected
// error.
err = forwardPackets(t, s, packet)
if err = s.ForwardPackets(nil, packet); err != nil {
t.Fatal(err)
}
select {
case p := <-aliceChannelLink.packets:
if p.linkFailure != nil {
err = p.linkFailure
}
case <-time.After(time.Second):
t.Fatal("no timely reply from switch")
}
if !reflect.DeepEqual(err, test.expectedErr) {
t.Fatalf("expected: %v, got: %v",
test.expectedErr, err)
@ -1634,18 +1677,33 @@ func testSkipIneligibleLinksMultiHopForward(t *testing.T,
}
// The request to forward should fail as
err = forwardPackets(t, s, packet)
if err := s.ForwardPackets(nil, packet); err != nil {
t.Fatal(err)
}
// We select from all links and extract the error if exists.
// The packet must be selected but we don't always expect a link error.
var linkError *LinkError
select {
case p := <-aliceChannelLink.packets:
linkError = p.linkFailure
case p := <-bobChannelLink1.packets:
linkError = p.linkFailure
case p := <-bobChannelLink2.packets:
linkError = p.linkFailure
case <-time.After(time.Second):
t.Fatal("no timely reply from switch")
}
failure := obfuscator.(*mockObfuscator).failure
if testCase.expectedReply == lnwire.CodeNone {
if err != nil {
if linkError != nil {
t.Fatalf("forwarding should have succeeded")
}
if failure != nil {
t.Fatalf("unexpected failure %T", failure)
}
} else {
if err == nil {
if linkError == nil {
t.Fatalf("forwarding should have failed due to " +
"inactive link")
}
@ -1793,7 +1851,7 @@ func TestSwitchCancel(t *testing.T) {
}
// Handle the request and checks that bob channel link received it.
if err := forwardPackets(t, s, request); err != nil {
if err := s.ForwardPackets(nil, request); err != nil {
t.Fatal(err)
}
@ -1825,7 +1883,7 @@ func TestSwitchCancel(t *testing.T) {
}
// Handle the request and checks that payment circuit works properly.
if err := forwardPackets(t, s, request); err != nil {
if err := s.ForwardPackets(nil, request); err != nil {
t.Fatal(err)
}
@ -1908,7 +1966,7 @@ func TestSwitchAddSamePayment(t *testing.T) {
}
// Handle the request and checks that bob channel link received it.
if err := forwardPackets(t, s, request); err != nil {
if err := s.ForwardPackets(nil, request); err != nil {
t.Fatal(err)
}
@ -1938,7 +1996,7 @@ func TestSwitchAddSamePayment(t *testing.T) {
}
// Handle the request and checks that bob channel link received it.
if err := forwardPackets(t, s, request); err != nil {
if err := s.ForwardPackets(nil, request); err != nil {
t.Fatal(err)
}
@ -1967,7 +2025,7 @@ func TestSwitchAddSamePayment(t *testing.T) {
}
// Handle the request and checks that payment circuit works properly.
if err := forwardPackets(t, s, request); err != nil {
if err := s.ForwardPackets(nil, request); err != nil {
t.Fatal(err)
}
@ -1993,7 +2051,7 @@ func TestSwitchAddSamePayment(t *testing.T) {
}
// Handle the request and checks that payment circuit works properly.
if err := forwardPackets(t, s, request); err != nil {
if err := s.ForwardPackets(nil, request); err != nil {
t.Fatal(err)
}
@ -2136,7 +2194,7 @@ func TestSwitchSendPayment(t *testing.T) {
},
}
if err := forwardPackets(t, s, packet); err != nil {
if err := s.ForwardPackets(nil, packet); err != nil {
t.Fatalf("can't forward htlc packet: %v", err)
}
@ -2631,7 +2689,7 @@ func TestInvalidFailure(t *testing.T) {
},
}
if err := forwardPackets(t, s, packet); err != nil {
if err := s.ForwardPackets(nil, packet); err != nil {
t.Fatalf("can't forward htlc packet: %v", err)
}
@ -3058,16 +3116,185 @@ func getThreeHopEvents(channels *clusterChannels, htlcID uint64,
return aliceEvents, bobEvents, carolEvents
}
// forwardPackets forwards packets to the switch and enforces a timeout on the
// reply.
func forwardPackets(t *testing.T, s *Switch, packets ...*htlcPacket) error {
type mockForwardInterceptor struct {
intercepted InterceptedForward
}
select {
case err := <-s.ForwardPackets(nil, packets...):
return err
func (m *mockForwardInterceptor) InterceptForwardHtlc(intercepted InterceptedForward) bool {
case <-time.After(time.Second):
t.Fatal("no timely reply from switch")
return nil
m.intercepted = intercepted
return true
}
func (m *mockForwardInterceptor) settle(preimage lntypes.Preimage) error {
return m.intercepted.Settle(preimage)
}
func (m *mockForwardInterceptor) fail() error {
return m.intercepted.Fail()
}
func (m *mockForwardInterceptor) resume() error {
return m.intercepted.Resume()
}
func assertNumCircuits(t *testing.T, s *Switch, pending, opened int) {
if s.circuits.NumPending() != pending {
t.Fatal("wrong amount of half circuits")
}
if s.circuits.NumOpen() != opened {
t.Fatal("wrong amount of circuits")
}
}
func assertOutgoingLinkReceive(t *testing.T, targetLink *mockChannelLink,
expectReceive bool) {
// Pull packet from targetLink link.
select {
case packet := <-targetLink.packets:
if !expectReceive {
t.Fatal("forward was intercepted, shouldn't land at bob link")
} else if err := targetLink.completeCircuit(packet); err != nil {
t.Fatalf("unable to complete payment circuit: %v", err)
}
case <-time.After(time.Second):
if expectReceive {
t.Fatal("request was not propagated to destination")
}
}
}
func TestSwitchHoldForward(t *testing.T) {
t.Parallel()
chanID1, chanID2, aliceChanID, bobChanID := genIDs()
alicePeer, err := newMockServer(
t, "alice", testStartingHeight, nil, testDefaultDelta,
)
if err != nil {
t.Fatalf("unable to create alice server: %v", err)
}
bobPeer, err := newMockServer(
t, "bob", testStartingHeight, nil, testDefaultDelta,
)
if err != nil {
t.Fatalf("unable to create bob server: %v", err)
}
tempPath, err := ioutil.TempDir("", "circuitdb")
if err != nil {
t.Fatalf("unable to temporary path: %v", err)
}
cdb, err := channeldb.Open(tempPath)
if err != nil {
t.Fatalf("unable to open channeldb: %v", err)
}
s, err := initSwitchWithDB(testStartingHeight, cdb)
if err != nil {
t.Fatalf("unable to init switch: %v", err)
}
if err := s.Start(); err != nil {
t.Fatalf("unable to start switch: %v", err)
}
defer func() {
if err := s.Stop(); err != nil {
t.Fatalf(err.Error())
}
}()
aliceChannelLink := newMockChannelLink(
s, chanID1, aliceChanID, alicePeer, true,
)
bobChannelLink := newMockChannelLink(
s, chanID2, bobChanID, bobPeer, true,
)
if err := s.AddLink(aliceChannelLink); err != nil {
t.Fatalf("unable to add alice link: %v", err)
}
if err := s.AddLink(bobChannelLink); err != nil {
t.Fatalf("unable to add bob link: %v", err)
}
// Create request which should be forwarded from Alice channel link to
// bob channel link.
preimage := [sha256.Size]byte{1}
rhash := fastsha256.Sum256(preimage[:])
ogPacket := &htlcPacket{
incomingChanID: aliceChannelLink.ShortChanID(),
incomingHTLCID: 0,
outgoingChanID: bobChannelLink.ShortChanID(),
obfuscator: NewMockObfuscator(),
htlc: &lnwire.UpdateAddHTLC{
PaymentHash: rhash,
Amount: 1,
},
}
forwardInterceptor := &mockForwardInterceptor{}
switchForwardInterceptor := NewInterceptableSwitch(s)
switchForwardInterceptor.SetInterceptor(forwardInterceptor.InterceptForwardHtlc)
linkQuit := make(chan struct{})
// Test resume a hold forward
assertNumCircuits(t, s, 0, 0)
if err := switchForwardInterceptor.ForwardPackets(linkQuit, ogPacket); err != nil {
t.Fatalf("can't forward htlc packet: %v", err)
}
assertNumCircuits(t, s, 0, 0)
assertOutgoingLinkReceive(t, bobChannelLink, false)
if err := forwardInterceptor.resume(); err != nil {
t.Fatalf("failed to resume forward")
}
assertOutgoingLinkReceive(t, bobChannelLink, true)
assertNumCircuits(t, s, 1, 1)
// settling the htlc to close the circuit.
settle := &htlcPacket{
outgoingChanID: bobChannelLink.ShortChanID(),
outgoingHTLCID: 0,
amount: 1,
htlc: &lnwire.UpdateFulfillHTLC{
PaymentPreimage: preimage,
},
}
if err := switchForwardInterceptor.ForwardPackets(linkQuit, settle); err != nil {
t.Fatalf("can't forward htlc packet: %v", err)
}
assertOutgoingLinkReceive(t, aliceChannelLink, true)
assertNumCircuits(t, s, 0, 0)
// Test failing a hold forward
if err := switchForwardInterceptor.ForwardPackets(linkQuit, ogPacket); err != nil {
t.Fatalf("can't forward htlc packet: %v", err)
}
assertNumCircuits(t, s, 0, 0)
assertOutgoingLinkReceive(t, bobChannelLink, false)
if err := forwardInterceptor.fail(); err != nil {
t.Fatalf("failed to cancel forward %v", err)
}
assertOutgoingLinkReceive(t, bobChannelLink, false)
assertOutgoingLinkReceive(t, aliceChannelLink, true)
assertNumCircuits(t, s, 0, 0)
// Test settling a hold forward
if err := switchForwardInterceptor.ForwardPackets(linkQuit, ogPacket); err != nil {
t.Fatalf("can't forward htlc packet: %v", err)
}
assertNumCircuits(t, s, 0, 0)
assertOutgoingLinkReceive(t, bobChannelLink, false)
if err := forwardInterceptor.settle(preimage); err != nil {
t.Fatal("failed to cancel forward")
}
assertOutgoingLinkReceive(t, bobChannelLink, false)
assertOutgoingLinkReceive(t, aliceChannelLink, true)
assertNumCircuits(t, s, 0, 0)
}

@ -211,6 +211,8 @@ http:
# deprecated, no REST endpoint
- selector: routerrpc.Router.TrackPayment
# deprecated, no REST endpoint
- selector: routerrpc.HtlcInterceptor
# request streaming RPC, REST not supported
# signrpc/signer.proto
- selector: signrpc.Signer.SignOutputRaw

@ -0,0 +1,217 @@
package routerrpc
import (
"errors"
"fmt"
"sync"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
)
var (
// ErrFwdNotExists is an error returned when the caller tries to resolve
// a forward that doesn't exist anymore.
ErrFwdNotExists = errors.New("forward does not exist")
// ErrMissingPreimage is an error returned when the caller tries to settle
// a forward and doesn't provide a preimage.
ErrMissingPreimage = errors.New("missing preimage")
)
// forwardInterceptor is a helper struct that handles the lifecycle of an rpc
// interceptor streaming session.
// It is created when the stream opens and disconnects when the stream closes.
type forwardInterceptor struct {
// server is the Server reference
server *Server
// holdForwards is a map of current hold forwards and their corresponding
// ForwardResolver.
holdForwards map[channeldb.CircuitKey]htlcswitch.InterceptedForward
// stream is the bidirectional RPC stream
stream Router_HtlcInterceptorServer
// quit is a channel that is closed when this forwardInterceptor is shutting
// down.
quit chan struct{}
// intercepted is where we stream all intercepted packets coming from
// the switch.
intercepted chan htlcswitch.InterceptedForward
wg sync.WaitGroup
}
// newForwardInterceptor creates a new forwardInterceptor.
func newForwardInterceptor(server *Server, stream Router_HtlcInterceptorServer) *forwardInterceptor {
return &forwardInterceptor{
server: server,
stream: stream,
holdForwards: make(
map[channeldb.CircuitKey]htlcswitch.InterceptedForward),
quit: make(chan struct{}),
intercepted: make(chan htlcswitch.InterceptedForward),
}
}
// run sends the intercepted packets to the client and receives the
// corersponding responses. On one hand it regsitered itself as an interceptor
// that receives the switch packets and on the other hand launches a go routine
// to read from the client stream.
// To coordinate all this and make sure it is safe for concurrent access all
// packets are sent to the main where they are handled.
func (r *forwardInterceptor) run() error {
// make sure we disconnect and resolves all remaining packets if any.
defer r.onDisconnect()
// Register our interceptor so we receive all forwarded packets.
interceptableForwarder := r.server.cfg.RouterBackend.InterceptableForwarder
interceptableForwarder.SetInterceptor(r.onIntercept)
defer interceptableForwarder.SetInterceptor(nil)
// start a go routine that reads client resolutions.
errChan := make(chan error)
resolutionRequests := make(chan *ForwardHtlcInterceptResponse)
r.wg.Add(1)
go r.readClientResponses(resolutionRequests, errChan)
// run the main loop that synchronizes both sides input into one go routine.
for {
select {
case intercepted := <-r.intercepted:
log.Tracef("sending intercepted packet to client %v", intercepted)
// in case we couldn't forward we exit the loop and drain the
// current interceptor as this indicates on a connection problem.
if err := r.holdAndForwardToClient(intercepted); err != nil {
return err
}
case resolution := <-resolutionRequests:
log.Tracef("resolving intercepted packet %v", resolution)
// in case we couldn't resolve we just add a log line since this
// does not indicate on any connection problem.
if err := r.resolveFromClient(resolution); err != nil {
log.Warnf("client resolution of intercepted "+
"packet failed %v", err)
}
case err := <-errChan:
return err
case <-r.server.quit:
return nil
}
}
}
// onIntercept is the function that is called by the switch for every forwarded
// packet. Our interceptor makes sure we hold the packet and then signal to the
// main loop to handle the packet. We only return true if we were able
// to deliver the packet to the main loop.
func (r *forwardInterceptor) onIntercept(p htlcswitch.InterceptedForward) bool {
select {
case r.intercepted <- p:
return true
case <-r.quit:
return false
case <-r.server.quit:
return false
}
}
func (r *forwardInterceptor) readClientResponses(
resolutionChan chan *ForwardHtlcInterceptResponse, errChan chan error) {
defer r.wg.Done()
for {
resp, err := r.stream.Recv()
if err != nil {
errChan <- err
return
}
// Now that we have the response from the RPC client, send it to
// the responses chan.
select {
case resolutionChan <- resp:
case <-r.quit:
return
case <-r.server.quit:
return
}
}
}
// holdAndForwardToClient forwards the intercepted htlc to the client.
func (r *forwardInterceptor) holdAndForwardToClient(
forward htlcswitch.InterceptedForward) error {
htlc := forward.Packet()
inKey := forward.CircuitKey()
// First hold the forward, then send to client.
r.holdForwards[forward.CircuitKey()] = forward
interceptionRequest := &ForwardHtlcInterceptRequest{
IncomingCircuitKey: &CircuitKey{
ChanId: inKey.ChanID.ToUint64(),
HtlcId: inKey.HtlcID,
},
HtlcPaymentHash: htlc.PaymentHash[:],
AmountMsat: uint64(htlc.Amount),
Expiry: htlc.Expiry,
}
return r.stream.Send(interceptionRequest)
}
// resolveFromClient handles a resolution arrived from the client.
func (r *forwardInterceptor) resolveFromClient(
in *ForwardHtlcInterceptResponse) error {
circuitKey := channeldb.CircuitKey{
ChanID: lnwire.NewShortChanIDFromInt(in.IncomingCircuitKey.ChanId),
HtlcID: in.IncomingCircuitKey.HtlcId,
}
var interceptedForward htlcswitch.InterceptedForward
interceptedForward, ok := r.holdForwards[circuitKey]
if !ok {
return ErrFwdNotExists
}
delete(r.holdForwards, circuitKey)
switch in.Action {
case ResolveHoldForwardAction_RESUME:
return interceptedForward.Resume()
case ResolveHoldForwardAction_FAIL:
return interceptedForward.Fail()
case ResolveHoldForwardAction_SETTLE:
if in.Preimage == nil {
return ErrMissingPreimage
}
preimage, err := lntypes.MakePreimage(in.Preimage)
if err != nil {
return err
}
return interceptedForward.Settle(preimage)
default:
return fmt.Errorf("unrecognized resolve action %v", in.Action)
}
}
// onDisconnect removes all previousely held forwards from
// the store. Before they are removed it ensure to resume as the default
// behavior.
func (r *forwardInterceptor) onDisconnect() {
// Then close the channel so all go routine will exit.
close(r.quit)
log.Infof("RPC interceptor disconnected, resolving held packets")
for key, forward := range r.holdForwards {
if err := forward.Resume(); err != nil {
log.Errorf("failed to resume hold forward %v", err)
}
delete(r.holdForwards, key)
}
r.wg.Wait()
}

@ -169,6 +169,34 @@ func (PaymentState) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_7a0613f69d37b0a5, []int{1}
}
type ResolveHoldForwardAction int32
const (
ResolveHoldForwardAction_SETTLE ResolveHoldForwardAction = 0
ResolveHoldForwardAction_FAIL ResolveHoldForwardAction = 1
ResolveHoldForwardAction_RESUME ResolveHoldForwardAction = 2
)
var ResolveHoldForwardAction_name = map[int32]string{
0: "SETTLE",
1: "FAIL",
2: "RESUME",
}
var ResolveHoldForwardAction_value = map[string]int32{
"SETTLE": 0,
"FAIL": 1,
"RESUME": 2,
}
func (x ResolveHoldForwardAction) String() string {
return proto.EnumName(ResolveHoldForwardAction_name, int32(x))
}
func (ResolveHoldForwardAction) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_7a0613f69d37b0a5, []int{2}
}
type HtlcEvent_EventType int32
const (
@ -1727,9 +1755,196 @@ func (m *PaymentStatus) GetHtlcs() []*lnrpc.HTLCAttempt {
return nil
}
type CircuitKey struct {
/// The id of the channel that the is part of this circuit.
ChanId uint64 `protobuf:"varint,1,opt,name=chan_id,json=chanId,proto3" json:"chan_id,omitempty"`
/// The index of the incoming htlc in the incoming channel.
HtlcId uint64 `protobuf:"varint,2,opt,name=htlc_id,json=htlcId,proto3" json:"htlc_id,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *CircuitKey) Reset() { *m = CircuitKey{} }
func (m *CircuitKey) String() string { return proto.CompactTextString(m) }
func (*CircuitKey) ProtoMessage() {}
func (*CircuitKey) Descriptor() ([]byte, []int) {
return fileDescriptor_7a0613f69d37b0a5, []int{24}
}
func (m *CircuitKey) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CircuitKey.Unmarshal(m, b)
}
func (m *CircuitKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_CircuitKey.Marshal(b, m, deterministic)
}
func (m *CircuitKey) XXX_Merge(src proto.Message) {
xxx_messageInfo_CircuitKey.Merge(m, src)
}
func (m *CircuitKey) XXX_Size() int {
return xxx_messageInfo_CircuitKey.Size(m)
}
func (m *CircuitKey) XXX_DiscardUnknown() {
xxx_messageInfo_CircuitKey.DiscardUnknown(m)
}
var xxx_messageInfo_CircuitKey proto.InternalMessageInfo
func (m *CircuitKey) GetChanId() uint64 {
if m != nil {
return m.ChanId
}
return 0
}
func (m *CircuitKey) GetHtlcId() uint64 {
if m != nil {
return m.HtlcId
}
return 0
}
type ForwardHtlcInterceptRequest struct {
//
//The key of this forwarded htlc. It defines the incoming channel id and
//the index in this channel.
IncomingCircuitKey *CircuitKey `protobuf:"bytes,1,opt,name=incoming_circuit_key,json=incomingCircuitKey,proto3" json:"incoming_circuit_key,omitempty"`
//
//The htlc payment hash. This value is not guaranteed to be unique per
//request.
HtlcPaymentHash []byte `protobuf:"bytes,2,opt,name=htlc_payment_hash,json=htlcPaymentHash,proto3" json:"htlc_payment_hash,omitempty"`
/// The htlc amount.
AmountMsat uint64 `protobuf:"varint,3,opt,name=amount_msat,json=amountMsat,proto3" json:"amount_msat,omitempty"`
/// The htlc expiry.
Expiry uint32 `protobuf:"varint,4,opt,name=expiry,proto3" json:"expiry,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ForwardHtlcInterceptRequest) Reset() { *m = ForwardHtlcInterceptRequest{} }
func (m *ForwardHtlcInterceptRequest) String() string { return proto.CompactTextString(m) }
func (*ForwardHtlcInterceptRequest) ProtoMessage() {}
func (*ForwardHtlcInterceptRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_7a0613f69d37b0a5, []int{25}
}
func (m *ForwardHtlcInterceptRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ForwardHtlcInterceptRequest.Unmarshal(m, b)
}
func (m *ForwardHtlcInterceptRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ForwardHtlcInterceptRequest.Marshal(b, m, deterministic)
}
func (m *ForwardHtlcInterceptRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_ForwardHtlcInterceptRequest.Merge(m, src)
}
func (m *ForwardHtlcInterceptRequest) XXX_Size() int {
return xxx_messageInfo_ForwardHtlcInterceptRequest.Size(m)
}
func (m *ForwardHtlcInterceptRequest) XXX_DiscardUnknown() {
xxx_messageInfo_ForwardHtlcInterceptRequest.DiscardUnknown(m)
}
var xxx_messageInfo_ForwardHtlcInterceptRequest proto.InternalMessageInfo
func (m *ForwardHtlcInterceptRequest) GetIncomingCircuitKey() *CircuitKey {
if m != nil {
return m.IncomingCircuitKey
}
return nil
}
func (m *ForwardHtlcInterceptRequest) GetHtlcPaymentHash() []byte {
if m != nil {
return m.HtlcPaymentHash
}
return nil
}
func (m *ForwardHtlcInterceptRequest) GetAmountMsat() uint64 {
if m != nil {
return m.AmountMsat
}
return 0
}
func (m *ForwardHtlcInterceptRequest) GetExpiry() uint32 {
if m != nil {
return m.Expiry
}
return 0
}
//*
//ForwardHtlcInterceptResponse enables the caller to resolve a previously hold forward.
//The caller can choose either to:
//- `Resume`: Execute the default behavior (usually forward).
//- `Reject`: Fail the htlc backwards.
//- `Settle`: Settle this htlc with a given preimage.
type ForwardHtlcInterceptResponse struct {
//*
//The key of this forwarded htlc. It defines the incoming channel id and
//the index in this channel.
IncomingCircuitKey *CircuitKey `protobuf:"bytes,1,opt,name=incoming_circuit_key,json=incomingCircuitKey,proto3" json:"incoming_circuit_key,omitempty"`
// The resolve action for this intercepted htlc.
Action ResolveHoldForwardAction `protobuf:"varint,2,opt,name=action,proto3,enum=routerrpc.ResolveHoldForwardAction" json:"action,omitempty"`
// The preimage in case the resolve action is Settle.
Preimage []byte `protobuf:"bytes,3,opt,name=preimage,proto3" json:"preimage,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ForwardHtlcInterceptResponse) Reset() { *m = ForwardHtlcInterceptResponse{} }
func (m *ForwardHtlcInterceptResponse) String() string { return proto.CompactTextString(m) }
func (*ForwardHtlcInterceptResponse) ProtoMessage() {}
func (*ForwardHtlcInterceptResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_7a0613f69d37b0a5, []int{26}
}
func (m *ForwardHtlcInterceptResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ForwardHtlcInterceptResponse.Unmarshal(m, b)
}
func (m *ForwardHtlcInterceptResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ForwardHtlcInterceptResponse.Marshal(b, m, deterministic)
}
func (m *ForwardHtlcInterceptResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_ForwardHtlcInterceptResponse.Merge(m, src)
}
func (m *ForwardHtlcInterceptResponse) XXX_Size() int {
return xxx_messageInfo_ForwardHtlcInterceptResponse.Size(m)
}
func (m *ForwardHtlcInterceptResponse) XXX_DiscardUnknown() {
xxx_messageInfo_ForwardHtlcInterceptResponse.DiscardUnknown(m)
}
var xxx_messageInfo_ForwardHtlcInterceptResponse proto.InternalMessageInfo
func (m *ForwardHtlcInterceptResponse) GetIncomingCircuitKey() *CircuitKey {
if m != nil {
return m.IncomingCircuitKey
}
return nil
}
func (m *ForwardHtlcInterceptResponse) GetAction() ResolveHoldForwardAction {
if m != nil {
return m.Action
}
return ResolveHoldForwardAction_SETTLE
}
func (m *ForwardHtlcInterceptResponse) GetPreimage() []byte {
if m != nil {
return m.Preimage
}
return nil
}
func init() {
proto.RegisterEnum("routerrpc.FailureDetail", FailureDetail_name, FailureDetail_value)
proto.RegisterEnum("routerrpc.PaymentState", PaymentState_name, PaymentState_value)
proto.RegisterEnum("routerrpc.ResolveHoldForwardAction", ResolveHoldForwardAction_name, ResolveHoldForwardAction_value)
proto.RegisterEnum("routerrpc.HtlcEvent_EventType", HtlcEvent_EventType_name, HtlcEvent_EventType_value)
proto.RegisterType((*SendPaymentRequest)(nil), "routerrpc.SendPaymentRequest")
proto.RegisterMapType((map[uint64][]byte)(nil), "routerrpc.SendPaymentRequest.DestCustomRecordsEntry")
@ -1756,155 +1971,172 @@ func init() {
proto.RegisterType((*SettleEvent)(nil), "routerrpc.SettleEvent")
proto.RegisterType((*LinkFailEvent)(nil), "routerrpc.LinkFailEvent")
proto.RegisterType((*PaymentStatus)(nil), "routerrpc.PaymentStatus")
proto.RegisterType((*CircuitKey)(nil), "routerrpc.CircuitKey")
proto.RegisterType((*ForwardHtlcInterceptRequest)(nil), "routerrpc.ForwardHtlcInterceptRequest")
proto.RegisterType((*ForwardHtlcInterceptResponse)(nil), "routerrpc.ForwardHtlcInterceptResponse")
}
func init() { proto.RegisterFile("routerrpc/router.proto", fileDescriptor_7a0613f69d37b0a5) }
var fileDescriptor_7a0613f69d37b0a5 = []byte{
// 2283 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x58, 0x5d, 0x73, 0xdb, 0xc6,
0xd5, 0x0e, 0x48, 0x50, 0x22, 0x0f, 0x3f, 0x04, 0x2d, 0x1d, 0x19, 0x2f, 0x15, 0x27, 0x0c, 0xde,
0xc4, 0xe1, 0xb8, 0x89, 0xe4, 0xa8, 0x9d, 0x36, 0xd3, 0x36, 0x69, 0x29, 0x12, 0x8a, 0x60, 0x53,
0x00, 0xb3, 0xa4, 0x64, 0xbb, 0xbe, 0xd8, 0x81, 0xc8, 0xa5, 0x88, 0x0a, 0x04, 0x58, 0x60, 0x69,
0x47, 0x97, 0xbd, 0xeb, 0xf4, 0x8f, 0xf4, 0xae, 0xbf, 0xa0, 0x3f, 0xa5, 0x33, 0xbd, 0xed, 0x7d,
0x67, 0x7a, 0xdd, 0xd9, 0xc5, 0x82, 0x02, 0x29, 0xca, 0x6e, 0x6f, 0x6c, 0xe2, 0x39, 0xcf, 0x9e,
0x3d, 0x67, 0xcf, 0xd7, 0xae, 0x60, 0x2f, 0x0a, 0x17, 0x8c, 0x46, 0xd1, 0x7c, 0x74, 0x98, 0xfc,
0x3a, 0x98, 0x47, 0x21, 0x0b, 0x51, 0x69, 0x89, 0x37, 0x4a, 0xd1, 0x7c, 0x94, 0xa0, 0xc6, 0xbf,
0xb6, 0x00, 0x0d, 0x68, 0x30, 0xee, 0xbb, 0x37, 0x33, 0x1a, 0x30, 0x4c, 0xff, 0xb0, 0xa0, 0x31,
0x43, 0x08, 0xd4, 0x31, 0x8d, 0x99, 0xae, 0x34, 0x95, 0x56, 0x05, 0x8b, 0xdf, 0x48, 0x83, 0xbc,
0x3b, 0x63, 0x7a, 0xae, 0xa9, 0xb4, 0xf2, 0x98, 0xff, 0x44, 0xff, 0x07, 0x45, 0x77, 0xc6, 0xc8,
0x2c, 0x76, 0x99, 0x5e, 0x11, 0xf0, 0xb6, 0x3b, 0x63, 0x67, 0xb1, 0xcb, 0xd0, 0xa7, 0x50, 0x99,
0x27, 0x2a, 0xc9, 0xd4, 0x8d, 0xa7, 0x7a, 0x5e, 0x28, 0x2a, 0x4b, 0xec, 0xd4, 0x8d, 0xa7, 0xa8,
0x05, 0xda, 0xc4, 0x0b, 0x5c, 0x9f, 0x8c, 0x7c, 0xf6, 0x86, 0x8c, 0xa9, 0xcf, 0x5c, 0x5d, 0x6d,
0x2a, 0xad, 0x02, 0xae, 0x09, 0xbc, 0xe3, 0xb3, 0x37, 0x5d, 0x8e, 0xa2, 0x2f, 0x60, 0x27, 0x55,
0x16, 0x25, 0x06, 0xea, 0x85, 0xa6, 0xd2, 0x2a, 0xe1, 0xda, 0x7c, 0xd5, 0xec, 0x2f, 0x60, 0x87,
0x79, 0x33, 0x1a, 0x2e, 0x18, 0x89, 0xe9, 0x28, 0x0c, 0xc6, 0xb1, 0xbe, 0x95, 0x68, 0x94, 0xf0,
0x20, 0x41, 0x91, 0x01, 0xd5, 0x09, 0xa5, 0xc4, 0xf7, 0x66, 0x1e, 0x23, 0xdc, 0xfc, 0x6d, 0x61,
0x7e, 0x79, 0x42, 0x69, 0x8f, 0x63, 0x03, 0x97, 0xa1, 0xcf, 0xa0, 0x76, 0xcb, 0x11, 0x3e, 0x56,
0x05, 0xa9, 0x92, 0x92, 0x84, 0xa3, 0x07, 0xa0, 0x85, 0x0b, 0x76, 0x15, 0x7a, 0xc1, 0x15, 0x19,
0x4d, 0xdd, 0x80, 0x78, 0x63, 0xbd, 0xd8, 0x54, 0x5a, 0xea, 0xb1, 0xaa, 0x2b, 0x4f, 0x15, 0x5c,
0x4b, 0xa5, 0x9d, 0xa9, 0x1b, 0x58, 0x63, 0xf4, 0x04, 0x76, 0xd7, 0xf9, 0xb1, 0x5e, 0x6f, 0xe6,
0x5b, 0x2a, 0xde, 0x59, 0xa5, 0xc6, 0xe8, 0x31, 0xec, 0xf8, 0x6e, 0xcc, 0xc8, 0x34, 0x9c, 0x93,
0xf9, 0xe2, 0xf2, 0x9a, 0xde, 0xe8, 0x35, 0x71, 0x8e, 0x55, 0x0e, 0x9f, 0x86, 0xf3, 0xbe, 0x00,
0xd1, 0x23, 0x00, 0x71, 0x86, 0xc2, 0x54, 0xbd, 0x24, 0x3c, 0x2e, 0x71, 0x44, 0x98, 0x89, 0xbe,
0x86, 0xb2, 0x88, 0x3d, 0x99, 0x7a, 0x01, 0x8b, 0x75, 0x68, 0xe6, 0x5b, 0xe5, 0x23, 0xed, 0xc0,
0x0f, 0x78, 0x1a, 0x60, 0x2e, 0x39, 0xf5, 0x02, 0x86, 0x21, 0x4a, 0x7f, 0xc6, 0x68, 0x0c, 0x75,
0x1e, 0x73, 0x32, 0x5a, 0xc4, 0x2c, 0x9c, 0x91, 0x88, 0x8e, 0xc2, 0x68, 0x1c, 0xeb, 0x65, 0xb1,
0xf4, 0x67, 0x07, 0xcb, 0x54, 0x3a, 0xb8, 0x9b, 0x3b, 0x07, 0x5d, 0x1a, 0xb3, 0x8e, 0x58, 0x87,
0x93, 0x65, 0x66, 0xc0, 0xa2, 0x1b, 0xbc, 0x3b, 0x5e, 0xc7, 0xd1, 0x97, 0x80, 0x5c, 0xdf, 0x0f,
0xdf, 0x92, 0x98, 0xfa, 0x13, 0x22, 0x63, 0xa9, 0xef, 0x34, 0x95, 0x56, 0x11, 0x6b, 0x42, 0x32,
0xa0, 0xfe, 0x44, 0xaa, 0x47, 0x3f, 0x87, 0xaa, 0xb0, 0x69, 0x42, 0x5d, 0xb6, 0x88, 0x68, 0xac,
0x6b, 0xcd, 0x7c, 0xab, 0x76, 0xb4, 0x2b, 0x1d, 0x39, 0x49, 0xe0, 0x63, 0x8f, 0xe1, 0x0a, 0xe7,
0xc9, 0xef, 0x18, 0xed, 0x43, 0x69, 0xe6, 0xfe, 0x48, 0xe6, 0x6e, 0xc4, 0x62, 0x7d, 0xb7, 0xa9,
0xb4, 0xaa, 0xb8, 0x38, 0x73, 0x7f, 0xec, 0xf3, 0x6f, 0x74, 0x00, 0xf5, 0x20, 0x24, 0x5e, 0x30,
0xf1, 0xbd, 0xab, 0x29, 0x23, 0x8b, 0xf9, 0xd8, 0x65, 0x34, 0xd6, 0x91, 0xb0, 0x61, 0x37, 0x08,
0x2d, 0x29, 0x39, 0x4f, 0x04, 0x8d, 0x2e, 0xec, 0x6d, 0xf6, 0x8f, 0x97, 0x07, 0x0f, 0x10, 0xaf,
0x18, 0x15, 0xf3, 0x9f, 0xe8, 0x01, 0x14, 0xde, 0xb8, 0xfe, 0x82, 0x8a, 0x92, 0xa9, 0xe0, 0xe4,
0xe3, 0x97, 0xb9, 0x6f, 0x14, 0x63, 0x0a, 0xf5, 0x61, 0xe4, 0x8e, 0xae, 0xd7, 0xaa, 0x6e, 0xbd,
0x68, 0x94, 0xbb, 0x45, 0x73, 0x8f, 0xbd, 0xb9, 0x7b, 0xec, 0x35, 0xbe, 0x83, 0x1d, 0x11, 0xe1,
0x13, 0x4a, 0xdf, 0x55, 0xdb, 0x0f, 0x81, 0x57, 0xae, 0xa8, 0x84, 0xa4, 0xbe, 0xb7, 0xdc, 0x19,
0x2f, 0x02, 0x63, 0x0c, 0xda, 0xed, 0xfa, 0x78, 0x1e, 0x06, 0x31, 0xe5, 0x85, 0xcb, 0x13, 0x80,
0x67, 0x30, 0x2f, 0x10, 0x51, 0x1a, 0x8a, 0x58, 0x55, 0x93, 0xf8, 0x09, 0xa5, 0xa2, 0x38, 0x1e,
0x27, 0xf5, 0x48, 0xfc, 0x70, 0x74, 0xcd, 0x2b, 0xdc, 0xbd, 0x91, 0xea, 0xab, 0x1c, 0xee, 0x85,
0xa3, 0xeb, 0x2e, 0x07, 0x8d, 0xd7, 0x49, 0x13, 0x1a, 0x86, 0x62, 0xaf, 0xff, 0xe1, 0x38, 0x0c,
0x28, 0x88, 0x5c, 0x14, 0x6a, 0xcb, 0x47, 0x95, 0x6c, 0x52, 0xe3, 0x44, 0x64, 0xbc, 0x86, 0xfa,
0x8a, 0x72, 0xe9, 0x45, 0x03, 0x8a, 0xf3, 0x88, 0x7a, 0x33, 0xf7, 0x8a, 0x4a, 0xcd, 0xcb, 0x6f,
0xd4, 0x82, 0xed, 0x89, 0xeb, 0xf9, 0x8b, 0x28, 0x55, 0x5c, 0x4b, 0x93, 0x2c, 0x41, 0x71, 0x2a,
0x36, 0x3e, 0x82, 0x06, 0xa6, 0x31, 0x65, 0x67, 0x5e, 0x1c, 0x7b, 0x61, 0xd0, 0x09, 0x03, 0x16,
0x85, 0xbe, 0xf4, 0xc0, 0x78, 0x04, 0xfb, 0x1b, 0xa5, 0x89, 0x09, 0x7c, 0xf1, 0x0f, 0x0b, 0x1a,
0xdd, 0x6c, 0x5e, 0xfc, 0x03, 0xec, 0x6f, 0x94, 0x4a, 0xfb, 0xbf, 0x84, 0xc2, 0xdc, 0xf5, 0x22,
0x1e, 0x7b, 0x5e, 0x94, 0x7b, 0x99, 0xa2, 0xec, 0xbb, 0x5e, 0x74, 0xea, 0xc5, 0x2c, 0x8c, 0x6e,
0x70, 0x42, 0x7a, 0xa6, 0x16, 0x15, 0x2d, 0x67, 0xfc, 0x59, 0x81, 0x72, 0x46, 0xc8, 0x4b, 0x23,
0x08, 0xc7, 0x94, 0x4c, 0xa2, 0x70, 0x96, 0x1e, 0x02, 0x07, 0x4e, 0xa2, 0x70, 0xc6, 0x73, 0x42,
0x08, 0x59, 0x28, 0x13, 0x78, 0x8b, 0x7f, 0x0e, 0x43, 0xf4, 0x15, 0x6c, 0x4f, 0x13, 0x05, 0xa2,
0x6d, 0x96, 0x8f, 0xea, 0x6b, 0x7b, 0x77, 0x5d, 0xe6, 0xe2, 0x94, 0xf3, 0x4c, 0x2d, 0xe6, 0x35,
0xf5, 0x99, 0x5a, 0x54, 0xb5, 0xc2, 0x33, 0xb5, 0x58, 0xd0, 0xb6, 0x9e, 0xa9, 0xc5, 0x2d, 0x6d,
0xdb, 0xf8, 0xa7, 0x02, 0xc5, 0x94, 0xcd, 0x2d, 0xe1, 0x47, 0x4a, 0x78, 0x5e, 0xc8, 0x64, 0x2a,
0x72, 0x60, 0xe8, 0xcd, 0x28, 0x6a, 0x42, 0x45, 0x08, 0x57, 0x53, 0x14, 0x38, 0xd6, 0x16, 0x69,
0x2a, 0xfa, 0x79, 0xca, 0x10, 0xf9, 0xa8, 0xca, 0x7e, 0x9e, 0x50, 0xd2, 0x91, 0x14, 0x2f, 0x46,
0x23, 0x1a, 0xc7, 0xc9, 0x2e, 0x85, 0x84, 0x22, 0x31, 0xb1, 0xd1, 0x63, 0xd8, 0x49, 0x29, 0xe9,
0x5e, 0x5b, 0x49, 0xbe, 0x4a, 0x58, 0x6e, 0xd7, 0x02, 0x2d, 0xcb, 0x9b, 0xdd, 0x4e, 0x90, 0xda,
0x2d, 0x91, 0x6f, 0x9a, 0x38, 0x6f, 0xfc, 0x1e, 0x1e, 0x8a, 0x50, 0xf6, 0xa3, 0xf0, 0xd2, 0xbd,
0xf4, 0x7c, 0x8f, 0xdd, 0xa4, 0x49, 0xce, 0x1d, 0x8f, 0xc2, 0x19, 0xe1, 0x67, 0x9b, 0x86, 0x80,
0x03, 0x76, 0x38, 0xa6, 0x3c, 0x04, 0x2c, 0x4c, 0x44, 0x32, 0x04, 0x2c, 0x14, 0x82, 0xec, 0xe4,
0xcd, 0xaf, 0x4c, 0x5e, 0xe3, 0x1a, 0xf4, 0xbb, 0x7b, 0xc9, 0x9c, 0x69, 0x42, 0x79, 0x7e, 0x0b,
0x8b, 0xed, 0x14, 0x9c, 0x85, 0xb2, 0xb1, 0xcd, 0xbd, 0x3f, 0xb6, 0xc6, 0x5f, 0x14, 0xd8, 0x3d,
0x5e, 0x78, 0xfe, 0x78, 0xa5, 0x70, 0xb3, 0xd6, 0x29, 0xab, 0xf7, 0x82, 0x4d, 0x43, 0x3f, 0xb7,
0x71, 0xe8, 0x7f, 0xb9, 0x61, 0xb0, 0xe6, 0xc5, 0x60, 0xcd, 0x6d, 0x18, 0xab, 0x9f, 0x40, 0xf9,
0x76, 0x4a, 0xc6, 0xba, 0xda, 0xcc, 0xb7, 0x2a, 0x18, 0xa6, 0xe9, 0x88, 0x8c, 0x8d, 0x6f, 0x00,
0x65, 0x0d, 0x95, 0x07, 0xb2, 0xec, 0x1f, 0xca, 0xfd, 0xfd, 0xe3, 0x23, 0x68, 0x0c, 0x16, 0x97,
0xf1, 0x28, 0xf2, 0x2e, 0xe9, 0x29, 0xf3, 0x47, 0xe6, 0x1b, 0x1a, 0xb0, 0x38, 0xad, 0xd2, 0x7f,
0xab, 0x50, 0x5a, 0xa2, 0xbc, 0x3d, 0x7b, 0xc1, 0x28, 0x9c, 0xa5, 0x46, 0x07, 0xd4, 0xe7, 0x76,
0x27, 0x43, 0x61, 0x37, 0x15, 0x75, 0x12, 0x89, 0x35, 0xe6, 0xfc, 0x15, 0x27, 0x25, 0x3f, 0x97,
0xf0, 0xb3, 0x3e, 0x26, 0xfc, 0x16, 0x68, 0x4b, 0xfd, 0x53, 0xe6, 0x8f, 0x96, 0x87, 0x82, 0x6b,
0x29, 0xce, 0x8d, 0x49, 0x98, 0x4b, 0xcd, 0x29, 0x53, 0x4d, 0x98, 0x29, 0x2e, 0x99, 0x9f, 0x42,
0x85, 0xd7, 0x43, 0xcc, 0xdc, 0xd9, 0x9c, 0x04, 0xb1, 0xa8, 0x0b, 0x15, 0x97, 0x97, 0x98, 0x1d,
0xa3, 0x6f, 0x01, 0x28, 0xf7, 0x8f, 0xb0, 0x9b, 0x39, 0x15, 0x25, 0x51, 0x3b, 0xfa, 0x38, 0x93,
0x18, 0xcb, 0x03, 0x38, 0x10, 0xff, 0x0e, 0x6f, 0xe6, 0x14, 0x97, 0x68, 0xfa, 0x13, 0x7d, 0x07,
0xd5, 0x49, 0x18, 0xbd, 0x75, 0xa3, 0x31, 0x11, 0xa0, 0x6c, 0x1b, 0x0f, 0x33, 0x1a, 0x4e, 0x12,
0xb9, 0x58, 0x7e, 0xfa, 0x01, 0xae, 0x4c, 0x32, 0xdf, 0xe8, 0x39, 0xa0, 0x74, 0xbd, 0xa8, 0xf2,
0x44, 0x49, 0x51, 0x28, 0xd9, 0xbf, 0xab, 0x84, 0x37, 0xe9, 0x54, 0x91, 0x36, 0x59, 0xc3, 0xd0,
0xaf, 0xa0, 0x12, 0x53, 0xc6, 0x7c, 0x2a, 0xd5, 0x94, 0x84, 0x9a, 0xbd, 0x95, 0x3b, 0x0d, 0x17,
0xa7, 0x1a, 0xca, 0xf1, 0xed, 0x27, 0x3a, 0x86, 0x1d, 0xdf, 0x0b, 0xae, 0xb3, 0x66, 0x80, 0x58,
0xaf, 0x67, 0xd6, 0xf7, 0xbc, 0xe0, 0x3a, 0x6b, 0x43, 0xd5, 0xcf, 0x02, 0xc6, 0xaf, 0xa1, 0xb4,
0x3c, 0x25, 0x54, 0x86, 0xed, 0x73, 0xfb, 0xb9, 0xed, 0xbc, 0xb0, 0xb5, 0x0f, 0x50, 0x11, 0xd4,
0x81, 0x69, 0x77, 0x35, 0x85, 0xc3, 0xd8, 0xec, 0x98, 0xd6, 0x85, 0xa9, 0xe5, 0xf8, 0xc7, 0x89,
0x83, 0x5f, 0xb4, 0x71, 0x57, 0xcb, 0x1f, 0x6f, 0x43, 0x41, 0xec, 0x6b, 0xfc, 0x4d, 0x81, 0xa2,
0x88, 0x60, 0x30, 0x09, 0xd1, 0x4f, 0x60, 0x99, 0x5c, 0xa2, 0xb9, 0xf1, 0x81, 0x2b, 0xb2, 0xae,
0x8a, 0x97, 0x09, 0x33, 0x94, 0x38, 0x27, 0x2f, 0x53, 0x63, 0x49, 0xce, 0x25, 0xe4, 0x54, 0xb0,
0x24, 0x3f, 0xc9, 0x68, 0x5e, 0x69, 0x39, 0x2a, 0xde, 0x49, 0x05, 0x69, 0x87, 0xcd, 0xde, 0x6d,
0x57, 0x3a, 0x71, 0xe6, 0x6e, 0x2b, 0xb9, 0xc6, 0x2f, 0xa0, 0x92, 0x8d, 0x39, 0xfa, 0x02, 0x54,
0x2f, 0x98, 0x84, 0xb2, 0x10, 0xeb, 0x6b, 0xc9, 0xc5, 0x9d, 0xc4, 0x82, 0x60, 0x20, 0xd0, 0xd6,
0xe3, 0x6c, 0x54, 0xa1, 0x9c, 0x09, 0x9a, 0xf1, 0x0f, 0x05, 0xaa, 0x2b, 0x41, 0xf8, 0xaf, 0xb5,
0xa3, 0x6f, 0xa1, 0xf2, 0xd6, 0x8b, 0x28, 0xc9, 0x8e, 0xff, 0xda, 0x51, 0x63, 0x75, 0xfc, 0xa7,
0xff, 0x77, 0xc2, 0x31, 0xc5, 0x65, 0xce, 0x97, 0x00, 0xfa, 0x0d, 0xd4, 0xe4, 0x4a, 0x32, 0xa6,
0xcc, 0xf5, 0x7c, 0x71, 0x54, 0xb5, 0x95, 0xf4, 0x90, 0xdc, 0xae, 0x90, 0xe3, 0xea, 0x24, 0xfb,
0x89, 0x3e, 0xbf, 0x55, 0x10, 0xb3, 0xc8, 0x0b, 0xae, 0xc4, 0xf9, 0x95, 0x96, 0xb4, 0x81, 0x00,
0xf9, 0x20, 0xaf, 0xca, 0xcb, 0xe3, 0x80, 0xb9, 0x6c, 0x11, 0xa3, 0xaf, 0xa0, 0x10, 0x33, 0x57,
0x76, 0xb2, 0xda, 0x4a, 0x6d, 0x65, 0x88, 0x14, 0x27, 0xac, 0x95, 0xdb, 0x4f, 0xee, 0xce, 0xed,
0xa7, 0xc0, 0x3b, 0x46, 0xd2, 0x45, 0xcb, 0x47, 0x48, 0x3a, 0x7f, 0x3a, 0xec, 0x75, 0xda, 0x8c,
0xd1, 0xd9, 0x9c, 0xe1, 0x84, 0x90, 0x4c, 0xb7, 0x27, 0x7f, 0x54, 0xa1, 0xba, 0xe2, 0xd4, 0x6a,
0x56, 0x57, 0xa1, 0x64, 0x3b, 0xa4, 0x6b, 0x0e, 0xdb, 0x56, 0x4f, 0x53, 0x90, 0x06, 0x15, 0xc7,
0xb6, 0x1c, 0x9b, 0x74, 0xcd, 0x8e, 0xd3, 0xe5, 0xf9, 0xfd, 0x21, 0xec, 0xf6, 0x2c, 0xfb, 0x39,
0xb1, 0x9d, 0x21, 0x31, 0x7b, 0xd6, 0xf7, 0xd6, 0x71, 0xcf, 0xd4, 0xf2, 0xe8, 0x01, 0x68, 0x8e,
0x4d, 0x3a, 0xa7, 0x6d, 0xcb, 0x26, 0x43, 0xeb, 0xcc, 0x74, 0xce, 0x87, 0x9a, 0xca, 0x51, 0x6e,
0x08, 0x31, 0x5f, 0x76, 0x4c, 0xb3, 0x3b, 0x20, 0x67, 0xed, 0x97, 0x5a, 0x01, 0xe9, 0xf0, 0xc0,
0xb2, 0x07, 0xe7, 0x27, 0x27, 0x56, 0xc7, 0x32, 0xed, 0x21, 0x39, 0x6e, 0xf7, 0xda, 0x76, 0xc7,
0xd4, 0xb6, 0xd0, 0x1e, 0x20, 0xcb, 0xee, 0x38, 0x67, 0xfd, 0x9e, 0x39, 0x34, 0x49, 0x5a, 0x47,
0xdb, 0xa8, 0x0e, 0x3b, 0x42, 0x4f, 0xbb, 0xdb, 0x25, 0x27, 0x6d, 0xab, 0x67, 0x76, 0xb5, 0x22,
0xb7, 0x44, 0x32, 0x06, 0xa4, 0x6b, 0x0d, 0xda, 0xc7, 0x1c, 0x2e, 0xf1, 0x3d, 0x2d, 0xfb, 0xc2,
0xb1, 0x3a, 0x26, 0xe9, 0x70, 0xb5, 0x1c, 0x05, 0x4e, 0x4e, 0xd1, 0x73, 0xbb, 0x6b, 0xe2, 0x7e,
0xdb, 0xea, 0x6a, 0x65, 0xb4, 0x0f, 0x0f, 0x53, 0xd8, 0x7c, 0xd9, 0xb7, 0xf0, 0x2b, 0x32, 0x74,
0x1c, 0x32, 0x70, 0x1c, 0x5b, 0xab, 0x64, 0x35, 0x71, 0x6f, 0x9d, 0xbe, 0x69, 0x6b, 0x55, 0xf4,
0x10, 0xea, 0x67, 0xfd, 0x3e, 0x49, 0x25, 0xa9, 0xb3, 0x35, 0x4e, 0x6f, 0x77, 0xbb, 0xd8, 0x1c,
0x0c, 0xc8, 0x99, 0x35, 0x38, 0x6b, 0x0f, 0x3b, 0xa7, 0xda, 0x0e, 0x77, 0x69, 0x60, 0x0e, 0xc9,
0xd0, 0x19, 0xb6, 0x7b, 0xb7, 0xb8, 0xc6, 0x0d, 0xba, 0xc5, 0xf9, 0xa6, 0x3d, 0xe7, 0x85, 0xb6,
0xcb, 0x0f, 0x9c, 0xc3, 0xce, 0x85, 0x34, 0x11, 0x71, 0xdf, 0x65, 0x78, 0xd2, 0x3d, 0xb5, 0x3a,
0x07, 0x2d, 0xfb, 0xa2, 0xdd, 0xb3, 0xba, 0xe4, 0xb9, 0xf9, 0x4a, 0xf4, 0xa1, 0x07, 0x1c, 0x4c,
0x2c, 0x23, 0x7d, 0xec, 0x7c, 0xcf, 0x0d, 0xd1, 0x3e, 0x44, 0x08, 0x6a, 0x1d, 0x0b, 0x77, 0xce,
0x7b, 0x6d, 0x4c, 0xb0, 0x73, 0x3e, 0x34, 0xb5, 0xbd, 0x27, 0x7f, 0x55, 0xa0, 0x92, 0xcd, 0x33,
0x1e, 0x75, 0xcb, 0x26, 0x27, 0x3d, 0xeb, 0xfb, 0xd3, 0x61, 0x92, 0x04, 0x83, 0xf3, 0x0e, 0x0f,
0x99, 0xc9, 0xfb, 0x1b, 0x82, 0x5a, 0x72, 0xe8, 0x4b, 0x67, 0x73, 0x7c, 0x2f, 0x89, 0xd9, 0x8e,
0xd4, 0x9b, 0xe7, 0xc6, 0x4b, 0xd0, 0xc4, 0xd8, 0xc1, 0x9a, 0x8a, 0x3e, 0x83, 0xa6, 0x44, 0x78,
0x5c, 0x31, 0x36, 0x3b, 0x43, 0xd2, 0x6f, 0xbf, 0x3a, 0xe3, 0x61, 0x4f, 0x92, 0x6c, 0xa0, 0x15,
0xd0, 0x27, 0xb0, 0xbf, 0x64, 0x6d, 0xca, 0x8b, 0xa3, 0xbf, 0x6f, 0xc3, 0x96, 0x18, 0xf3, 0x11,
0xfa, 0x2d, 0x54, 0x33, 0xcf, 0xd8, 0x8b, 0x23, 0xf4, 0xe8, 0x9d, 0x0f, 0xdc, 0x46, 0xfa, 0x18,
0x90, 0xf0, 0x53, 0x05, 0x1d, 0x43, 0x2d, 0xfb, 0x9e, 0xbb, 0x38, 0x42, 0xd9, 0xe9, 0xb8, 0xe1,
0xa9, 0xb7, 0x41, 0xc7, 0x73, 0xd0, 0xcc, 0x98, 0x79, 0x33, 0x5e, 0xa4, 0xf2, 0xc5, 0x85, 0x1a,
0x19, 0x2d, 0x6b, 0xcf, 0xb8, 0xc6, 0xfe, 0x46, 0x99, 0xbc, 0xd7, 0xfc, 0xc0, 0x1b, 0xe2, 0xf2,
0xcd, 0x73, 0xc7, 0xa1, 0xd5, 0x87, 0x56, 0xe3, 0xe3, 0xfb, 0xc4, 0xf2, 0x9d, 0x92, 0xff, 0x53,
0x8e, 0xfb, 0x58, 0xcd, 0xc8, 0x36, 0x9c, 0xd2, 0x9a, 0xd2, 0x0d, 0x6d, 0x03, 0x8d, 0xa1, 0xbe,
0xe1, 0x3d, 0x84, 0x3e, 0xcf, 0xba, 0x72, 0xef, 0x6b, 0xaa, 0xf1, 0xf8, 0x7d, 0x34, 0xe9, 0xfc,
0x18, 0xea, 0x1b, 0x1e, 0x4e, 0x2b, 0xbb, 0xdc, 0xff, 0xec, 0x5a, 0xd9, 0xe5, 0x5d, 0xef, 0xaf,
0xd7, 0xa0, 0xad, 0xdf, 0xb3, 0x91, 0xb1, 0xbe, 0xf6, 0xee, 0x85, 0xbf, 0xf1, 0xff, 0xef, 0xe4,
0x48, 0xe5, 0x16, 0xc0, 0xed, 0x6d, 0x15, 0x7d, 0x94, 0x59, 0x72, 0xe7, 0xb6, 0xdd, 0x78, 0x74,
0x8f, 0x54, 0xaa, 0x1a, 0x42, 0x7d, 0xc3, 0xf5, 0x75, 0xe5, 0x34, 0xee, 0xbf, 0xde, 0x36, 0x1e,
0x6c, 0xba, 0xe5, 0x3d, 0x55, 0xd0, 0x59, 0x92, 0x60, 0xe9, 0xdf, 0x66, 0xde, 0x53, 0x31, 0xfa,
0xe6, 0x69, 0xb4, 0x88, 0x45, 0x6a, 0x3d, 0x55, 0x90, 0x03, 0x95, 0x6c, 0x95, 0xbc, 0xb7, 0x7c,
0xde, 0xa7, 0xf0, 0xf8, 0xeb, 0xdf, 0x1d, 0x5e, 0x79, 0x6c, 0xba, 0xb8, 0x3c, 0x18, 0x85, 0xb3,
0x43, 0xf1, 0x27, 0x91, 0xc0, 0x0b, 0xae, 0x02, 0xca, 0xde, 0x86, 0xd1, 0xf5, 0xa1, 0x1f, 0x8c,
0x0f, 0x45, 0x7a, 0x1e, 0x2e, 0xf5, 0x5c, 0x6e, 0x89, 0xbf, 0x88, 0xfe, 0xf4, 0x3f, 0x01, 0x00,
0x00, 0xff, 0xff, 0x77, 0xd4, 0x07, 0xd1, 0x41, 0x15, 0x00, 0x00,
// 2499 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x58, 0x4b, 0x73, 0x1b, 0xc7,
0xf1, 0xf7, 0xe2, 0x45, 0xa0, 0xf1, 0x5a, 0x0e, 0x64, 0x0a, 0x7f, 0x50, 0xb2, 0xe1, 0xb5, 0x2d,
0xa1, 0xf4, 0xb7, 0x29, 0x99, 0x49, 0x25, 0xae, 0xf8, 0x91, 0x80, 0xc0, 0x52, 0x5c, 0x09, 0x04,
0xe0, 0x01, 0x48, 0xdb, 0xf1, 0x61, 0x6a, 0x09, 0x0c, 0x88, 0x0d, 0x17, 0xbb, 0xc8, 0xee, 0x40,
0x32, 0x8f, 0xb9, 0xa5, 0xf2, 0x45, 0x72, 0xcb, 0x27, 0xc8, 0x21, 0x87, 0xdc, 0xf2, 0x21, 0x72,
0xcd, 0x3d, 0x55, 0x39, 0xa7, 0xe6, 0xb1, 0xc0, 0x2e, 0x09, 0x4a, 0x49, 0x55, 0x2e, 0xe4, 0xce,
0xaf, 0x7b, 0x7a, 0xba, 0xa7, 0x5f, 0xd3, 0x80, 0xbd, 0xc0, 0x5f, 0x31, 0x1a, 0x04, 0xcb, 0xc9,
0x53, 0xf9, 0x75, 0xb0, 0x0c, 0x7c, 0xe6, 0xa3, 0xc2, 0x1a, 0x6f, 0x14, 0x82, 0xe5, 0x44, 0xa2,
0xc6, 0x3f, 0x73, 0x80, 0x46, 0xd4, 0x9b, 0x0e, 0xed, 0xeb, 0x05, 0xf5, 0x18, 0xa6, 0xbf, 0x5d,
0xd1, 0x90, 0x21, 0x04, 0x99, 0x29, 0x0d, 0x59, 0x5d, 0x6b, 0x6a, 0xad, 0x12, 0x16, 0xdf, 0x48,
0x87, 0xb4, 0xbd, 0x60, 0xf5, 0x54, 0x53, 0x6b, 0xa5, 0x31, 0xff, 0x44, 0xff, 0x07, 0x79, 0x7b,
0xc1, 0xc8, 0x22, 0xb4, 0x59, 0xbd, 0x24, 0xe0, 0x1d, 0x7b, 0xc1, 0x4e, 0x43, 0x9b, 0xa1, 0x0f,
0xa0, 0xb4, 0x94, 0x22, 0xc9, 0xdc, 0x0e, 0xe7, 0xf5, 0xb4, 0x10, 0x54, 0x54, 0xd8, 0x89, 0x1d,
0xce, 0x51, 0x0b, 0xf4, 0x99, 0xe3, 0xd9, 0x2e, 0x99, 0xb8, 0xec, 0x15, 0x99, 0x52, 0x97, 0xd9,
0xf5, 0x4c, 0x53, 0x6b, 0x65, 0x71, 0x45, 0xe0, 0x1d, 0x97, 0xbd, 0xea, 0x72, 0x14, 0x3d, 0x86,
0x6a, 0x24, 0x2c, 0x90, 0x0a, 0xd6, 0xb3, 0x4d, 0xad, 0x55, 0xc0, 0x95, 0x65, 0x52, 0xed, 0xc7,
0x50, 0x65, 0xce, 0x82, 0xfa, 0x2b, 0x46, 0x42, 0x3a, 0xf1, 0xbd, 0x69, 0x58, 0xcf, 0x49, 0x89,
0x0a, 0x1e, 0x49, 0x14, 0x19, 0x50, 0x9e, 0x51, 0x4a, 0x5c, 0x67, 0xe1, 0x30, 0xc2, 0xd5, 0xdf,
0x11, 0xea, 0x17, 0x67, 0x94, 0xf6, 0x38, 0x36, 0xb2, 0x19, 0xfa, 0x08, 0x2a, 0x1b, 0x1e, 0x61,
0x63, 0x59, 0x30, 0x95, 0x22, 0x26, 0x61, 0xe8, 0x01, 0xe8, 0xfe, 0x8a, 0x5d, 0xfa, 0x8e, 0x77,
0x49, 0x26, 0x73, 0xdb, 0x23, 0xce, 0xb4, 0x9e, 0x6f, 0x6a, 0xad, 0xcc, 0x51, 0xa6, 0xae, 0x3d,
0xd3, 0x70, 0x25, 0xa2, 0x76, 0xe6, 0xb6, 0x67, 0x4d, 0xd1, 0x13, 0xd8, 0xbd, 0xc9, 0x1f, 0xd6,
0x6b, 0xcd, 0x74, 0x2b, 0x83, 0xab, 0x49, 0xd6, 0x10, 0x3d, 0x82, 0xaa, 0x6b, 0x87, 0x8c, 0xcc,
0xfd, 0x25, 0x59, 0xae, 0x2e, 0xae, 0xe8, 0x75, 0xbd, 0x22, 0xee, 0xb1, 0xcc, 0xe1, 0x13, 0x7f,
0x39, 0x14, 0x20, 0x7a, 0x08, 0x20, 0xee, 0x50, 0xa8, 0x5a, 0x2f, 0x08, 0x8b, 0x0b, 0x1c, 0x11,
0x6a, 0xa2, 0xcf, 0xa0, 0x28, 0x7c, 0x4f, 0xe6, 0x8e, 0xc7, 0xc2, 0x3a, 0x34, 0xd3, 0xad, 0xe2,
0xa1, 0x7e, 0xe0, 0x7a, 0x3c, 0x0c, 0x30, 0xa7, 0x9c, 0x38, 0x1e, 0xc3, 0x10, 0x44, 0x9f, 0x21,
0x9a, 0x42, 0x8d, 0xfb, 0x9c, 0x4c, 0x56, 0x21, 0xf3, 0x17, 0x24, 0xa0, 0x13, 0x3f, 0x98, 0x86,
0xf5, 0xa2, 0xd8, 0xfa, 0xd3, 0x83, 0x75, 0x28, 0x1d, 0xdc, 0x8e, 0x9d, 0x83, 0x2e, 0x0d, 0x59,
0x47, 0xec, 0xc3, 0x72, 0x9b, 0xe9, 0xb1, 0xe0, 0x1a, 0xef, 0x4e, 0x6f, 0xe2, 0xe8, 0x13, 0x40,
0xb6, 0xeb, 0xfa, 0xaf, 0x49, 0x48, 0xdd, 0x19, 0x51, 0xbe, 0xac, 0x57, 0x9b, 0x5a, 0x2b, 0x8f,
0x75, 0x41, 0x19, 0x51, 0x77, 0xa6, 0xc4, 0xa3, 0x9f, 0x41, 0x59, 0xe8, 0x34, 0xa3, 0x36, 0x5b,
0x05, 0x34, 0xac, 0xeb, 0xcd, 0x74, 0xab, 0x72, 0xb8, 0xab, 0x0c, 0x39, 0x96, 0xf0, 0x91, 0xc3,
0x70, 0x89, 0xf3, 0xa9, 0x75, 0x88, 0xf6, 0xa1, 0xb0, 0xb0, 0x7f, 0x24, 0x4b, 0x3b, 0x60, 0x61,
0x7d, 0xb7, 0xa9, 0xb5, 0xca, 0x38, 0xbf, 0xb0, 0x7f, 0x1c, 0xf2, 0x35, 0x3a, 0x80, 0x9a, 0xe7,
0x13, 0xc7, 0x9b, 0xb9, 0xce, 0xe5, 0x9c, 0x91, 0xd5, 0x72, 0x6a, 0x33, 0x1a, 0xd6, 0x91, 0xd0,
0x61, 0xd7, 0xf3, 0x2d, 0x45, 0x39, 0x93, 0x84, 0x46, 0x17, 0xf6, 0xb6, 0xdb, 0xc7, 0xd3, 0x83,
0x3b, 0x88, 0x67, 0x4c, 0x06, 0xf3, 0x4f, 0x74, 0x0f, 0xb2, 0xaf, 0x6c, 0x77, 0x45, 0x45, 0xca,
0x94, 0xb0, 0x5c, 0xfc, 0x22, 0xf5, 0xb9, 0x66, 0xcc, 0xa1, 0x36, 0x0e, 0xec, 0xc9, 0xd5, 0x8d,
0xac, 0xbb, 0x99, 0x34, 0xda, 0xed, 0xa4, 0xb9, 0x43, 0xdf, 0xd4, 0x1d, 0xfa, 0x1a, 0x5f, 0x43,
0x55, 0x78, 0xf8, 0x98, 0xd2, 0x37, 0xe5, 0xf6, 0x7d, 0xe0, 0x99, 0x2b, 0x32, 0x41, 0xe6, 0x77,
0xce, 0x5e, 0xf0, 0x24, 0x30, 0xa6, 0xa0, 0x6f, 0xf6, 0x87, 0x4b, 0xdf, 0x0b, 0x29, 0x4f, 0x5c,
0x1e, 0x00, 0x3c, 0x82, 0x79, 0x82, 0x88, 0xd4, 0xd0, 0xc4, 0xae, 0x8a, 0xc2, 0x8f, 0x29, 0x15,
0xc9, 0xf1, 0x48, 0xe6, 0x23, 0x71, 0xfd, 0xc9, 0x15, 0xcf, 0x70, 0xfb, 0x5a, 0x89, 0x2f, 0x73,
0xb8, 0xe7, 0x4f, 0xae, 0xba, 0x1c, 0x34, 0x7e, 0x90, 0x45, 0x68, 0xec, 0x8b, 0xb3, 0xfe, 0x8b,
0xeb, 0x30, 0x20, 0x2b, 0x62, 0x51, 0x88, 0x2d, 0x1e, 0x96, 0xe2, 0x41, 0x8d, 0x25, 0xc9, 0xf8,
0x01, 0x6a, 0x09, 0xe1, 0xca, 0x8a, 0x06, 0xe4, 0x97, 0x01, 0x75, 0x16, 0xf6, 0x25, 0x55, 0x92,
0xd7, 0x6b, 0xd4, 0x82, 0x9d, 0x99, 0xed, 0xb8, 0xab, 0x20, 0x12, 0x5c, 0x89, 0x82, 0x4c, 0xa2,
0x38, 0x22, 0x1b, 0x0f, 0xa0, 0x81, 0x69, 0x48, 0xd9, 0xa9, 0x13, 0x86, 0x8e, 0xef, 0x75, 0x7c,
0x8f, 0x05, 0xbe, 0xab, 0x2c, 0x30, 0x1e, 0xc2, 0xfe, 0x56, 0xaa, 0x54, 0x81, 0x6f, 0xfe, 0x66,
0x45, 0x83, 0xeb, 0xed, 0x9b, 0xbf, 0x81, 0xfd, 0xad, 0x54, 0xa5, 0xff, 0x27, 0x90, 0x5d, 0xda,
0x4e, 0xc0, 0x7d, 0xcf, 0x93, 0x72, 0x2f, 0x96, 0x94, 0x43, 0xdb, 0x09, 0x4e, 0x9c, 0x90, 0xf9,
0xc1, 0x35, 0x96, 0x4c, 0x2f, 0x32, 0x79, 0x4d, 0x4f, 0x19, 0x7f, 0xd0, 0xa0, 0x18, 0x23, 0xf2,
0xd4, 0xf0, 0xfc, 0x29, 0x25, 0xb3, 0xc0, 0x5f, 0x44, 0x97, 0xc0, 0x81, 0xe3, 0xc0, 0x5f, 0xf0,
0x98, 0x10, 0x44, 0xe6, 0xab, 0x00, 0xce, 0xf1, 0xe5, 0xd8, 0x47, 0x9f, 0xc2, 0xce, 0x5c, 0x0a,
0x10, 0x65, 0xb3, 0x78, 0x58, 0xbb, 0x71, 0x76, 0xd7, 0x66, 0x36, 0x8e, 0x78, 0x5e, 0x64, 0xf2,
0x69, 0x3d, 0xf3, 0x22, 0x93, 0xcf, 0xe8, 0xd9, 0x17, 0x99, 0x7c, 0x56, 0xcf, 0xbd, 0xc8, 0xe4,
0x73, 0xfa, 0x8e, 0xf1, 0x0f, 0x0d, 0xf2, 0x11, 0x37, 0xd7, 0x84, 0x5f, 0x29, 0xe1, 0x71, 0xa1,
0x82, 0x29, 0xcf, 0x81, 0xb1, 0xb3, 0xa0, 0xa8, 0x09, 0x25, 0x41, 0x4c, 0x86, 0x28, 0x70, 0xac,
0x2d, 0xc2, 0x54, 0xd4, 0xf3, 0x88, 0x43, 0xc4, 0x63, 0x46, 0xd5, 0x73, 0xc9, 0x12, 0xb5, 0xa4,
0x70, 0x35, 0x99, 0xd0, 0x30, 0x94, 0xa7, 0x64, 0x25, 0x8b, 0xc2, 0xc4, 0x41, 0x8f, 0xa0, 0x1a,
0xb1, 0x44, 0x67, 0xe5, 0x64, 0xbc, 0x2a, 0x58, 0x1d, 0xd7, 0x02, 0x3d, 0xce, 0xb7, 0xd8, 0x74,
0x90, 0xca, 0x86, 0x91, 0x1f, 0x2a, 0x8d, 0x37, 0x7e, 0x03, 0xf7, 0x85, 0x2b, 0x87, 0x81, 0x7f,
0x61, 0x5f, 0x38, 0xae, 0xc3, 0xae, 0xa3, 0x20, 0xe7, 0x86, 0x07, 0xfe, 0x82, 0xf0, 0xbb, 0x8d,
0x5c, 0xc0, 0x81, 0xbe, 0x3f, 0xa5, 0xdc, 0x05, 0xcc, 0x97, 0x24, 0xe5, 0x02, 0xe6, 0x0b, 0x42,
0xbc, 0xf3, 0xa6, 0x13, 0x9d, 0xd7, 0xb8, 0x82, 0xfa, 0xed, 0xb3, 0x54, 0xcc, 0x34, 0xa1, 0xb8,
0xdc, 0xc0, 0xe2, 0x38, 0x0d, 0xc7, 0xa1, 0xb8, 0x6f, 0x53, 0x6f, 0xf7, 0xad, 0xf1, 0x47, 0x0d,
0x76, 0x8f, 0x56, 0x8e, 0x3b, 0x4d, 0x24, 0x6e, 0x5c, 0x3b, 0x2d, 0xf9, 0x2e, 0xd8, 0xd6, 0xf4,
0x53, 0x5b, 0x9b, 0xfe, 0x27, 0x5b, 0x1a, 0x6b, 0x5a, 0x34, 0xd6, 0xd4, 0x96, 0xb6, 0xfa, 0x3e,
0x14, 0x37, 0x5d, 0x32, 0xac, 0x67, 0x9a, 0xe9, 0x56, 0x09, 0xc3, 0x3c, 0x6a, 0x91, 0xa1, 0xf1,
0x39, 0xa0, 0xb8, 0xa2, 0xea, 0x42, 0xd6, 0xf5, 0x43, 0xbb, 0xbb, 0x7e, 0x3c, 0x80, 0xc6, 0x68,
0x75, 0x11, 0x4e, 0x02, 0xe7, 0x82, 0x9e, 0x30, 0x77, 0x62, 0xbe, 0xa2, 0x1e, 0x0b, 0xa3, 0x2c,
0xfd, 0x57, 0x06, 0x0a, 0x6b, 0x94, 0x97, 0x67, 0xc7, 0x9b, 0xf8, 0x8b, 0x48, 0x69, 0x8f, 0xba,
0x5c, 0x6f, 0xd9, 0x14, 0x76, 0x23, 0x52, 0x47, 0x52, 0xac, 0x29, 0xe7, 0x4f, 0x18, 0xa9, 0xf8,
0x53, 0x92, 0x3f, 0x6e, 0xa3, 0xe4, 0x6f, 0x81, 0xbe, 0x96, 0x3f, 0x67, 0xee, 0x64, 0x7d, 0x29,
0xb8, 0x12, 0xe1, 0x5c, 0x19, 0xc9, 0xb9, 0x96, 0x1c, 0x71, 0x66, 0x24, 0x67, 0x84, 0x2b, 0xce,
0x0f, 0xa0, 0xc4, 0xf3, 0x21, 0x64, 0xf6, 0x62, 0x49, 0xbc, 0x50, 0xe4, 0x45, 0x06, 0x17, 0xd7,
0x58, 0x3f, 0x44, 0x5f, 0x01, 0x50, 0x6e, 0x1f, 0x61, 0xd7, 0x4b, 0x2a, 0x52, 0xa2, 0x72, 0xf8,
0x5e, 0x2c, 0x30, 0xd6, 0x17, 0x70, 0x20, 0xfe, 0x8e, 0xaf, 0x97, 0x14, 0x17, 0x68, 0xf4, 0x89,
0xbe, 0x86, 0xf2, 0xcc, 0x0f, 0x5e, 0xdb, 0xc1, 0x94, 0x08, 0x50, 0x95, 0x8d, 0xfb, 0x31, 0x09,
0xc7, 0x92, 0x2e, 0xb6, 0x9f, 0xbc, 0x83, 0x4b, 0xb3, 0xd8, 0x1a, 0xbd, 0x04, 0x14, 0xed, 0x17,
0x59, 0x2e, 0x85, 0xe4, 0x85, 0x90, 0xfd, 0xdb, 0x42, 0x78, 0x91, 0x8e, 0x04, 0xe9, 0xb3, 0x1b,
0x18, 0xfa, 0x02, 0x4a, 0x21, 0x65, 0xcc, 0xa5, 0x4a, 0x4c, 0x41, 0x88, 0xd9, 0x4b, 0xbc, 0x69,
0x38, 0x39, 0x92, 0x50, 0x0c, 0x37, 0x4b, 0x74, 0x04, 0x55, 0xd7, 0xf1, 0xae, 0xe2, 0x6a, 0x80,
0xd8, 0x5f, 0x8f, 0xed, 0xef, 0x39, 0xde, 0x55, 0x5c, 0x87, 0xb2, 0x1b, 0x07, 0x8c, 0x2f, 0xa1,
0xb0, 0xbe, 0x25, 0x54, 0x84, 0x9d, 0xb3, 0xfe, 0xcb, 0xfe, 0xe0, 0xdb, 0xbe, 0xfe, 0x0e, 0xca,
0x43, 0x66, 0x64, 0xf6, 0xbb, 0xba, 0xc6, 0x61, 0x6c, 0x76, 0x4c, 0xeb, 0xdc, 0xd4, 0x53, 0x7c,
0x71, 0x3c, 0xc0, 0xdf, 0xb6, 0x71, 0x57, 0x4f, 0x1f, 0xed, 0x40, 0x56, 0x9c, 0x6b, 0xfc, 0x59,
0x83, 0xbc, 0xf0, 0xa0, 0x37, 0xf3, 0xd1, 0xff, 0xc3, 0x3a, 0xb8, 0x44, 0x71, 0xe3, 0x0d, 0x57,
0x44, 0x5d, 0x19, 0xaf, 0x03, 0x66, 0xac, 0x70, 0xce, 0xbc, 0x0e, 0x8d, 0x35, 0x73, 0x4a, 0x32,
0x47, 0x84, 0x35, 0xf3, 0x93, 0x98, 0xe4, 0x44, 0xc9, 0xc9, 0xe0, 0x6a, 0x44, 0x88, 0x2a, 0x6c,
0xfc, 0x6d, 0x9b, 0xa8, 0xc4, 0xb1, 0xb7, 0xad, 0xe2, 0x35, 0x7e, 0x0e, 0xa5, 0xb8, 0xcf, 0xd1,
0x63, 0xc8, 0x38, 0xde, 0xcc, 0x57, 0x89, 0x58, 0xbb, 0x11, 0x5c, 0xdc, 0x48, 0x2c, 0x18, 0x0c,
0x04, 0xfa, 0x4d, 0x3f, 0x1b, 0x65, 0x28, 0xc6, 0x9c, 0x66, 0xfc, 0x5d, 0x83, 0x72, 0xc2, 0x09,
0xff, 0xb1, 0x74, 0xf4, 0x15, 0x94, 0x5e, 0x3b, 0x01, 0x25, 0xf1, 0xf6, 0x5f, 0x39, 0x6c, 0x24,
0xdb, 0x7f, 0xf4, 0xbf, 0xe3, 0x4f, 0x29, 0x2e, 0x72, 0x7e, 0x05, 0xa0, 0x5f, 0x42, 0x45, 0xed,
0x24, 0x53, 0xca, 0x6c, 0xc7, 0x15, 0x57, 0x55, 0x49, 0x84, 0x87, 0xe2, 0xed, 0x0a, 0x3a, 0x2e,
0xcf, 0xe2, 0x4b, 0xf4, 0xf1, 0x46, 0x40, 0xc8, 0x02, 0xc7, 0xbb, 0x14, 0xf7, 0x57, 0x58, 0xb3,
0x8d, 0x04, 0xc8, 0x1b, 0x79, 0x59, 0x3d, 0x1e, 0x47, 0xcc, 0x66, 0xab, 0x10, 0x7d, 0x0a, 0xd9,
0x90, 0xd9, 0xaa, 0x92, 0x55, 0x12, 0xb9, 0x15, 0x63, 0xa4, 0x58, 0x72, 0x25, 0x5e, 0x3f, 0xa9,
0x5b, 0xaf, 0x9f, 0x2c, 0xaf, 0x18, 0xb2, 0x8a, 0x16, 0x0f, 0x91, 0x32, 0xfe, 0x64, 0xdc, 0xeb,
0xb4, 0x19, 0xa3, 0x8b, 0x25, 0xc3, 0x92, 0x41, 0x75, 0xb7, 0xaf, 0x01, 0x3a, 0x4e, 0x30, 0x59,
0x39, 0xec, 0x25, 0xbd, 0xe6, 0x3d, 0x2b, 0x2a, 0xd7, 0xb2, 0xec, 0xe5, 0x26, 0xb2, 0x44, 0xdf,
0x87, 0x9d, 0xa8, 0x10, 0xc9, 0xfa, 0x96, 0x9b, 0x8b, 0x02, 0x64, 0xfc, 0x4d, 0x83, 0x7d, 0xe5,
0x52, 0xe9, 0x0d, 0x46, 0x83, 0x09, 0x5d, 0xae, 0x9f, 0xc5, 0xcf, 0xe1, 0xde, 0xa6, 0xa8, 0xca,
0x83, 0x48, 0xf4, 0xd4, 0x2e, 0x1e, 0xbe, 0x1b, 0xb3, 0x74, 0xa3, 0x06, 0x46, 0xeb, 0x62, 0xbb,
0x51, 0xed, 0x09, 0xec, 0x0a, 0x0d, 0x12, 0xaf, 0x4a, 0x69, 0x7d, 0x95, 0x13, 0x86, 0xb1, 0x97,
0xe5, 0xfb, 0x50, 0xb4, 0x17, 0xfe, 0xca, 0x4b, 0x44, 0x3c, 0x48, 0x48, 0x04, 0xfb, 0x1e, 0xe4,
0xe8, 0x8f, 0x4b, 0x27, 0xb8, 0x16, 0x1e, 0x2a, 0x63, 0xb5, 0x32, 0xfe, 0xa2, 0xc1, 0x83, 0xed,
0xd6, 0xa8, 0x9e, 0xf3, 0x3f, 0x33, 0xe7, 0x0b, 0xc8, 0xd9, 0x13, 0xe6, 0xf8, 0x9e, 0x8a, 0xd2,
0x0f, 0x63, 0x5b, 0x31, 0x0d, 0x7d, 0xf7, 0x15, 0x3d, 0xf1, 0xdd, 0xa9, 0x52, 0xa6, 0x2d, 0x58,
0xb1, 0xda, 0x92, 0x08, 0x80, 0x74, 0x32, 0x00, 0x9e, 0xfc, 0x2e, 0x03, 0xe5, 0x44, 0x94, 0x26,
0xcb, 0x54, 0x19, 0x0a, 0xfd, 0x01, 0xe9, 0x9a, 0xe3, 0xb6, 0xd5, 0xd3, 0x35, 0xa4, 0x43, 0x69,
0xd0, 0xb7, 0x06, 0x7d, 0xd2, 0x35, 0x3b, 0x83, 0x2e, 0x2f, 0x58, 0xef, 0xc2, 0x6e, 0xcf, 0xea,
0xbf, 0x24, 0xfd, 0xc1, 0x98, 0x98, 0x3d, 0xeb, 0xb9, 0x75, 0xd4, 0x33, 0xf5, 0x34, 0xba, 0x07,
0xfa, 0xa0, 0x4f, 0x3a, 0x27, 0x6d, 0xab, 0x4f, 0xc6, 0xd6, 0xa9, 0x39, 0x38, 0x1b, 0xeb, 0x19,
0x8e, 0xf2, 0xc8, 0x22, 0xe6, 0x77, 0x1d, 0xd3, 0xec, 0x8e, 0xc8, 0x69, 0xfb, 0x3b, 0x3d, 0x8b,
0xea, 0x70, 0xcf, 0xea, 0x8f, 0xce, 0x8e, 0x8f, 0xad, 0x8e, 0x65, 0xf6, 0xc7, 0xe4, 0xa8, 0xdd,
0x6b, 0xf7, 0x3b, 0xa6, 0x9e, 0x43, 0x7b, 0x80, 0xac, 0x7e, 0x67, 0x70, 0x3a, 0xec, 0x99, 0x63,
0x93, 0x44, 0x85, 0x71, 0x07, 0xd5, 0xa0, 0x2a, 0xe4, 0xb4, 0xbb, 0x5d, 0x72, 0xdc, 0xb6, 0x7a,
0x66, 0x57, 0xcf, 0x73, 0x4d, 0x14, 0xc7, 0x88, 0x74, 0xad, 0x51, 0xfb, 0x88, 0xc3, 0x05, 0x7e,
0xa6, 0xd5, 0x3f, 0x1f, 0x58, 0x1d, 0x93, 0x74, 0xb8, 0x58, 0x8e, 0x02, 0x67, 0x8e, 0xd0, 0xb3,
0x7e, 0xd7, 0xc4, 0xc3, 0xb6, 0xd5, 0xd5, 0x8b, 0x68, 0x1f, 0xee, 0x47, 0xb0, 0xf9, 0xdd, 0xd0,
0xc2, 0xdf, 0x93, 0xf1, 0x60, 0x40, 0x46, 0x83, 0x41, 0x5f, 0x2f, 0xc5, 0x25, 0x71, 0x6b, 0x07,
0x43, 0xb3, 0xaf, 0x97, 0xd1, 0x7d, 0xa8, 0x9d, 0x0e, 0x87, 0x24, 0xa2, 0x44, 0xc6, 0x56, 0x38,
0x7b, 0xbb, 0xdb, 0xc5, 0xe6, 0x68, 0x44, 0x4e, 0xad, 0xd1, 0x69, 0x7b, 0xdc, 0x39, 0xd1, 0xab,
0xdc, 0xa4, 0x91, 0x39, 0x26, 0xe3, 0xc1, 0xb8, 0xdd, 0xdb, 0xe0, 0x3a, 0x57, 0x68, 0x83, 0xf3,
0x43, 0x7b, 0x83, 0x6f, 0xf5, 0x5d, 0x7e, 0xe1, 0x1c, 0x1e, 0x9c, 0x2b, 0x15, 0x11, 0xb7, 0x5d,
0xb9, 0x27, 0x3a, 0x53, 0xaf, 0x71, 0xd0, 0xea, 0x9f, 0xb7, 0x7b, 0x56, 0x97, 0xbc, 0x34, 0xbf,
0x17, 0x8d, 0xe5, 0x1e, 0x07, 0xa5, 0x66, 0x64, 0x88, 0x07, 0xcf, 0xb9, 0x22, 0xfa, 0xbb, 0x08,
0x41, 0xa5, 0x63, 0xe1, 0xce, 0x59, 0xaf, 0x8d, 0x09, 0x1e, 0x9c, 0x8d, 0x4d, 0x7d, 0xef, 0xc9,
0x9f, 0x34, 0x28, 0xc5, 0x0b, 0x07, 0xf7, 0xba, 0xd5, 0x27, 0xc7, 0x3d, 0xeb, 0xf9, 0xc9, 0x58,
0x06, 0xc1, 0xe8, 0xac, 0xc3, 0x5d, 0x66, 0xf2, 0x86, 0x85, 0xa0, 0x22, 0x2f, 0x7d, 0x6d, 0x6c,
0x8a, 0x9f, 0xa5, 0xb0, 0xfe, 0x40, 0xc9, 0x4d, 0x73, 0xe5, 0x15, 0x68, 0x62, 0x3c, 0xc0, 0x7a,
0x06, 0x7d, 0x04, 0x4d, 0x85, 0x70, 0xbf, 0x62, 0x6c, 0x76, 0xc6, 0x64, 0xd8, 0xfe, 0xfe, 0x94,
0xbb, 0x5d, 0x06, 0xd9, 0x48, 0xcf, 0xa2, 0xf7, 0x61, 0x7f, 0xcd, 0xb5, 0x2d, 0x2e, 0x9e, 0x7c,
0x09, 0xf5, 0xbb, 0x82, 0x1e, 0x01, 0xe4, 0x46, 0xe6, 0x78, 0xdc, 0x33, 0x65, 0x93, 0x3d, 0x96,
0x81, 0x0b, 0x90, 0xc3, 0xe6, 0xe8, 0xec, 0xd4, 0xd4, 0x53, 0x87, 0x7f, 0xcd, 0x43, 0x4e, 0xbc,
0xfa, 0x02, 0xf4, 0x2b, 0x28, 0xc7, 0x7e, 0xd5, 0x38, 0x3f, 0x44, 0x0f, 0xdf, 0xf8, 0x7b, 0x47,
0x23, 0x9a, 0x0d, 0x15, 0xfc, 0x4c, 0x43, 0x47, 0x50, 0x89, 0x8f, 0xf7, 0xe7, 0x87, 0x28, 0xfe,
0x58, 0xda, 0x32, 0xf9, 0x6f, 0x91, 0xf1, 0x12, 0x74, 0x33, 0x64, 0xce, 0x82, 0xd7, 0x6c, 0x35,
0x80, 0xa3, 0x46, 0x3c, 0xc1, 0x93, 0x53, 0x7d, 0x63, 0x7f, 0x2b, 0x4d, 0x95, 0x9c, 0x6f, 0x78,
0x7f, 0x5c, 0x8f, 0xc0, 0xb7, 0x0c, 0x4a, 0xce, 0xdd, 0x8d, 0xf7, 0xee, 0x22, 0xab, 0xb1, 0x35,
0xfd, 0xfb, 0x14, 0xb7, 0xb1, 0x1c, 0xa3, 0x6d, 0xb9, 0xa5, 0x1b, 0x42, 0xb7, 0x74, 0x11, 0x34,
0x85, 0xda, 0x96, 0xf1, 0x18, 0x7d, 0x9c, 0xac, 0x63, 0x77, 0x0c, 0xd7, 0x8d, 0x47, 0x6f, 0x63,
0x53, 0xc6, 0x4f, 0xa1, 0xb6, 0x65, 0x8e, 0x4e, 0x9c, 0x72, 0xf7, 0x14, 0x9e, 0x38, 0xe5, 0x4d,
0xe3, 0xf8, 0x0f, 0xa0, 0xdf, 0x1c, 0xbb, 0x90, 0x71, 0x73, 0xef, 0xed, 0xf9, 0xaf, 0xf1, 0xe1,
0x1b, 0x79, 0x94, 0x70, 0x0b, 0x60, 0x33, 0xbc, 0xa0, 0x07, 0xb1, 0x2d, 0xb7, 0x86, 0xaf, 0xc6,
0xc3, 0x3b, 0xa8, 0x4a, 0xd4, 0x18, 0x6a, 0x5b, 0xa6, 0x99, 0xc4, 0x6d, 0xdc, 0x3d, 0xed, 0x34,
0xee, 0x6d, 0x7b, 0xf4, 0x3f, 0xd3, 0xd0, 0xa9, 0x0c, 0xb0, 0xe8, 0xa7, 0xba, 0xb7, 0x64, 0x4c,
0x7d, 0xfb, 0xe3, 0x64, 0x15, 0x8a, 0xd0, 0x7a, 0xa6, 0xa1, 0x01, 0x94, 0xe2, 0x59, 0xf2, 0xd6,
0xf4, 0x79, 0xab, 0xc0, 0x19, 0x54, 0x13, 0xcd, 0xd8, 0x0f, 0xd0, 0xe3, 0xdb, 0x83, 0xc3, 0xd6,
0x7e, 0x9d, 0x88, 0x80, 0x37, 0x3c, 0x53, 0x5a, 0xda, 0x33, 0xed, 0xe8, 0xb3, 0x5f, 0x3f, 0xbd,
0x74, 0xd8, 0x7c, 0x75, 0x71, 0x30, 0xf1, 0x17, 0x4f, 0xc5, 0x2f, 0x71, 0x9e, 0xe3, 0x5d, 0x7a,
0x94, 0xbd, 0xf6, 0x83, 0xab, 0xa7, 0xae, 0x37, 0x7d, 0x2a, 0xd2, 0xe0, 0xe9, 0x5a, 0xe4, 0x45,
0x4e, 0xfc, 0x10, 0xff, 0x93, 0x7f, 0x07, 0x00, 0x00, 0xff, 0xff, 0x33, 0x56, 0x83, 0xe8, 0xb8,
0x17, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
@ -1975,6 +2207,13 @@ type RouterClient interface {
//Deprecated, use TrackPaymentV2. TrackPayment returns an update stream for
//the payment identified by the payment hash.
TrackPayment(ctx context.Context, in *TrackPaymentRequest, opts ...grpc.CallOption) (Router_TrackPaymentClient, error)
//*
//HtlcInterceptor dispatches a bi-directional streaming RPC in which
//Forwarded HTLC requests are sent to the client and the client responds with
//a boolean that tells LND if this htlc should be intercepted.
//In case of interception, the htlc can be either settled, cancelled or
//resumed later by using the ResolveHoldForward endpoint.
HtlcInterceptor(ctx context.Context, opts ...grpc.CallOption) (Router_HtlcInterceptorClient, error)
}
type routerClient struct {
@ -2211,6 +2450,37 @@ func (x *routerTrackPaymentClient) Recv() (*PaymentStatus, error) {
return m, nil
}
func (c *routerClient) HtlcInterceptor(ctx context.Context, opts ...grpc.CallOption) (Router_HtlcInterceptorClient, error) {
stream, err := c.cc.NewStream(ctx, &_Router_serviceDesc.Streams[5], "/routerrpc.Router/HtlcInterceptor", opts...)
if err != nil {
return nil, err
}
x := &routerHtlcInterceptorClient{stream}
return x, nil
}
type Router_HtlcInterceptorClient interface {
Send(*ForwardHtlcInterceptResponse) error
Recv() (*ForwardHtlcInterceptRequest, error)
grpc.ClientStream
}
type routerHtlcInterceptorClient struct {
grpc.ClientStream
}
func (x *routerHtlcInterceptorClient) Send(m *ForwardHtlcInterceptResponse) error {
return x.ClientStream.SendMsg(m)
}
func (x *routerHtlcInterceptorClient) Recv() (*ForwardHtlcInterceptRequest, error) {
m := new(ForwardHtlcInterceptRequest)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// RouterServer is the server API for Router service.
type RouterServer interface {
//
@ -2269,6 +2539,13 @@ type RouterServer interface {
//Deprecated, use TrackPaymentV2. TrackPayment returns an update stream for
//the payment identified by the payment hash.
TrackPayment(*TrackPaymentRequest, Router_TrackPaymentServer) error
//*
//HtlcInterceptor dispatches a bi-directional streaming RPC in which
//Forwarded HTLC requests are sent to the client and the client responds with
//a boolean that tells LND if this htlc should be intercepted.
//In case of interception, the htlc can be either settled, cancelled or
//resumed later by using the ResolveHoldForward endpoint.
HtlcInterceptor(Router_HtlcInterceptorServer) error
}
// UnimplementedRouterServer can be embedded to have forward compatible implementations.
@ -2311,6 +2588,9 @@ func (*UnimplementedRouterServer) SendPayment(req *SendPaymentRequest, srv Route
func (*UnimplementedRouterServer) TrackPayment(req *TrackPaymentRequest, srv Router_TrackPaymentServer) error {
return status.Errorf(codes.Unimplemented, "method TrackPayment not implemented")
}
func (*UnimplementedRouterServer) HtlcInterceptor(srv Router_HtlcInterceptorServer) error {
return status.Errorf(codes.Unimplemented, "method HtlcInterceptor not implemented")
}
func RegisterRouterServer(s *grpc.Server, srv RouterServer) {
s.RegisterService(&_Router_serviceDesc, srv)
@ -2547,6 +2827,32 @@ func (x *routerTrackPaymentServer) Send(m *PaymentStatus) error {
return x.ServerStream.SendMsg(m)
}
func _Router_HtlcInterceptor_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(RouterServer).HtlcInterceptor(&routerHtlcInterceptorServer{stream})
}
type Router_HtlcInterceptorServer interface {
Send(*ForwardHtlcInterceptRequest) error
Recv() (*ForwardHtlcInterceptResponse, error)
grpc.ServerStream
}
type routerHtlcInterceptorServer struct {
grpc.ServerStream
}
func (x *routerHtlcInterceptorServer) Send(m *ForwardHtlcInterceptRequest) error {
return x.ServerStream.SendMsg(m)
}
func (x *routerHtlcInterceptorServer) Recv() (*ForwardHtlcInterceptResponse, error) {
m := new(ForwardHtlcInterceptResponse)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
var _Router_serviceDesc = grpc.ServiceDesc{
ServiceName: "routerrpc.Router",
HandlerType: (*RouterServer)(nil),
@ -2606,6 +2912,12 @@ var _Router_serviceDesc = grpc.ServiceDesc{
Handler: _Router_TrackPayment_Handler,
ServerStreams: true,
},
{
StreamName: "HtlcInterceptor",
Handler: _Router_HtlcInterceptor_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "routerrpc/router.proto",
}

@ -98,6 +98,15 @@ service Router {
rpc TrackPayment (TrackPaymentRequest) returns (stream PaymentStatus) {
option deprecated = true;
}
/**
HtlcInterceptor dispatches a bi-directional streaming RPC in which
Forwarded HTLC requests are sent to the client and the client responds with
a boolean that tells LND if this htlc should be intercepted.
In case of interception, the htlc can be either settled, cancelled or
resumed later by using the ResolveHoldForward endpoint.
*/
rpc HtlcInterceptor (stream ForwardHtlcInterceptResponse) returns (stream ForwardHtlcInterceptRequest);
}
message SendPaymentRequest {
@ -579,3 +588,59 @@ message PaymentStatus {
*/
repeated lnrpc.HTLCAttempt htlcs = 4;
}
message CircuitKey {
/// The id of the channel that the is part of this circuit.
uint64 chan_id = 1;
/// The index of the incoming htlc in the incoming channel.
uint64 htlc_id = 2;
}
message ForwardHtlcInterceptRequest {
/*
The key of this forwarded htlc. It defines the incoming channel id and
the index in this channel.
*/
CircuitKey incoming_circuit_key = 1;
/*
The htlc payment hash. This value is not guaranteed to be unique per
request.
*/
bytes htlc_payment_hash = 2;
/// The htlc amount.
uint64 amount_msat = 3;
/// The htlc expiry.
uint32 expiry = 4;
}
/**
ForwardHtlcInterceptResponse enables the caller to resolve a previously hold forward.
The caller can choose either to:
- `Resume`: Execute the default behavior (usually forward).
- `Reject`: Fail the htlc backwards.
- `Settle`: Settle this htlc with a given preimage.
*/
message ForwardHtlcInterceptResponse {
/**
The key of this forwarded htlc. It defines the incoming channel id and
the index in this channel.
*/
CircuitKey incoming_circuit_key = 1;
// The resolve action for this intercepted htlc.
ResolveHoldForwardAction action = 2;
// The preimage in case the resolve action is Settle.
bytes preimage = 3;
}
enum ResolveHoldForwardAction {
SETTLE = 0;
FAIL = 1;
RESUME = 2;
}

@ -844,6 +844,21 @@
}
}
},
"routerrpcCircuitKey": {
"type": "object",
"properties": {
"chan_id": {
"type": "string",
"format": "uint64",
"description": "/ The id of the channel that the is part of this circuit."
},
"htlc_id": {
"type": "string",
"format": "uint64",
"description": "/ The index of the incoming htlc in the incoming channel."
}
}
},
"routerrpcFailureDetail": {
"type": "string",
"enum": [
@ -885,6 +900,30 @@
"routerrpcForwardFailEvent": {
"type": "object"
},
"routerrpcForwardHtlcInterceptRequest": {
"type": "object",
"properties": {
"incoming_circuit_key": {
"$ref": "#/definitions/routerrpcCircuitKey",
"description": "The key of this forwarded htlc. It defines the incoming channel id and\nthe index in this channel."
},
"htlc_payment_hash": {
"type": "string",
"format": "byte",
"description": "The htlc payment hash. This value is not guaranteed to be unique per\nrequest."
},
"amount_msat": {
"type": "string",
"format": "uint64",
"description": "/ The htlc amount."
},
"expiry": {
"type": "integer",
"format": "int64",
"description": "/ The htlc expiry."
}
}
},
"routerrpcHtlcEvent": {
"type": "object",
"properties": {
@ -1110,6 +1149,15 @@
"routerrpcResetMissionControlResponse": {
"type": "object"
},
"routerrpcResolveHoldForwardAction": {
"type": "string",
"enum": [
"SETTLE",
"FAIL",
"RESUME"
],
"default": "SETTLE"
},
"routerrpcRouteFeeRequest": {
"type": "object",
"properties": {

@ -70,6 +70,10 @@ type RouterBackend struct {
// SubscribeHtlcEvents returns a subscription client for the node's
// htlc events.
SubscribeHtlcEvents func() (*subscribe.Client, error)
// InterceptableForwarder exposes the ability to intercept forward events
// by letting the router register a ForwardInterceptor.
InterceptableForwarder htlcswitch.InterceptableHtlcForwarder
}
// MissionControl defines the mission control dependencies of routerrpc.

@ -34,6 +34,11 @@ const (
var (
errServerShuttingDown = errors.New("routerrpc server shutting down")
// ErrInterceptorAlreadyExists is an error returned when the a new stream
// is opened and there is already one active interceptor.
// The user must disconnect prior to open another stream.
ErrInterceptorAlreadyExists = errors.New("interceptor already exists")
// macaroonOps are the set of capabilities that our minted macaroon (if
// it doesn't already exist) will have.
macaroonOps = []bakery.Op{
@ -97,6 +102,10 @@ var (
Entity: "offchain",
Action: "read",
}},
"/routerrpc.Router/HtlcInterceptor": {{
Entity: "offchain",
Action: "write",
}},
}
// DefaultRouterMacFilename is the default name of the router macaroon
@ -108,8 +117,9 @@ var (
// Server is a stand alone sub RPC server which exposes functionality that
// allows clients to route arbitrary payment through the Lightning Network.
type Server struct {
started int32 // To be used atomically.
shutdown int32 // To be used atomically.
started int32 // To be used atomically.
shutdown int32 // To be used atomically.
forwardInterceptorActive int32 // To be used atomically.
cfg *Config
@ -609,3 +619,22 @@ func (s *Server) SubscribeHtlcEvents(req *SubscribeHtlcEventsRequest,
}
}
}
// HtlcInterceptor is a bidirectional stream for streaming interception
// requests to the caller.
// Upon connection it does the following:
// 1. Check if there is already a live stream, if yes it rejects the request.
// 2. Regsitered a ForwardInterceptor
// 3. Delivers to the caller every √√ and detect his answer.
// It uses a local implementation of holdForwardsStore to keep all the hold
// forwards and find them when manual resolution is later needed.
func (s *Server) HtlcInterceptor(stream Router_HtlcInterceptorServer) error {
// We ensure there is only one interceptor at a time.
if !atomic.CompareAndSwapInt32(&s.forwardInterceptorActive, 0, 1) {
return ErrInterceptorAlreadyExists
}
defer atomic.CompareAndSwapInt32(&s.forwardInterceptorActive, 1, 0)
// run the forward interceptor.
return newForwardInterceptor(s, stream).run()
}

@ -0,0 +1,387 @@
// +build rpctest
package itest
import (
"context"
"encoding/hex"
"fmt"
"sync"
"time"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/routing/route"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type interceptorTestCase struct {
amountMsat int64
invoice *lnrpc.Invoice
shouldHold bool
interceptorAction routerrpc.ResolveHoldForwardAction
}
// testForwardInterceptor tests the forward interceptor RPC layer.
// The test creates a cluster of 3 connected nodes: Alice -> Bob -> Carol
// Alice sends 4 different payments to Carol while the interceptor handles
// differently the htlcs.
// The test ensures that:
// 1. Intercepted failed htlcs result in no payment (invoice is not settled).
// 2. Intercepted resumed htlcs result in a payment (invoice is settled).
// 3. Intercepted held htlcs result in no payment (invoice is not settled).
// 4. When Interceptor disconnects it resumes all held htlcs, which result in
// valid payment (invoice is settled).
func testForwardInterceptor(net *lntest.NetworkHarness, t *harnessTest) {
// initialize the test context with 3 connected nodes.
testContext := newInterceptorTestContext(t, net)
defer testContext.shutdownNodes()
const (
chanAmt = btcutil.Amount(300000)
)
// Open and wait for channels.
testContext.openChannel(testContext.alice, testContext.bob, chanAmt)
testContext.openChannel(testContext.bob, testContext.carol, chanAmt)
defer testContext.closeChannels()
testContext.waitForChannels()
// Connect the interceptor.
ctx := context.Background()
ctxt, cancelInterceptor := context.WithTimeout(ctx, defaultTimeout)
interceptor, err := testContext.bob.RouterClient.HtlcInterceptor(ctxt)
if err != nil {
t.Fatalf("failed to create HtlcInterceptor %v", err)
}
// Prepare the test cases.
testCases, err := testContext.prepareTestCases()
if err != nil {
t.Fatalf("failed to prepare test cases")
}
// A channel for the interceptor go routine to send the requested packets.
interceptedChan := make(chan *routerrpc.ForwardHtlcInterceptRequest,
len(testCases))
// Run the interceptor loop in its own go routine.
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
for {
request, err := interceptor.Recv()
if err != nil {
// If it is just the error result of the context cancellation
// the we exit silently.
status, ok := status.FromError(err)
if ok && status.Code() == codes.Canceled {
return
}
// Otherwise it an unexpected error, we fail the test.
t.t.Errorf("unexpected error in interceptor.Recv() %v", err)
return
}
interceptedChan <- request
}
}()
// For each test case make sure we initiate a payment from Alice to Carol
// routed through Bob. For each payment we also test its final status
// according to the interceptorAction specified in the test case.
wg.Add(1)
go func() {
defer wg.Done()
for _, tc := range testCases {
attempt, err := testContext.sendAliceToCarolPayment(
context.Background(), tc.invoice.ValueMsat, tc.invoice.RHash)
if t.t.Failed() {
return
}
if err != nil {
t.t.Errorf("failed to send payment %v", err)
}
switch tc.interceptorAction {
// For 'fail' interceptor action we make sure the payment failed.
case routerrpc.ResolveHoldForwardAction_FAIL:
if attempt.Status != lnrpc.HTLCAttempt_FAILED {
t.t.Errorf("expected payment to fail, instead got %v", attempt.Status)
}
// For settle and resume we make sure the payment is successfull.
case routerrpc.ResolveHoldForwardAction_SETTLE:
fallthrough
case routerrpc.ResolveHoldForwardAction_RESUME:
if attempt.Status != lnrpc.HTLCAttempt_SUCCEEDED {
t.t.Errorf("expected payment to succeed, instead got %v", attempt.Status)
}
}
}
}()
// We make sure here the interceptor has processed all packets before we
// check the payment statuses.
for i := 0; i < len(testCases); i++ {
select {
case request := <-interceptedChan:
testCase := testCases[i]
// For held packets we ignore, keeping them in hold status.
if testCase.shouldHold {
continue
}
// For all other packets we resolve according to the test case.
interceptor.Send(&routerrpc.ForwardHtlcInterceptResponse{
IncomingCircuitKey: request.IncomingCircuitKey,
Action: testCase.interceptorAction,
Preimage: testCase.invoice.RPreimage,
})
case <-time.After(defaultTimeout):
t.Fatalf("response from interceptor was not received %v", i)
}
}
// At this point we are left with the held packets, we want to make sure
// each one of them has a corresponding 'in-flight' payment at
// Alice's node.
payments, err := testContext.alice.ListPayments(context.Background(),
&lnrpc.ListPaymentsRequest{IncludeIncomplete: true})
if err != nil {
t.Fatalf("failed to fetch payments")
}
for _, testCase := range testCases {
if testCase.shouldHold {
hashStr := hex.EncodeToString(testCase.invoice.RHash)
var foundPayment *lnrpc.Payment
expectedAmt := testCase.invoice.ValueMsat
for _, p := range payments.Payments {
if p.PaymentHash == hashStr {
foundPayment = p
break
}
}
if foundPayment == nil {
t.Fatalf("expected to find pending payment for held"+
"htlc %v", hashStr)
}
if foundPayment.ValueMsat != expectedAmt ||
foundPayment.Status != lnrpc.Payment_IN_FLIGHT {
t.Fatalf("expected to find in flight payment for"+
"amount %v, %v", testCase.invoice.ValueMsat, foundPayment.Status)
}
}
}
// Disconnect interceptor should cause resume held packets.
// After that we wait for all go routines to finish, including the one
// that tests the payment final status for the held payment.
cancelInterceptor()
wg.Wait()
}
// interceptorTestContext is a helper struct to hold the test context and
// provide the needed functionality.
type interceptorTestContext struct {
t *harnessTest
net *lntest.NetworkHarness
// Keep a list of all our active channels.
networkChans []*lnrpc.ChannelPoint
closeChannelFuncs []func()
alice, bob, carol *lntest.HarnessNode
nodes []*lntest.HarnessNode
}
func newInterceptorTestContext(t *harnessTest,
net *lntest.NetworkHarness) *interceptorTestContext {
ctxb := context.Background()
// Create a three-node context consisting of Alice, Bob and Carol
carol, err := net.NewNode("carol", nil)
if err != nil {
t.Fatalf("unable to create carol: %v", err)
}
// Connect nodes
nodes := []*lntest.HarnessNode{net.Alice, net.Bob, carol}
for i := 0; i < len(nodes); i++ {
for j := i + 1; j < len(nodes); j++ {
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
if err := net.EnsureConnected(ctxt, nodes[i], nodes[j]); err != nil {
t.Fatalf("unable to connect nodes: %v", err)
}
}
}
ctx := interceptorTestContext{
t: t,
net: net,
alice: net.Alice,
bob: net.Bob,
carol: carol,
nodes: nodes,
}
return &ctx
}
// prepareTestCases prepares 4 tests:
// 1. failed htlc.
// 2. resumed htlc.
// 3. settling htlc externally.
// 4. held htlc that is resumed later.
func (c *interceptorTestContext) prepareTestCases() (
[]*interceptorTestCase, error) {
cases := []*interceptorTestCase{
&interceptorTestCase{amountMsat: 1000, shouldHold: false,
interceptorAction: routerrpc.ResolveHoldForwardAction_FAIL},
&interceptorTestCase{amountMsat: 1000, shouldHold: false,
interceptorAction: routerrpc.ResolveHoldForwardAction_RESUME},
&interceptorTestCase{amountMsat: 1000, shouldHold: false,
interceptorAction: routerrpc.ResolveHoldForwardAction_SETTLE},
&interceptorTestCase{amountMsat: 1000, shouldHold: true,
interceptorAction: routerrpc.ResolveHoldForwardAction_RESUME},
}
for _, t := range cases {
addResponse, err := c.carol.AddInvoice(context.Background(), &lnrpc.Invoice{
ValueMsat: t.amountMsat,
})
if err != nil {
return nil, fmt.Errorf("unable to add invoice: %v", err)
}
invoice, err := c.carol.LookupInvoice(context.Background(), &lnrpc.PaymentHash{
RHashStr: hex.EncodeToString(addResponse.RHash),
})
if err != nil {
return nil, fmt.Errorf("unable to add invoice: %v", err)
}
t.invoice = invoice
}
return cases, nil
}
func (c *interceptorTestContext) openChannel(from, to *lntest.HarnessNode, chanSize btcutil.Amount) {
ctxb := context.Background()
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
err := c.net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, from)
if err != nil {
c.t.Fatalf("unable to send coins : %v", err)
}
ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout)
chanPoint := openChannelAndAssert(
ctxt, c.t, c.net, from, to,
lntest.OpenChannelParams{
Amt: chanSize,
},
)
c.closeChannelFuncs = append(c.closeChannelFuncs, func() {
ctxt, _ := context.WithTimeout(ctxb, channelCloseTimeout)
closeChannelAndAssert(
ctxt, c.t, c.net, from, chanPoint, false,
)
})
c.networkChans = append(c.networkChans, chanPoint)
}
func (c *interceptorTestContext) closeChannels() {
for _, f := range c.closeChannelFuncs {
f()
}
}
func (c *interceptorTestContext) shutdownNodes() {
shutdownAndAssert(c.net, c.t, c.carol)
}
func (c *interceptorTestContext) waitForChannels() {
ctxb := context.Background()
// Wait for all nodes to have seen all channels.
for _, chanPoint := range c.networkChans {
for _, node := range c.nodes {
txid, err := lnd.GetChanPointFundingTxid(chanPoint)
if err != nil {
c.t.Fatalf("unable to get txid: %v", err)
}
point := wire.OutPoint{
Hash: *txid,
Index: chanPoint.OutputIndex,
}
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
err = node.WaitForNetworkChannelOpen(ctxt, chanPoint)
if err != nil {
c.t.Fatalf("(%d): timeout waiting for "+
"channel(%s) open: %v",
node.NodeID, point, err)
}
}
}
}
// sendAliceToCarolPayment sends a payment from alice to carol and make an
// attempt to pay. The lnrpc.HTLCAttempt is returned.
func (c *interceptorTestContext) sendAliceToCarolPayment(ctx context.Context,
amtMsat int64, paymentHash []byte) (*lnrpc.HTLCAttempt, error) {
// Build a route from alice to carol.
route, err := c.buildRoute(ctx, amtMsat, []*lntest.HarnessNode{c.bob, c.carol})
if err != nil {
return nil, err
}
sendReq := &routerrpc.SendToRouteRequest{
PaymentHash: paymentHash,
Route: route,
}
// Send the payment.
return c.alice.RouterClient.SendToRouteV2(ctx, sendReq)
}
// buildRoute is a helper function to build a route with given hops.
func (c *interceptorTestContext) buildRoute(ctx context.Context, amtMsat int64, hops []*lntest.HarnessNode) (
*lnrpc.Route, error) {
rpcHops := make([][]byte, 0, len(hops))
for _, hop := range hops {
k := hop.PubKeyStr
pubkey, err := route.NewVertexFromStr(k)
if err != nil {
return nil, fmt.Errorf("error parsing %v: %v",
k, err)
}
rpcHops = append(rpcHops, pubkey[:])
}
req := &routerrpc.BuildRouteRequest{
AmtMsat: amtMsat,
FinalCltvDelta: lnd.DefaultBitcoinTimeLockDelta,
HopPubkeys: rpcHops,
}
routeResp, err := c.alice.RouterClient.BuildRoute(ctx, req)
if err != nil {
return nil, err
}
return routeResp.Route, nil
}

@ -14669,6 +14669,10 @@ var testsCases = []*testCase{
name: "REST API",
test: testRestApi,
},
{
name: "intercept forwarded htlc packets",
test: testForwardInterceptor,
},
}
// TestLightningNetworkDaemon performs a series of integration tests amongst a

@ -90,12 +90,12 @@
<time> [ERR] HSWC: ChannelLink(<chan>): unhandled error while forwarding htlc packet over htlcswitch: insufficient bandwidth to route htlc
<time> [ERR] HSWC: ChannelLink(<chan>): unhandled error while forwarding htlc packet over htlcswitch: node configured to disallow forwards
<time> [ERR] HSWC: ChannelLink(<chan>): unhandled error while forwarding htlc packet over htlcswitch: UnknownNextPeer
<time> [ERR] HSWC: ChannelLink(<chan>): Unhandled error while reforwarding htlc settle/fail over htlcswitch: AmountBelowMinimum(amt=<amt>, update=(lnwire.ChannelUpdate) {
<time> [ERR] HSWC: ChannelLink(<chan>): Unhandled error while reforwarding htlc settle/fail over htlcswitch: circuit has already been closed
<time> [ERR] HSWC: ChannelLink(<chan>): Unhandled error while reforwarding htlc settle/fail over htlcswitch: FeeInsufficient(htlc_amt==<amt>, update=(lnwire.ChannelUpdate) {
<time> [ERR] HSWC: ChannelLink(<chan>): Unhandled error while reforwarding htlc settle/fail over htlcswitch: insufficient bandwidth to route htlc
<time> [ERR] HSWC: ChannelLink(<chan>): Unhandled error while reforwarding htlc settle/fail over htlcswitch: node configured to disallow forwards
<time> [ERR] HSWC: ChannelLink(<chan>): Unhandled error while reforwarding htlc settle/fail over htlcswitch: UnknownNextPeer
<time> [ERR] HSWC: Unhandled error while reforwarding htlc settle/fail over htlcswitch: AmountBelowMinimum(amt=<amt>, update=(lnwire.ChannelUpdate) {
<time> [ERR] HSWC: Unhandled error while reforwarding htlc settle/fail over htlcswitch: circuit has already been closed
<time> [ERR] HSWC: Unhandled error while reforwarding htlc settle/fail over htlcswitch: FeeInsufficient(htlc_amt==<amt>, update=(lnwire.ChannelUpdate) {
<time> [ERR] HSWC: Unhandled error while reforwarding htlc settle/fail over htlcswitch: insufficient bandwidth to route htlc
<time> [ERR] HSWC: Unhandled error while reforwarding htlc settle/fail over htlcswitch: node configured to disallow forwards
<time> [ERR] HSWC: Unhandled error while reforwarding htlc settle/fail over htlcswitch: UnknownNextPeer
<time> [ERR] HSWC: ChannelLink(<chan>): failing link: unable to synchronize channel states: unable to send chan sync message for ChannelPoint(<chan_point>): set tcp <ip>: use of closed network connection with error: unable to resume channel, recovery required
<time> [ERR] HSWC: FeeInsufficient(htlc_amt==<amt>, update=(lnwire.ChannelUpdate) {
<time> [ERR] HSWC: insufficient bandwidth to route htlc
@ -191,3 +191,4 @@
<time> [ERR] FNDG: Unable to advance state(<chan_point>): failed adding to router graph: error sending channel announcement: gossiper is shutting down
<time> [ERR] PEER: unable to close channel, ChannelID(<hex>) is unknown
<time> [ERR] HSWC: ChannelLink(<chan>): unable to update signals
<time> [ERR] RPCS: [/routerrpc.Router/HtlcInterceptor]: rpc error: code = Canceled desc = context canceled

@ -660,7 +660,7 @@ func (p *peer) addLink(chanPoint *wire.OutPoint,
Registry: p.server.invoices,
Switch: p.server.htlcSwitch,
Circuits: p.server.htlcSwitch.CircuitModifier(),
ForwardPackets: p.server.htlcSwitch.ForwardPackets,
ForwardPackets: p.server.interceptableSwitch.ForwardPackets,
FwrdingPolicy: *forwardingPolicy,
FeeEstimator: p.server.cc.feeEstimator,
PreimageCache: p.server.witnessBeacon,

@ -549,13 +549,14 @@ func newRPCServer(cfg *Config, s *server, macService *macaroons.Service,
return info.NodeKey1Bytes, info.NodeKey2Bytes, nil
},
FindRoute: s.chanRouter.FindRoute,
MissionControl: s.missionControl,
ActiveNetParams: activeNetParams.Params,
Tower: s.controlTower,
MaxTotalTimelock: cfg.MaxOutgoingCltvExpiry,
DefaultFinalCltvDelta: uint16(cfg.Bitcoin.TimeLockDelta),
SubscribeHtlcEvents: s.htlcNotifier.SubscribeHtlcEvents,
FindRoute: s.chanRouter.FindRoute,
MissionControl: s.missionControl,
ActiveNetParams: activeNetParams.Params,
Tower: s.controlTower,
MaxTotalTimelock: cfg.MaxOutgoingCltvExpiry,
DefaultFinalCltvDelta: uint16(cfg.Bitcoin.TimeLockDelta),
SubscribeHtlcEvents: s.htlcNotifier.SubscribeHtlcEvents,
InterceptableForwarder: s.interceptableSwitch,
}
genInvoiceFeatures := func() *lnwire.FeatureVector {

@ -207,6 +207,8 @@ type server struct {
htlcSwitch *htlcswitch.Switch
interceptableSwitch *htlcswitch.InterceptableSwitch
invoices *invoices.InvoiceRegistry
channelNotifier *channelnotifier.ChannelNotifier
@ -515,6 +517,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB,
if err != nil {
return nil, err
}
s.interceptableSwitch = htlcswitch.NewInterceptableSwitch(s.htlcSwitch)
chanStatusMgrCfg := &netann.ChanStatusConfig{
ChanStatusSampleInterval: cfg.ChanStatusSampleInterval,