From 4a3e90f4d06d66f86ab65d33472dc44987774776 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Mon, 15 Aug 2022 13:00:08 +0200 Subject: [PATCH 1/6] htlcswitch: add InterceptableSwitchConfig Preparation for adding more config options. --- htlcswitch/interceptable_switch.go | 25 +++++++++++++++++++------ htlcswitch/switch_test.go | 19 ++++++++++++++++--- peer/test_utils.go | 4 +++- server.go | 7 +++++-- 4 files changed, 43 insertions(+), 12 deletions(-) diff --git a/htlcswitch/interceptable_switch.go b/htlcswitch/interceptable_switch.go index fb3a28458..f4e02c712 100644 --- a/htlcswitch/interceptable_switch.go +++ b/htlcswitch/interceptable_switch.go @@ -111,19 +111,32 @@ type fwdResolution struct { errChan chan error } -// NewInterceptableSwitch returns an instance of InterceptableSwitch. -func NewInterceptableSwitch(s *Switch, cltvRejectDelta uint32, - requireInterceptor bool) *InterceptableSwitch { +// InterceptableSwitchConfig contains the configuration of InterceptableSwitch. +type InterceptableSwitchConfig struct { + // Switch is a reference to the actual switch implementation that + // packets get sent to on resume. + Switch *Switch + // CltvRejectDelta defines the number of blocks before the expiry of the + // htlc where we no longer intercept it and instead cancel it back. + CltvRejectDelta uint32 + + // RequireInterceptor indicates whether processing should block if no + // interceptor is connected. + RequireInterceptor bool +} + +// NewInterceptableSwitch returns an instance of InterceptableSwitch. +func NewInterceptableSwitch(cfg *InterceptableSwitchConfig) *InterceptableSwitch { return &InterceptableSwitch{ - htlcSwitch: s, + htlcSwitch: cfg.Switch, intercepted: make(chan *interceptedPackets), onchainIntercepted: make(chan InterceptedForward), interceptorRegistration: make(chan ForwardInterceptor), holdForwards: make(map[channeldb.CircuitKey]InterceptedForward), resolutionChan: make(chan *fwdResolution), - requireInterceptor: requireInterceptor, - cltvRejectDelta: cltvRejectDelta, + requireInterceptor: cfg.RequireInterceptor, + cltvRejectDelta: cfg.CltvRejectDelta, quit: make(chan struct{}), } diff --git a/htlcswitch/switch_test.go b/htlcswitch/switch_test.go index 61c7514ce..83389a374 100644 --- a/htlcswitch/switch_test.go +++ b/htlcswitch/switch_test.go @@ -3846,7 +3846,10 @@ func TestSwitchHoldForward(t *testing.T) { interceptedChan: make(chan InterceptedPacket), } switchForwardInterceptor := NewInterceptableSwitch( - s, cltvRejectDelta, false, + &InterceptableSwitchConfig{ + Switch: s, + CltvRejectDelta: cltvRejectDelta, + }, ) require.NoError(t, switchForwardInterceptor.Start()) @@ -4037,7 +4040,13 @@ func TestSwitchHoldForward(t *testing.T) { require.NoError(t, switchForwardInterceptor.Stop()) // Test always-on interception. - switchForwardInterceptor = NewInterceptableSwitch(s, cltvRejectDelta, true) + switchForwardInterceptor = NewInterceptableSwitch( + &InterceptableSwitchConfig{ + Switch: s, + CltvRejectDelta: cltvRejectDelta, + RequireInterceptor: true, + }, + ) require.NoError(t, switchForwardInterceptor.Start()) // Forward a fresh packet. It is expected to be failed immediately, @@ -5329,7 +5338,11 @@ func testSwitchAliasInterceptFail(t *testing.T, zeroConf bool) { t: t, interceptedChan: make(chan InterceptedPacket), } - interceptSwitch := NewInterceptableSwitch(s, 0, false) + interceptSwitch := NewInterceptableSwitch( + &InterceptableSwitchConfig{ + Switch: s, + }, + ) require.NoError(t, interceptSwitch.Start()) interceptSwitch.SetInterceptor(forwardInterceptor.InterceptForwardHtlc) diff --git a/peer/test_utils.go b/peer/test_utils.go index 938c3e51c..1bbde70b1 100644 --- a/peer/test_utils.go +++ b/peer/test_utils.go @@ -367,7 +367,9 @@ func createTestPeer(t *testing.T, notifier chainntnfs.ChainNotifier, Switch: mockSwitch, ChanActiveTimeout: chanActiveTimeout, InterceptSwitch: htlcswitch.NewInterceptableSwitch( - nil, testCltvRejectDelta, false, + &htlcswitch.InterceptableSwitchConfig{ + CltvRejectDelta: testCltvRejectDelta, + }, ), ChannelDB: dbAlice.ChannelStateDB(), FeeEstimator: estimator, diff --git a/server.go b/server.go index e3b86da6c..50b072357 100644 --- a/server.go +++ b/server.go @@ -667,8 +667,11 @@ func newServer(cfg *Config, listenAddrs []net.Addr, return nil, err } s.interceptableSwitch = htlcswitch.NewInterceptableSwitch( - s.htlcSwitch, lncfg.DefaultFinalCltvRejectDelta, - s.cfg.RequireInterceptor, + &htlcswitch.InterceptableSwitchConfig{ + Switch: s.htlcSwitch, + CltvRejectDelta: lncfg.DefaultFinalCltvRejectDelta, + RequireInterceptor: s.cfg.RequireInterceptor, + }, ) s.witnessBeacon = newPreimageBeacon( From a6df9567badd98404ea0fe534e6a5b996e95cba1 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Mon, 15 Aug 2022 15:28:23 +0200 Subject: [PATCH 2/6] htlcswitch: add dedicated block subscription to interceptable switch Preparation for making the interceptable switch aware of expiring htlcs. --- htlcswitch/interceptable_switch.go | 62 ++++++++++++++++++++++++++++-- htlcswitch/switch_test.go | 26 ++++++++++++- peer/test_utils.go | 8 ++++ server.go | 1 + 4 files changed, 92 insertions(+), 5 deletions(-) diff --git a/htlcswitch/interceptable_switch.go b/htlcswitch/interceptable_switch.go index f4e02c712..15b6a8799 100644 --- a/htlcswitch/interceptable_switch.go +++ b/htlcswitch/interceptable_switch.go @@ -6,6 +6,7 @@ import ( "sync" "github.com/go-errors/errors" + "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/htlcswitch/hop" "github.com/lightningnetwork/lnd/lntypes" @@ -20,6 +21,8 @@ var ( // ErrUnsupportedFailureCode when processing of an unsupported failure // code is attempted. ErrUnsupportedFailureCode = errors.New("unsupported failure code") + + errBlockStreamStopped = errors.New("block epoch stream stopped") ) // InterceptableSwitch is an implementation of ForwardingSwitch interface. @@ -61,6 +64,18 @@ type InterceptableSwitch struct { // htlc where we no longer intercept it and instead cancel it back. cltvRejectDelta uint32 + // notifier is an instance of a chain notifier that we'll use to signal + // the switch when a new block has arrived. + notifier chainntnfs.ChainNotifier + + // blockEpochStream is an active block epoch event stream backed by an + // active ChainNotifier instance. This will be used to retrieve the + // latest height of the chain. + blockEpochStream *chainntnfs.BlockEpochEvent + + // currentHeight is the currently best known height. + currentHeight int32 + wg sync.WaitGroup quit chan struct{} } @@ -117,6 +132,10 @@ type InterceptableSwitchConfig struct { // packets get sent to on resume. Switch *Switch + // Notifier is an instance of a chain notifier that we'll use to signal + // the switch when a new block has arrived. + Notifier chainntnfs.ChainNotifier + // CltvRejectDelta defines the number of blocks before the expiry of the // htlc where we no longer intercept it and instead cancel it back. CltvRejectDelta uint32 @@ -137,6 +156,7 @@ func NewInterceptableSwitch(cfg *InterceptableSwitchConfig) *InterceptableSwitch resolutionChan: make(chan *fwdResolution), requireInterceptor: cfg.RequireInterceptor, cltvRejectDelta: cfg.CltvRejectDelta, + notifier: cfg.Notifier, quit: make(chan struct{}), } @@ -157,11 +177,20 @@ func (s *InterceptableSwitch) SetInterceptor( } func (s *InterceptableSwitch) Start() error { + blockEpochStream, err := s.notifier.RegisterBlockEpochNtfn(nil) + if err != nil { + return err + } + s.blockEpochStream = blockEpochStream + s.wg.Add(1) go func() { defer s.wg.Done() - s.run() + err := s.run() + if err != nil { + log.Errorf("InterceptableSwitch stopped: %v", err) + } }() return nil @@ -171,10 +200,28 @@ func (s *InterceptableSwitch) Stop() error { close(s.quit) s.wg.Wait() + s.blockEpochStream.Cancel() + return nil } -func (s *InterceptableSwitch) run() { +func (s *InterceptableSwitch) run() error { + // The block epoch stream will immediately stream the current height. + // Read it out here. + select { + case currentBlock, ok := <-s.blockEpochStream.Epochs: + if !ok { + return errBlockStreamStopped + } + s.currentHeight = currentBlock.Height + + case <-s.quit: + return nil + } + + log.Debugf("InterceptableSwitch running: height=%v, "+ + "requireInterceptor=%v", s.currentHeight, s.requireInterceptor) + for { select { // An interceptor registration or de-registration came in. @@ -210,8 +257,15 @@ func (s *InterceptableSwitch) run() { case res := <-s.resolutionChan: res.errChan <- s.resolve(res.resolution) + case currentBlock, ok := <-s.blockEpochStream.Epochs: + if !ok { + return errBlockStreamStopped + } + + s.currentHeight = currentBlock.Height + case <-s.quit: - return + return nil } } } @@ -448,7 +502,7 @@ func (s *InterceptableSwitch) forward( func (s *InterceptableSwitch) handleExpired(fwd *interceptedForward) ( bool, error) { - height := s.htlcSwitch.BestHeight() + height := uint32(s.currentHeight) if fwd.packet.incomingTimeout >= height+s.cltvRejectDelta { return false, nil } diff --git a/htlcswitch/switch_test.go b/htlcswitch/switch_test.go index 83389a374..ed97fa297 100644 --- a/htlcswitch/switch_test.go +++ b/htlcswitch/switch_test.go @@ -14,10 +14,12 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/davecgh/go-spew/spew" "github.com/go-errors/errors" + "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/contractcourt" "github.com/lightningnetwork/lnd/htlcswitch/hodl" "github.com/lightningnetwork/lnd/htlcswitch/hop" + "github.com/lightningnetwork/lnd/lntest/mock" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/ticker" @@ -3750,6 +3752,8 @@ func assertOutgoingLinkReceive(t *testing.T, targetLink *mockChannelLink, func assertOutgoingLinkReceiveIntercepted(t *testing.T, targetLink *mockChannelLink) { + t.Helper() + select { case <-targetLink.packets: case <-time.After(time.Second): @@ -3845,10 +3849,17 @@ func TestSwitchHoldForward(t *testing.T) { t: t, interceptedChan: make(chan InterceptedPacket), } + + notifier := &mock.ChainNotifier{ + EpochChan: make(chan *chainntnfs.BlockEpoch, 1), + } + notifier.EpochChan <- &chainntnfs.BlockEpoch{Height: testStartingHeight} + switchForwardInterceptor := NewInterceptableSwitch( &InterceptableSwitchConfig{ Switch: s, CltvRejectDelta: cltvRejectDelta, + Notifier: notifier, }, ) require.NoError(t, switchForwardInterceptor.Start()) @@ -4040,11 +4051,17 @@ func TestSwitchHoldForward(t *testing.T) { require.NoError(t, switchForwardInterceptor.Stop()) // Test always-on interception. + notifier = &mock.ChainNotifier{ + EpochChan: make(chan *chainntnfs.BlockEpoch, 1), + } + notifier.EpochChan <- &chainntnfs.BlockEpoch{Height: testStartingHeight} + switchForwardInterceptor = NewInterceptableSwitch( &InterceptableSwitchConfig{ Switch: s, CltvRejectDelta: cltvRejectDelta, RequireInterceptor: true, + Notifier: notifier, }, ) require.NoError(t, switchForwardInterceptor.Start()) @@ -5338,9 +5355,16 @@ func testSwitchAliasInterceptFail(t *testing.T, zeroConf bool) { t: t, interceptedChan: make(chan InterceptedPacket), } + + notifier := &mock.ChainNotifier{ + EpochChan: make(chan *chainntnfs.BlockEpoch, 1), + } + notifier.EpochChan <- &chainntnfs.BlockEpoch{Height: testStartingHeight} + interceptSwitch := NewInterceptableSwitch( &InterceptableSwitchConfig{ - Switch: s, + Switch: s, + Notifier: notifier, }, ) require.NoError(t, interceptSwitch.Start()) diff --git a/peer/test_utils.go b/peer/test_utils.go index 1bbde70b1..7ba6474c8 100644 --- a/peer/test_utils.go +++ b/peer/test_utils.go @@ -359,6 +359,13 @@ func createTestPeer(t *testing.T, notifier chainntnfs.ChainNotifier, ChainNet: wire.SimNet, } + interceptableSwitchNotifier := &mock.ChainNotifier{ + EpochChan: make(chan *chainntnfs.BlockEpoch, 1), + } + interceptableSwitchNotifier.EpochChan <- &chainntnfs.BlockEpoch{ + Height: 1, + } + cfg := &Config{ Addr: cfgAddr, PubKeyBytes: pubKey, @@ -369,6 +376,7 @@ func createTestPeer(t *testing.T, notifier chainntnfs.ChainNotifier, InterceptSwitch: htlcswitch.NewInterceptableSwitch( &htlcswitch.InterceptableSwitchConfig{ CltvRejectDelta: testCltvRejectDelta, + Notifier: interceptableSwitchNotifier, }, ), ChannelDB: dbAlice.ChannelStateDB(), diff --git a/server.go b/server.go index 50b072357..1022b2ad5 100644 --- a/server.go +++ b/server.go @@ -671,6 +671,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, Switch: s.htlcSwitch, CltvRejectDelta: lncfg.DefaultFinalCltvRejectDelta, RequireInterceptor: s.cfg.RequireInterceptor, + Notifier: s.cc.ChainNotifier, }, ) From 9c063db698c82a3e0571e262c95a1d3e1a8f9365 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Mon, 15 Aug 2022 16:24:38 +0200 Subject: [PATCH 3/6] htlcswitch: add heldHtlcSet Isolation of the set logic so that it will be easier to add watchdog functionality later. --- htlcswitch/held_htlc_set.go | 75 ++++++++++++++++++++++++++++ htlcswitch/held_htlc_set_test.go | 80 ++++++++++++++++++++++++++++++ htlcswitch/interceptable_switch.go | 71 +++++++++++++++----------- 3 files changed, 196 insertions(+), 30 deletions(-) create mode 100644 htlcswitch/held_htlc_set.go create mode 100644 htlcswitch/held_htlc_set_test.go diff --git a/htlcswitch/held_htlc_set.go b/htlcswitch/held_htlc_set.go new file mode 100644 index 000000000..f7f0562cb --- /dev/null +++ b/htlcswitch/held_htlc_set.go @@ -0,0 +1,75 @@ +package htlcswitch + +import ( + "errors" + "fmt" + + "github.com/lightningnetwork/lnd/channeldb" +) + +// heldHtlcSet keeps track of outstanding intercepted forwards. It exposes +// several methods to manipulate the underlying map structure in a consistent +// way. +type heldHtlcSet struct { + set map[channeldb.CircuitKey]InterceptedForward +} + +func newHeldHtlcSet() *heldHtlcSet { + return &heldHtlcSet{ + set: make(map[channeldb.CircuitKey]InterceptedForward), + } +} + +// forEach iterates over all held forwards and calls the given callback for each +// of them. +func (h *heldHtlcSet) forEach(cb func(InterceptedForward)) { + for _, fwd := range h.set { + cb(fwd) + } +} + +// popAll calls the callback for each forward and removes them from the set. +func (h *heldHtlcSet) popAll(cb func(InterceptedForward)) { + for _, fwd := range h.set { + cb(fwd) + } + + h.set = make(map[channeldb.CircuitKey]InterceptedForward) +} + +// pop returns the specified forward and removes it from the set. +func (h *heldHtlcSet) pop(key channeldb.CircuitKey) (InterceptedForward, error) { + intercepted, ok := h.set[key] + if !ok { + return nil, fmt.Errorf("fwd %v not found", key) + } + + delete(h.set, key) + + return intercepted, nil +} + +// exists tests whether the specified forward is part of the set. +func (h *heldHtlcSet) exists(key channeldb.CircuitKey) bool { + _, ok := h.set[key] + + return ok +} + +// push adds the specified forward to the set. An error is returned if the +// forward exists already. +func (h *heldHtlcSet) push(key channeldb.CircuitKey, + fwd InterceptedForward) error { + + if fwd == nil { + return errors.New("nil fwd pushed") + } + + if h.exists(key) { + return errors.New("htlc already exists in set") + } + + h.set[key] = fwd + + return nil +} diff --git a/htlcswitch/held_htlc_set_test.go b/htlcswitch/held_htlc_set_test.go new file mode 100644 index 000000000..6a39fc5fc --- /dev/null +++ b/htlcswitch/held_htlc_set_test.go @@ -0,0 +1,80 @@ +package htlcswitch + +import ( + "testing" + + "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/stretchr/testify/require" +) + +func TestHeldHtlcSetEmpty(t *testing.T) { + set := newHeldHtlcSet() + + // Test operations on an empty set. + require.False(t, set.exists(channeldb.CircuitKey{})) + + _, err := set.pop(channeldb.CircuitKey{}) + require.Error(t, err) + + set.popAll( + func(_ InterceptedForward) { + require.Fail(t, "unexpected fwd") + }, + ) +} + +func TestHeldHtlcSet(t *testing.T) { + set := newHeldHtlcSet() + + key := channeldb.CircuitKey{ + ChanID: lnwire.NewShortChanIDFromInt(1), + HtlcID: 2, + } + + // Test pushing a nil forward. + require.Error(t, set.push(key, nil)) + + // Test pushing a forward. + fwd := &interceptedForward{ + htlc: &lnwire.UpdateAddHTLC{}, + } + require.NoError(t, set.push(key, fwd)) + + // Re-pushing should fail. + require.Error(t, set.push(key, fwd)) + + // Test popping the fwd. + poppedFwd, err := set.pop(key) + require.NoError(t, err) + require.Equal(t, fwd, poppedFwd) + + _, err = set.pop(key) + require.Error(t, err) + + // Pushing the forward again. + require.NoError(t, set.push(key, fwd)) + + // Test for each. + var cbCalled bool + set.forEach(func(_ InterceptedForward) { + cbCalled = true + + require.Equal(t, fwd, poppedFwd) + }) + require.True(t, cbCalled) + + // Test popping all forwards. + cbCalled = false + set.popAll( + func(_ InterceptedForward) { + cbCalled = true + + require.Equal(t, fwd, poppedFwd) + }, + ) + require.True(t, cbCalled) + + _, err = set.pop(key) + require.Error(t, err) +} diff --git a/htlcswitch/interceptable_switch.go b/htlcswitch/interceptable_switch.go index 15b6a8799..9675c3a4b 100644 --- a/htlcswitch/interceptable_switch.go +++ b/htlcswitch/interceptable_switch.go @@ -57,8 +57,8 @@ type InterceptableSwitch struct { // interceptor is the handler for intercepted packets. interceptor ForwardInterceptor - // holdForwards keeps track of outstanding intercepted forwards. - holdForwards map[channeldb.CircuitKey]InterceptedForward + // heldHtlcSet keeps track of outstanding intercepted forwards. + heldHtlcSet *heldHtlcSet // cltvRejectDelta defines the number of blocks before the expiry of the // htlc where we no longer intercept it and instead cancel it back. @@ -152,7 +152,7 @@ func NewInterceptableSwitch(cfg *InterceptableSwitchConfig) *InterceptableSwitch intercepted: make(chan *interceptedPackets), onchainIntercepted: make(chan InterceptedForward), interceptorRegistration: make(chan ForwardInterceptor), - holdForwards: make(map[channeldb.CircuitKey]InterceptedForward), + heldHtlcSet: newHeldHtlcSet(), resolutionChan: make(chan *fwdResolution), requireInterceptor: cfg.RequireInterceptor, cltvRejectDelta: cfg.CltvRejectDelta, @@ -231,7 +231,14 @@ func (s *InterceptableSwitch) run() error { case packets := <-s.intercepted: var notIntercepted []*htlcPacket for _, p := range packets.packets { - if !s.interceptForward(p, packets.isReplay) { + intercepted, err := s.interceptForward( + p, packets.isReplay, + ) + if err != nil { + return err + } + + if !intercepted { notIntercepted = append( notIntercepted, p, ) @@ -252,7 +259,9 @@ func (s *InterceptableSwitch) run() error { // already intercepted in the off-chain flow. And even // if not, it is safe to signal replay so that we won't // unexpectedly skip over this htlc. - s.forward(fwd, true) + if _, err := s.forward(fwd, true); err != nil { + return err + } case res := <-s.resolutionChan: res.errChan <- s.resolve(res.resolution) @@ -287,9 +296,7 @@ func (s *InterceptableSwitch) setInterceptor(interceptor ForwardInterceptor) { if interceptor != nil { log.Debugf("Interceptor connected") - for _, fwd := range s.holdForwards { - s.sendForward(fwd) - } + s.heldHtlcSet.forEach(s.sendForward) return } @@ -305,20 +312,19 @@ func (s *InterceptableSwitch) setInterceptor(interceptor ForwardInterceptor) { // Interceptor is not required. Release held forwards. log.Infof("Interceptor disconnected, resolving held packets") - for _, fwd := range s.holdForwards { - if err := fwd.Resume(); err != nil { + s.heldHtlcSet.popAll(func(fwd InterceptedForward) { + err := fwd.Resume() + if err != nil { log.Errorf("Failed to resume hold forward %v", err) } - } - s.holdForwards = make(map[channeldb.CircuitKey]InterceptedForward) + }) } func (s *InterceptableSwitch) resolve(res *FwdResolution) error { - intercepted, ok := s.holdForwards[res.Key] - if !ok { - return fmt.Errorf("fwd %v not found", res.Key) + intercepted, err := s.heldHtlcSet.pop(res.Key) + if err != nil { + return err } - delete(s.holdForwards, res.Key) switch res.Action { case FwdActionResume: @@ -405,13 +411,13 @@ func (s *InterceptableSwitch) ForwardPacket( // interceptForward forwards the packet to the external interceptor after // checking the interception criteria. func (s *InterceptableSwitch) interceptForward(packet *htlcPacket, - isReplay bool) bool { + isReplay bool) (bool, error) { switch htlc := packet.htlc.(type) { case *lnwire.UpdateAddHTLC: // We are not interested in intercepting initiated payments. if packet.incomingChanID == hop.Source { - return false + return false, nil } intercepted := &interceptedForward{ @@ -435,28 +441,28 @@ func (s *InterceptableSwitch) interceptForward(packet *htlcPacket, // will remain stuck and potentially force-close the // channel. But in the end, we should never get here, so // the actual return value doesn't matter that much. - return false + return false, nil } if handled { - return true + return true, nil } return s.forward(intercepted, isReplay) default: - return false + return false, nil } } // forward records the intercepted htlc and forwards it to the interceptor. func (s *InterceptableSwitch) forward( - fwd InterceptedForward, isReplay bool) bool { + fwd InterceptedForward, isReplay bool) (bool, error) { inKey := fwd.Packet().IncomingCircuit // Ignore already held htlcs. - if _, ok := s.holdForwards[inKey]; ok { - return true + if s.heldHtlcSet.exists(inKey) { + return true, nil } // If there is no interceptor currently registered, configuration and packet @@ -464,7 +470,7 @@ func (s *InterceptableSwitch) forward( if s.interceptor == nil { // Process normally if an interceptor is not required. if !s.requireInterceptor { - return false + return false, nil } // We are in interceptor-required mode. If this is a new packet, it is @@ -478,23 +484,28 @@ func (s *InterceptableSwitch) forward( log.Errorf("Cannot fail packet: %v", err) } - return true + return true, nil } // This packet is a replay. It is not safe to fail back, because the // interceptor may still signal otherwise upon reconnect. Keep the // packet in the queue until then. - s.holdForwards[inKey] = fwd + if err := s.heldHtlcSet.push(inKey, fwd); err != nil { + return false, err + } - return true + return true, nil } // There is an interceptor registered. We can forward the packet right now. // Hold it in the queue too to track what is outstanding. - s.holdForwards[inKey] = fwd + if err := s.heldHtlcSet.push(inKey, fwd); err != nil { + return false, err + } + s.sendForward(fwd) - return true + return true, nil } // handleExpired checks that the htlc isn't too close to the channel From 93a7cab46e7eac1be5f1f15e7742f8674ff039c6 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 16 Aug 2022 14:04:22 +0200 Subject: [PATCH 4/6] htlcswitch/test: create interceptableSwitchTestContext Refactor to prepare for adding more tests. --- htlcswitch/switch_test.go | 294 +++++++++++++++++++++----------------- 1 file changed, 162 insertions(+), 132 deletions(-) diff --git a/htlcswitch/switch_test.go b/htlcswitch/switch_test.go index ed97fa297..27ebaa471 100644 --- a/htlcswitch/switch_test.go +++ b/htlcswitch/switch_test.go @@ -3761,8 +3761,23 @@ func assertOutgoingLinkReceiveIntercepted(t *testing.T, } } -func TestSwitchHoldForward(t *testing.T) { - t.Parallel() +type interceptableSwitchTestContext struct { + t *testing.T + + preimage [sha256.Size]byte + rhash [32]byte + onionBlob [1366]byte + incomingHtlcID uint64 + cltvRejectDelta uint32 + + forwardInterceptor *mockForwardInterceptor + aliceChannelLink *mockChannelLink + bobChannelLink *mockChannelLink + s *Switch +} + +func newInterceptableSwitchTestContext( + t *testing.T) *interceptableSwitchTestContext { chanID1, chanID2, aliceChanID, bobChanID := genIDs() @@ -3787,12 +3802,6 @@ func TestSwitchHoldForward(t *testing.T) { 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, emptyScid, alicePeer, true, false, false, false, @@ -3808,48 +3817,69 @@ func TestSwitchHoldForward(t *testing.T) { 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 := sha256.Sum256(preimage[:]) - onionBlob := [1366]byte{4, 5, 6} - incomingHtlcID := uint64(0) - const cltvRejectDelta = 13 - - createTestPacket := func() *htlcPacket { - incomingHtlcID++ - - return &htlcPacket{ - incomingChanID: aliceChannelLink.ShortChanID(), - incomingHTLCID: incomingHtlcID, - incomingTimeout: testStartingHeight + cltvRejectDelta + 1, - outgoingChanID: bobChannelLink.ShortChanID(), - obfuscator: NewMockObfuscator(), - htlc: &lnwire.UpdateAddHTLC{ - PaymentHash: rhash, - Amount: 1, - OnionBlob: onionBlob, - }, - } - } - - createSettlePacket := func(outgoingHTLCID uint64) *htlcPacket { - return &htlcPacket{ - outgoingChanID: bobChannelLink.ShortChanID(), - outgoingHTLCID: outgoingHTLCID, - amount: 1, - htlc: &lnwire.UpdateFulfillHTLC{ - PaymentPreimage: preimage, - }, - } - } - - forwardInterceptor := &mockForwardInterceptor{ + ctx := &interceptableSwitchTestContext{ t: t, - interceptedChan: make(chan InterceptedPacket), + preimage: preimage, + rhash: sha256.Sum256(preimage[:]), + onionBlob: [1366]byte{4, 5, 6}, + incomingHtlcID: uint64(0), + cltvRejectDelta: 13, + forwardInterceptor: &mockForwardInterceptor{ + t: t, + interceptedChan: make(chan InterceptedPacket), + }, + aliceChannelLink: aliceChannelLink, + bobChannelLink: bobChannelLink, + s: s, } + return ctx +} + +func (c *interceptableSwitchTestContext) createTestPacket() *htlcPacket { + c.incomingHtlcID++ + + return &htlcPacket{ + incomingChanID: c.aliceChannelLink.ShortChanID(), + incomingHTLCID: c.incomingHtlcID, + incomingTimeout: testStartingHeight + c.cltvRejectDelta + 1, + outgoingChanID: c.bobChannelLink.ShortChanID(), + obfuscator: NewMockObfuscator(), + htlc: &lnwire.UpdateAddHTLC{ + PaymentHash: c.rhash, + Amount: 1, + OnionBlob: c.onionBlob, + }, + } +} + +func (c *interceptableSwitchTestContext) finish() { + if err := c.s.Stop(); err != nil { + c.t.Fatalf(err.Error()) + } +} + +func (c *interceptableSwitchTestContext) createSettlePacket( + outgoingHTLCID uint64) *htlcPacket { + + return &htlcPacket{ + outgoingChanID: c.bobChannelLink.ShortChanID(), + outgoingHTLCID: outgoingHTLCID, + amount: 1, + htlc: &lnwire.UpdateFulfillHTLC{ + PaymentPreimage: c.preimage, + }, + } +} + +func TestSwitchHoldForward(t *testing.T) { + t.Parallel() + + c := newInterceptableSwitchTestContext(t) + defer c.finish() + notifier := &mock.ChainNotifier{ EpochChan: make(chan *chainntnfs.BlockEpoch, 1), } @@ -3857,33 +3887,33 @@ func TestSwitchHoldForward(t *testing.T) { switchForwardInterceptor := NewInterceptableSwitch( &InterceptableSwitchConfig{ - Switch: s, - CltvRejectDelta: cltvRejectDelta, + Switch: c.s, + CltvRejectDelta: c.cltvRejectDelta, Notifier: notifier, }, ) require.NoError(t, switchForwardInterceptor.Start()) - switchForwardInterceptor.SetInterceptor(forwardInterceptor.InterceptForwardHtlc) + switchForwardInterceptor.SetInterceptor(c.forwardInterceptor.InterceptForwardHtlc) linkQuit := make(chan struct{}) // Test a forward that expires too soon. - packet := createTestPacket() - packet.incomingTimeout = testStartingHeight + cltvRejectDelta - 1 + packet := c.createTestPacket() + packet.incomingTimeout = testStartingHeight + c.cltvRejectDelta - 1 - err = switchForwardInterceptor.ForwardPackets(linkQuit, false, packet) + err := switchForwardInterceptor.ForwardPackets(linkQuit, false, packet) require.NoError(t, err, "can't forward htlc packet") - assertOutgoingLinkReceive(t, bobChannelLink, false) - assertOutgoingLinkReceiveIntercepted(t, aliceChannelLink) - assertNumCircuits(t, s, 0, 0) + assertOutgoingLinkReceive(t, c.bobChannelLink, false) + assertOutgoingLinkReceiveIntercepted(t, c.aliceChannelLink) + assertNumCircuits(t, c.s, 0, 0) // Test a forward that expires too soon and can't be failed. - packet = createTestPacket() - packet.incomingTimeout = testStartingHeight + cltvRejectDelta - 1 + packet = c.createTestPacket() + packet.incomingTimeout = testStartingHeight + c.cltvRejectDelta - 1 // Simulate an error during the composition of the failure message. - currentCallback := s.cfg.FetchLastChannelUpdate - s.cfg.FetchLastChannelUpdate = func( + currentCallback := c.s.cfg.FetchLastChannelUpdate + c.s.cfg.FetchLastChannelUpdate = func( lnwire.ShortChannelID) (*lnwire.ChannelUpdate, error) { return nil, errors.New("cannot fetch update") @@ -3891,137 +3921,137 @@ func TestSwitchHoldForward(t *testing.T) { err = switchForwardInterceptor.ForwardPackets(linkQuit, false, packet) require.NoError(t, err, "can't forward htlc packet") - receivedPkt := assertOutgoingLinkReceive(t, bobChannelLink, true) - assertNumCircuits(t, s, 1, 1) + receivedPkt := assertOutgoingLinkReceive(t, c.bobChannelLink, true) + assertNumCircuits(t, c.s, 1, 1) require.NoError(t, switchForwardInterceptor.ForwardPackets( linkQuit, false, - createSettlePacket(receivedPkt.outgoingHTLCID), + c.createSettlePacket(receivedPkt.outgoingHTLCID), )) - assertOutgoingLinkReceive(t, aliceChannelLink, true) - assertNumCircuits(t, s, 0, 0) + assertOutgoingLinkReceive(t, c.aliceChannelLink, true) + assertNumCircuits(t, c.s, 0, 0) - s.cfg.FetchLastChannelUpdate = currentCallback + c.s.cfg.FetchLastChannelUpdate = currentCallback // Test resume a hold forward. - assertNumCircuits(t, s, 0, 0) + assertNumCircuits(t, c.s, 0, 0) err = switchForwardInterceptor.ForwardPackets( - linkQuit, false, createTestPacket(), + linkQuit, false, c.createTestPacket(), ) require.NoError(t, err) - assertNumCircuits(t, s, 0, 0) - assertOutgoingLinkReceive(t, bobChannelLink, false) + assertNumCircuits(t, c.s, 0, 0) + assertOutgoingLinkReceive(t, c.bobChannelLink, false) require.NoError(t, switchForwardInterceptor.Resolve(&FwdResolution{ Action: FwdActionResume, - Key: forwardInterceptor.getIntercepted().IncomingCircuit, + Key: c.forwardInterceptor.getIntercepted().IncomingCircuit, })) - receivedPkt = assertOutgoingLinkReceive(t, bobChannelLink, true) - assertNumCircuits(t, s, 1, 1) + receivedPkt = assertOutgoingLinkReceive(t, c.bobChannelLink, true) + assertNumCircuits(t, c.s, 1, 1) // settling the htlc to close the circuit. err = switchForwardInterceptor.ForwardPackets( linkQuit, false, - createSettlePacket(receivedPkt.outgoingHTLCID), + c.createSettlePacket(receivedPkt.outgoingHTLCID), ) require.NoError(t, err) - assertOutgoingLinkReceive(t, aliceChannelLink, true) - assertNumCircuits(t, s, 0, 0) + assertOutgoingLinkReceive(t, c.aliceChannelLink, true) + assertNumCircuits(t, c.s, 0, 0) // Test resume a hold forward after disconnection. require.NoError(t, switchForwardInterceptor.ForwardPackets( - linkQuit, false, createTestPacket(), + linkQuit, false, c.createTestPacket(), )) // Wait until the packet is offered to the interceptor. - _ = forwardInterceptor.getIntercepted() + _ = c.forwardInterceptor.getIntercepted() // No forward expected yet. - assertNumCircuits(t, s, 0, 0) - assertOutgoingLinkReceive(t, bobChannelLink, false) + assertNumCircuits(t, c.s, 0, 0) + assertOutgoingLinkReceive(t, c.bobChannelLink, false) // Disconnect should resume the forwarding. switchForwardInterceptor.SetInterceptor(nil) - receivedPkt = assertOutgoingLinkReceive(t, bobChannelLink, true) - assertNumCircuits(t, s, 1, 1) + receivedPkt = assertOutgoingLinkReceive(t, c.bobChannelLink, true) + assertNumCircuits(t, c.s, 1, 1) // Settle the htlc to close the circuit. require.NoError(t, switchForwardInterceptor.ForwardPackets( linkQuit, false, - createSettlePacket(receivedPkt.outgoingHTLCID), + c.createSettlePacket(receivedPkt.outgoingHTLCID), )) - assertOutgoingLinkReceive(t, aliceChannelLink, true) - assertNumCircuits(t, s, 0, 0) + assertOutgoingLinkReceive(t, c.aliceChannelLink, true) + assertNumCircuits(t, c.s, 0, 0) // Test failing a hold forward switchForwardInterceptor.SetInterceptor( - forwardInterceptor.InterceptForwardHtlc, + c.forwardInterceptor.InterceptForwardHtlc, ) require.NoError(t, switchForwardInterceptor.ForwardPackets( - linkQuit, false, createTestPacket(), + linkQuit, false, c.createTestPacket(), )) - assertNumCircuits(t, s, 0, 0) - assertOutgoingLinkReceive(t, bobChannelLink, false) + assertNumCircuits(t, c.s, 0, 0) + assertOutgoingLinkReceive(t, c.bobChannelLink, false) require.NoError(t, switchForwardInterceptor.Resolve(&FwdResolution{ Action: FwdActionFail, - Key: forwardInterceptor.getIntercepted().IncomingCircuit, + Key: c.forwardInterceptor.getIntercepted().IncomingCircuit, FailureCode: lnwire.CodeTemporaryChannelFailure, })) - assertOutgoingLinkReceive(t, bobChannelLink, false) - assertOutgoingLinkReceive(t, aliceChannelLink, true) - assertNumCircuits(t, s, 0, 0) + assertOutgoingLinkReceive(t, c.bobChannelLink, false) + assertOutgoingLinkReceive(t, c.aliceChannelLink, true) + assertNumCircuits(t, c.s, 0, 0) // Test failing a hold forward with a failure message. require.NoError(t, switchForwardInterceptor.ForwardPackets( - linkQuit, false, createTestPacket(), + linkQuit, false, c.createTestPacket(), ), ) - assertNumCircuits(t, s, 0, 0) - assertOutgoingLinkReceive(t, bobChannelLink, false) + assertNumCircuits(t, c.s, 0, 0) + assertOutgoingLinkReceive(t, c.bobChannelLink, false) reason := lnwire.OpaqueReason([]byte{1, 2, 3}) require.NoError(t, switchForwardInterceptor.Resolve(&FwdResolution{ Action: FwdActionFail, - Key: forwardInterceptor.getIntercepted().IncomingCircuit, + Key: c.forwardInterceptor.getIntercepted().IncomingCircuit, FailureMessage: reason, })) - assertOutgoingLinkReceive(t, bobChannelLink, false) - packet = assertOutgoingLinkReceive(t, aliceChannelLink, true) + assertOutgoingLinkReceive(t, c.bobChannelLink, false) + packet = assertOutgoingLinkReceive(t, c.aliceChannelLink, true) require.Equal(t, reason, packet.htlc.(*lnwire.UpdateFailHTLC).Reason) - assertNumCircuits(t, s, 0, 0) + assertNumCircuits(t, c.s, 0, 0) // Test failing a hold forward with a malformed htlc failure. err = switchForwardInterceptor.ForwardPackets( - linkQuit, false, createTestPacket(), + linkQuit, false, c.createTestPacket(), ) require.NoError(t, err) - assertNumCircuits(t, s, 0, 0) - assertOutgoingLinkReceive(t, bobChannelLink, false) + assertNumCircuits(t, c.s, 0, 0) + assertOutgoingLinkReceive(t, c.bobChannelLink, false) code := lnwire.CodeInvalidOnionKey require.NoError(t, switchForwardInterceptor.Resolve(&FwdResolution{ Action: FwdActionFail, - Key: forwardInterceptor.getIntercepted().IncomingCircuit, + Key: c.forwardInterceptor.getIntercepted().IncomingCircuit, FailureCode: code, })) - assertOutgoingLinkReceive(t, bobChannelLink, false) - packet = assertOutgoingLinkReceive(t, aliceChannelLink, true) + assertOutgoingLinkReceive(t, c.bobChannelLink, false) + packet = assertOutgoingLinkReceive(t, c.aliceChannelLink, true) failPacket := packet.htlc.(*lnwire.UpdateFailHTLC) - shaOnionBlob := sha256.Sum256(onionBlob[:]) + shaOnionBlob := sha256.Sum256(c.onionBlob[:]) expectedFailure := &lnwire.FailInvalidOnionKey{ OnionSHA256: shaOnionBlob, } @@ -4030,23 +4060,23 @@ func TestSwitchHoldForward(t *testing.T) { assert.Equal(t, lnwire.OpaqueReason(b.Bytes()), failPacket.Reason) - assertNumCircuits(t, s, 0, 0) + assertNumCircuits(t, c.s, 0, 0) // Test settling a hold forward require.NoError(t, switchForwardInterceptor.ForwardPackets( - linkQuit, false, createTestPacket(), + linkQuit, false, c.createTestPacket(), )) - assertNumCircuits(t, s, 0, 0) - assertOutgoingLinkReceive(t, bobChannelLink, false) + assertNumCircuits(t, c.s, 0, 0) + assertOutgoingLinkReceive(t, c.bobChannelLink, false) require.NoError(t, switchForwardInterceptor.Resolve(&FwdResolution{ - Key: forwardInterceptor.getIntercepted().IncomingCircuit, + Key: c.forwardInterceptor.getIntercepted().IncomingCircuit, Action: FwdActionSettle, - Preimage: preimage, + Preimage: c.preimage, })) - assertOutgoingLinkReceive(t, bobChannelLink, false) - assertOutgoingLinkReceive(t, aliceChannelLink, true) - assertNumCircuits(t, s, 0, 0) + assertOutgoingLinkReceive(t, c.bobChannelLink, false) + assertOutgoingLinkReceive(t, c.aliceChannelLink, true) + assertNumCircuits(t, c.s, 0, 0) require.NoError(t, switchForwardInterceptor.Stop()) @@ -4058,8 +4088,8 @@ func TestSwitchHoldForward(t *testing.T) { switchForwardInterceptor = NewInterceptableSwitch( &InterceptableSwitchConfig{ - Switch: s, - CltvRejectDelta: cltvRejectDelta, + Switch: c.s, + CltvRejectDelta: c.cltvRejectDelta, RequireInterceptor: true, Notifier: notifier, }, @@ -4069,12 +4099,12 @@ func TestSwitchHoldForward(t *testing.T) { // Forward a fresh packet. It is expected to be failed immediately, // because there is no interceptor registered. require.NoError(t, switchForwardInterceptor.ForwardPackets( - linkQuit, false, createTestPacket(), + linkQuit, false, c.createTestPacket(), )) - assertOutgoingLinkReceive(t, bobChannelLink, false) - assertOutgoingLinkReceive(t, aliceChannelLink, true) - assertNumCircuits(t, s, 0, 0) + assertOutgoingLinkReceive(t, c.bobChannelLink, false) + assertOutgoingLinkReceive(t, c.aliceChannelLink, true) + assertNumCircuits(t, c.s, 0, 0) // Forward a replayed packet. It is expected to be held until the // interceptor connects. To continue the test, it needs to be ran in a @@ -4082,48 +4112,48 @@ func TestSwitchHoldForward(t *testing.T) { errChan := make(chan error) go func() { errChan <- switchForwardInterceptor.ForwardPackets( - linkQuit, true, createTestPacket(), + linkQuit, true, c.createTestPacket(), ) }() // Assert that nothing is forward to the switch. - assertOutgoingLinkReceive(t, bobChannelLink, false) - assertNumCircuits(t, s, 0, 0) + assertOutgoingLinkReceive(t, c.bobChannelLink, false) + assertNumCircuits(t, c.s, 0, 0) // Register an interceptor. switchForwardInterceptor.SetInterceptor( - forwardInterceptor.InterceptForwardHtlc, + c.forwardInterceptor.InterceptForwardHtlc, ) // Expect the ForwardPackets call to unblock. require.NoError(t, <-errChan) // Now expect the queued packet to come through. - forwardInterceptor.getIntercepted() + c.forwardInterceptor.getIntercepted() // Disconnect and reconnect interceptor. switchForwardInterceptor.SetInterceptor(nil) switchForwardInterceptor.SetInterceptor( - forwardInterceptor.InterceptForwardHtlc, + c.forwardInterceptor.InterceptForwardHtlc, ) // A replay of the held packet is expected. - intercepted := forwardInterceptor.getIntercepted() + intercepted := c.forwardInterceptor.getIntercepted() // Settle the packet. require.NoError(t, switchForwardInterceptor.Resolve(&FwdResolution{ Key: intercepted.IncomingCircuit, Action: FwdActionSettle, - Preimage: preimage, + Preimage: c.preimage, })) - assertOutgoingLinkReceive(t, bobChannelLink, false) - assertOutgoingLinkReceive(t, aliceChannelLink, true) - assertNumCircuits(t, s, 0, 0) + assertOutgoingLinkReceive(t, c.bobChannelLink, false) + assertOutgoingLinkReceive(t, c.aliceChannelLink, true) + assertNumCircuits(t, c.s, 0, 0) require.NoError(t, switchForwardInterceptor.Stop()) select { - case <-forwardInterceptor.interceptedChan: + case <-c.forwardInterceptor.interceptedChan: require.Fail(t, "unexpected interception") default: From 74eeb95e8c584cab3a09c937357a6d21c701ccaf Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Fri, 14 Oct 2022 14:25:25 +0200 Subject: [PATCH 5/6] htlcswitch: add error return value to NewInterceptableSwitch Prepares for parameter validation. --- htlcswitch/interceptable_switch.go | 6 ++++-- htlcswitch/switch_test.go | 11 +++++++---- peer/test_utils.go | 31 +++++++++++++++++------------- server.go | 5 ++++- 4 files changed, 33 insertions(+), 20 deletions(-) diff --git a/htlcswitch/interceptable_switch.go b/htlcswitch/interceptable_switch.go index 9675c3a4b..dd8e69239 100644 --- a/htlcswitch/interceptable_switch.go +++ b/htlcswitch/interceptable_switch.go @@ -146,7 +146,9 @@ type InterceptableSwitchConfig struct { } // NewInterceptableSwitch returns an instance of InterceptableSwitch. -func NewInterceptableSwitch(cfg *InterceptableSwitchConfig) *InterceptableSwitch { +func NewInterceptableSwitch(cfg *InterceptableSwitchConfig) ( + *InterceptableSwitch, error) { + return &InterceptableSwitch{ htlcSwitch: cfg.Switch, intercepted: make(chan *interceptedPackets), @@ -159,7 +161,7 @@ func NewInterceptableSwitch(cfg *InterceptableSwitchConfig) *InterceptableSwitch notifier: cfg.Notifier, quit: make(chan struct{}), - } + }, nil } // SetInterceptor sets the ForwardInterceptor to be used. A nil argument diff --git a/htlcswitch/switch_test.go b/htlcswitch/switch_test.go index 27ebaa471..6a3d6fdb9 100644 --- a/htlcswitch/switch_test.go +++ b/htlcswitch/switch_test.go @@ -3885,13 +3885,14 @@ func TestSwitchHoldForward(t *testing.T) { } notifier.EpochChan <- &chainntnfs.BlockEpoch{Height: testStartingHeight} - switchForwardInterceptor := NewInterceptableSwitch( + switchForwardInterceptor, err := NewInterceptableSwitch( &InterceptableSwitchConfig{ Switch: c.s, CltvRejectDelta: c.cltvRejectDelta, Notifier: notifier, }, ) + require.NoError(t, err) require.NoError(t, switchForwardInterceptor.Start()) switchForwardInterceptor.SetInterceptor(c.forwardInterceptor.InterceptForwardHtlc) @@ -3901,7 +3902,7 @@ func TestSwitchHoldForward(t *testing.T) { packet := c.createTestPacket() packet.incomingTimeout = testStartingHeight + c.cltvRejectDelta - 1 - err := switchForwardInterceptor.ForwardPackets(linkQuit, false, packet) + err = switchForwardInterceptor.ForwardPackets(linkQuit, false, packet) require.NoError(t, err, "can't forward htlc packet") assertOutgoingLinkReceive(t, c.bobChannelLink, false) assertOutgoingLinkReceiveIntercepted(t, c.aliceChannelLink) @@ -4086,7 +4087,7 @@ func TestSwitchHoldForward(t *testing.T) { } notifier.EpochChan <- &chainntnfs.BlockEpoch{Height: testStartingHeight} - switchForwardInterceptor = NewInterceptableSwitch( + switchForwardInterceptor, err = NewInterceptableSwitch( &InterceptableSwitchConfig{ Switch: c.s, CltvRejectDelta: c.cltvRejectDelta, @@ -4094,6 +4095,7 @@ func TestSwitchHoldForward(t *testing.T) { Notifier: notifier, }, ) + require.NoError(t, err) require.NoError(t, switchForwardInterceptor.Start()) // Forward a fresh packet. It is expected to be failed immediately, @@ -5391,12 +5393,13 @@ func testSwitchAliasInterceptFail(t *testing.T, zeroConf bool) { } notifier.EpochChan <- &chainntnfs.BlockEpoch{Height: testStartingHeight} - interceptSwitch := NewInterceptableSwitch( + interceptSwitch, err := NewInterceptableSwitch( &InterceptableSwitchConfig{ Switch: s, Notifier: notifier, }, ) + require.NoError(t, err) require.NoError(t, interceptSwitch.Start()) interceptSwitch.SetInterceptor(forwardInterceptor.InterceptForwardHtlc) diff --git a/peer/test_utils.go b/peer/test_utils.go index 7ba6474c8..723a439bd 100644 --- a/peer/test_utils.go +++ b/peer/test_utils.go @@ -366,6 +366,16 @@ func createTestPeer(t *testing.T, notifier chainntnfs.ChainNotifier, Height: 1, } + interceptableSwitch, err := htlcswitch.NewInterceptableSwitch( + &htlcswitch.InterceptableSwitchConfig{ + CltvRejectDelta: testCltvRejectDelta, + Notifier: interceptableSwitchNotifier, + }, + ) + if err != nil { + return nil, nil, err + } + cfg := &Config{ Addr: cfgAddr, PubKeyBytes: pubKey, @@ -373,19 +383,14 @@ func createTestPeer(t *testing.T, notifier chainntnfs.ChainNotifier, ChainIO: chainIO, Switch: mockSwitch, ChanActiveTimeout: chanActiveTimeout, - InterceptSwitch: htlcswitch.NewInterceptableSwitch( - &htlcswitch.InterceptableSwitchConfig{ - CltvRejectDelta: testCltvRejectDelta, - Notifier: interceptableSwitchNotifier, - }, - ), - ChannelDB: dbAlice.ChannelStateDB(), - FeeEstimator: estimator, - Wallet: wallet, - ChainNotifier: notifier, - ChanStatusMgr: chanStatusMgr, - Features: lnwire.NewFeatureVector(nil, lnwire.Features), - DisconnectPeer: func(b *btcec.PublicKey) error { return nil }, + InterceptSwitch: interceptableSwitch, + ChannelDB: dbAlice.ChannelStateDB(), + FeeEstimator: estimator, + Wallet: wallet, + ChainNotifier: notifier, + ChanStatusMgr: chanStatusMgr, + Features: lnwire.NewFeatureVector(nil, lnwire.Features), + DisconnectPeer: func(b *btcec.PublicKey) error { return nil }, } alicePeer := NewBrontide(*cfg) diff --git a/server.go b/server.go index 1022b2ad5..372e349ce 100644 --- a/server.go +++ b/server.go @@ -666,7 +666,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, if err != nil { return nil, err } - s.interceptableSwitch = htlcswitch.NewInterceptableSwitch( + s.interceptableSwitch, err = htlcswitch.NewInterceptableSwitch( &htlcswitch.InterceptableSwitchConfig{ Switch: s.htlcSwitch, CltvRejectDelta: lncfg.DefaultFinalCltvRejectDelta, @@ -674,6 +674,9 @@ func newServer(cfg *Config, listenAddrs []net.Addr, Notifier: s.cc.ChainNotifier, }, ) + if err != nil { + return nil, err + } s.witnessBeacon = newPreimageBeacon( dbs.ChanStateDB.NewWitnessCache(), From a0a50fa844d8d1ccca02972628031ac0aafcd42d Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Mon, 15 Aug 2022 17:28:19 +0200 Subject: [PATCH 6/6] htlcswitch: auto-fail held htlcs Make the interceptable switch aware of htlc expiry and fail back htlcs in-time to prevent the counterparty from force-closing the channel. --- docs/release-notes/release-notes-0.16.0.md | 10 +- htlcswitch/held_htlc_set.go | 14 + htlcswitch/held_htlc_set_test.go | 46 +++ htlcswitch/interceptable_switch.go | 55 ++- htlcswitch/interfaces.go | 4 + htlcswitch/switch_test.go | 103 +++++- lncfg/config.go | 7 + lnrpc/routerrpc/forward_interceptor.go | 1 + lnrpc/routerrpc/router.pb.go | 386 +++++++++++---------- lnrpc/routerrpc/router.proto | 4 + lnrpc/routerrpc/router.swagger.json | 5 + peer/test_utils.go | 5 +- server.go | 1 + 13 files changed, 429 insertions(+), 212 deletions(-) diff --git a/docs/release-notes/release-notes-0.16.0.md b/docs/release-notes/release-notes-0.16.0.md index 4c3e5439f..48ec8b807 100644 --- a/docs/release-notes/release-notes-0.16.0.md +++ b/docs/release-notes/release-notes-0.16.0.md @@ -28,7 +28,14 @@ Similar to TrackPaymentV2, but for any inflight payment. * [Catch and throw an error](https://github.com/lightningnetwork/lnd/pull/6945) - during `openchannel` if the local funding amount given is zero. + during `openchannel` if the local funding amount given is zero. + +* [Extend](https://github.com/lightningnetwork/lnd/pull/6831) the HTLC + interceptor server implementation with watchdog functionality to cancel back + HTLCs for which an interceptor client does not provide a resolution in time. + If an HTLC expires, the counterparty will claim it back on-chain and the + receiver will lose it. Therefore the receiver can just as well fail off-chain + a few blocks before so that the channel is saved. * [Make remote channel reserve amount configurable for `openchannel`](https://github.com/lightningnetwork/lnd/pull/6956) @@ -167,6 +174,7 @@ details. * Graham Krizek * hieblmi * Jesse de Wit +* Joost Jager * Jordi Montes * Matt Morehouse * Michael Street diff --git a/htlcswitch/held_htlc_set.go b/htlcswitch/held_htlc_set.go index f7f0562cb..3c412757f 100644 --- a/htlcswitch/held_htlc_set.go +++ b/htlcswitch/held_htlc_set.go @@ -37,6 +37,20 @@ func (h *heldHtlcSet) popAll(cb func(InterceptedForward)) { h.set = make(map[channeldb.CircuitKey]InterceptedForward) } +// popAutoFails calls the callback for each forward that has an auto-fail height +// equal or less then the specified pop height and removes them from the set. +func (h *heldHtlcSet) popAutoFails(height uint32, cb func(InterceptedForward)) { + for key, fwd := range h.set { + if uint32(fwd.Packet().AutoFailHeight) > height { + continue + } + + cb(fwd) + + delete(h.set, key) + } +} + // pop returns the specified forward and removes it from the set. func (h *heldHtlcSet) pop(key channeldb.CircuitKey) (InterceptedForward, error) { intercepted, ok := h.set[key] diff --git a/htlcswitch/held_htlc_set_test.go b/htlcswitch/held_htlc_set_test.go index 6a39fc5fc..2e8ee9cb4 100644 --- a/htlcswitch/held_htlc_set_test.go +++ b/htlcswitch/held_htlc_set_test.go @@ -78,3 +78,49 @@ func TestHeldHtlcSet(t *testing.T) { _, err = set.pop(key) require.Error(t, err) } + +func TestHeldHtlcSetAutoFails(t *testing.T) { + set := newHeldHtlcSet() + + key := channeldb.CircuitKey{ + ChanID: lnwire.NewShortChanIDFromInt(1), + HtlcID: 2, + } + + const autoFailHeight = 100 + fwd := &interceptedForward{ + packet: &htlcPacket{}, + htlc: &lnwire.UpdateAddHTLC{}, + autoFailHeight: autoFailHeight, + } + require.NoError(t, set.push(key, fwd)) + + // Test popping auto fails up to one block before the auto-fail height + // of our forward. + set.popAutoFails( + autoFailHeight-1, + func(_ InterceptedForward) { + require.Fail(t, "unexpected fwd") + }, + ) + + // Popping succeeds at the auto-fail height. + cbCalled := false + set.popAutoFails( + autoFailHeight, + func(poppedFwd InterceptedForward) { + cbCalled = true + + require.Equal(t, fwd, poppedFwd) + }, + ) + require.True(t, cbCalled) + + // After this, there should be nothing more to pop. + set.popAutoFails( + autoFailHeight, + func(_ InterceptedForward) { + require.Fail(t, "unexpected fwd") + }, + ) +} diff --git a/htlcswitch/interceptable_switch.go b/htlcswitch/interceptable_switch.go index dd8e69239..87d761a3c 100644 --- a/htlcswitch/interceptable_switch.go +++ b/htlcswitch/interceptable_switch.go @@ -64,6 +64,13 @@ type InterceptableSwitch struct { // htlc where we no longer intercept it and instead cancel it back. cltvRejectDelta uint32 + // cltvInterceptDelta defines the number of blocks before the expiry of + // the htlc where we don't intercept anymore. This value must be greater + // than CltvRejectDelta, because we don't want to offer htlcs to the + // interceptor client for which there is no time left to resolve them + // anymore. + cltvInterceptDelta uint32 + // notifier is an instance of a chain notifier that we'll use to signal // the switch when a new block has arrived. notifier chainntnfs.ChainNotifier @@ -137,9 +144,17 @@ type InterceptableSwitchConfig struct { Notifier chainntnfs.ChainNotifier // CltvRejectDelta defines the number of blocks before the expiry of the - // htlc where we no longer intercept it and instead cancel it back. + // htlc where we auto-fail an intercepted htlc to prevent channel + // force-closure. CltvRejectDelta uint32 + // CltvInterceptDelta defines the number of blocks before the expiry of + // the htlc where we don't intercept anymore. This value must be greater + // than CltvRejectDelta, because we don't want to offer htlcs to the + // interceptor client for which there is no time left to resolve them + // anymore. + CltvInterceptDelta uint32 + // RequireInterceptor indicates whether processing should block if no // interceptor is connected. RequireInterceptor bool @@ -149,6 +164,12 @@ type InterceptableSwitchConfig struct { func NewInterceptableSwitch(cfg *InterceptableSwitchConfig) ( *InterceptableSwitch, error) { + if cfg.CltvInterceptDelta <= cfg.CltvRejectDelta { + return nil, fmt.Errorf("cltv intercept delta %v not greater "+ + "than cltv reject delta %v", + cfg.CltvInterceptDelta, cfg.CltvRejectDelta) + } + return &InterceptableSwitch{ htlcSwitch: cfg.Switch, intercepted: make(chan *interceptedPackets), @@ -158,6 +179,7 @@ func NewInterceptableSwitch(cfg *InterceptableSwitchConfig) ( resolutionChan: make(chan *fwdResolution), requireInterceptor: cfg.RequireInterceptor, cltvRejectDelta: cfg.CltvRejectDelta, + cltvInterceptDelta: cfg.CltvInterceptDelta, notifier: cfg.Notifier, quit: make(chan struct{}), @@ -275,11 +297,30 @@ func (s *InterceptableSwitch) run() error { s.currentHeight = currentBlock.Height + // A new block is appended. Fail any held htlcs that + // expire at this height to prevent channel force-close. + s.failExpiredHtlcs() + case <-s.quit: return nil } } } + +func (s *InterceptableSwitch) failExpiredHtlcs() { + s.heldHtlcSet.popAutoFails( + uint32(s.currentHeight), + func(fwd InterceptedForward) { + err := fwd.FailWithCode( + lnwire.CodeTemporaryChannelFailure, + ) + if err != nil { + log.Errorf("Cannot fail packet: %v", err) + } + }, + ) +} + func (s *InterceptableSwitch) sendForward(fwd InterceptedForward) { err := s.interceptor(fwd.Packet()) if err != nil { @@ -426,6 +467,8 @@ func (s *InterceptableSwitch) interceptForward(packet *htlcPacket, htlc: htlc, packet: packet, htlcSwitch: s.htlcSwitch, + autoFailHeight: int32(packet.incomingTimeout - + s.cltvRejectDelta), } // Handle forwards that are too close to expiry. @@ -516,7 +559,7 @@ func (s *InterceptableSwitch) handleExpired(fwd *interceptedForward) ( bool, error) { height := uint32(s.currentHeight) - if fwd.packet.incomingTimeout >= height+s.cltvRejectDelta { + if fwd.packet.incomingTimeout >= height+s.cltvInterceptDelta { return false, nil } @@ -540,9 +583,10 @@ func (s *InterceptableSwitch) handleExpired(fwd *interceptedForward) ( // It is passed from the switch to external interceptors that are interested // in holding forwards and resolve them manually. type interceptedForward struct { - htlc *lnwire.UpdateAddHTLC - packet *htlcPacket - htlcSwitch *Switch + htlc *lnwire.UpdateAddHTLC + packet *htlcPacket + htlcSwitch *Switch + autoFailHeight int32 } // Packet returns the intercepted htlc packet. @@ -560,6 +604,7 @@ func (f *interceptedForward) Packet() InterceptedPacket { IncomingExpiry: f.packet.incomingTimeout, CustomRecords: f.packet.customRecords, OnionBlob: f.htlc.OnionBlob, + AutoFailHeight: f.autoFailHeight, } } diff --git a/htlcswitch/interfaces.go b/htlcswitch/interfaces.go index 06b91da90..d6615856d 100644 --- a/htlcswitch/interfaces.go +++ b/htlcswitch/interfaces.go @@ -312,6 +312,10 @@ type InterceptedPacket struct { // OnionBlob is the onion packet for the next hop OnionBlob [lnwire.OnionPacketSize]byte + + // AutoFailHeight is the block height at which this intercept will be + // failed back automatically. + AutoFailHeight int32 } // InterceptedForward is passed to the ForwardInterceptor for every forwarded diff --git a/htlcswitch/switch_test.go b/htlcswitch/switch_test.go index 6a3d6fdb9..8a6794741 100644 --- a/htlcswitch/switch_test.go +++ b/htlcswitch/switch_test.go @@ -3704,6 +3704,8 @@ func (m *mockForwardInterceptor) InterceptForwardHtlc( } func (m *mockForwardInterceptor) getIntercepted() InterceptedPacket { + m.t.Helper() + select { case p := <-m.interceptedChan: return p @@ -3764,11 +3766,12 @@ func assertOutgoingLinkReceiveIntercepted(t *testing.T, type interceptableSwitchTestContext struct { t *testing.T - preimage [sha256.Size]byte - rhash [32]byte - onionBlob [1366]byte - incomingHtlcID uint64 - cltvRejectDelta uint32 + preimage [sha256.Size]byte + rhash [32]byte + onionBlob [1366]byte + incomingHtlcID uint64 + cltvRejectDelta uint32 + cltvInterceptDelta uint32 forwardInterceptor *mockForwardInterceptor aliceChannelLink *mockChannelLink @@ -3820,12 +3823,13 @@ func newInterceptableSwitchTestContext( preimage := [sha256.Size]byte{1} ctx := &interceptableSwitchTestContext{ - t: t, - preimage: preimage, - rhash: sha256.Sum256(preimage[:]), - onionBlob: [1366]byte{4, 5, 6}, - incomingHtlcID: uint64(0), - cltvRejectDelta: 13, + t: t, + preimage: preimage, + rhash: sha256.Sum256(preimage[:]), + onionBlob: [1366]byte{4, 5, 6}, + incomingHtlcID: uint64(0), + cltvRejectDelta: 10, + cltvInterceptDelta: 13, forwardInterceptor: &mockForwardInterceptor{ t: t, interceptedChan: make(chan InterceptedPacket), @@ -3844,7 +3848,7 @@ func (c *interceptableSwitchTestContext) createTestPacket() *htlcPacket { return &htlcPacket{ incomingChanID: c.aliceChannelLink.ShortChanID(), incomingHTLCID: c.incomingHtlcID, - incomingTimeout: testStartingHeight + c.cltvRejectDelta + 1, + incomingTimeout: testStartingHeight + c.cltvInterceptDelta + 1, outgoingChanID: c.bobChannelLink.ShortChanID(), obfuscator: NewMockObfuscator(), htlc: &lnwire.UpdateAddHTLC{ @@ -3887,9 +3891,10 @@ func TestSwitchHoldForward(t *testing.T) { switchForwardInterceptor, err := NewInterceptableSwitch( &InterceptableSwitchConfig{ - Switch: c.s, - CltvRejectDelta: c.cltvRejectDelta, - Notifier: notifier, + Switch: c.s, + CltvRejectDelta: c.cltvRejectDelta, + CltvInterceptDelta: c.cltvInterceptDelta, + Notifier: notifier, }, ) require.NoError(t, err) @@ -4091,6 +4096,7 @@ func TestSwitchHoldForward(t *testing.T) { &InterceptableSwitchConfig{ Switch: c.s, CltvRejectDelta: c.cltvRejectDelta, + CltvInterceptDelta: c.cltvInterceptDelta, RequireInterceptor: true, Notifier: notifier, }, @@ -4162,6 +4168,67 @@ func TestSwitchHoldForward(t *testing.T) { } } +func TestInterceptableSwitchWatchDog(t *testing.T) { + t.Parallel() + + c := newInterceptableSwitchTestContext(t) + defer c.finish() + + // Start interceptable switch. + notifier := &mock.ChainNotifier{ + EpochChan: make(chan *chainntnfs.BlockEpoch, 1), + } + notifier.EpochChan <- &chainntnfs.BlockEpoch{Height: testStartingHeight} + + switchForwardInterceptor, err := NewInterceptableSwitch( + &InterceptableSwitchConfig{ + Switch: c.s, + CltvRejectDelta: c.cltvRejectDelta, + CltvInterceptDelta: c.cltvInterceptDelta, + Notifier: notifier, + }, + ) + require.NoError(t, err) + require.NoError(t, switchForwardInterceptor.Start()) + + // Set interceptor. + switchForwardInterceptor.SetInterceptor( + c.forwardInterceptor.InterceptForwardHtlc, + ) + + // Receive a packet. + linkQuit := make(chan struct{}) + + packet := c.createTestPacket() + + err = switchForwardInterceptor.ForwardPackets(linkQuit, false, packet) + require.NoError(t, err, "can't forward htlc packet") + + // Intercept the packet. + intercepted := c.forwardInterceptor.getIntercepted() + + require.Equal(t, + int32(packet.incomingTimeout-c.cltvRejectDelta), + intercepted.AutoFailHeight, + ) + + // Htlc expires before a resolution from the interceptor. + notifier.EpochChan <- &chainntnfs.BlockEpoch{ + Height: int32(packet.incomingTimeout) - + int32(c.cltvRejectDelta), + } + + // Expect the htlc to be failed back. + assertOutgoingLinkReceive(t, c.aliceChannelLink, true) + + // It is too late now to resolve. Expect an error. + require.Error(t, switchForwardInterceptor.Resolve(&FwdResolution{ + Action: FwdActionSettle, + Key: intercepted.IncomingCircuit, + Preimage: c.preimage, + })) +} + // TestSwitchDustForwarding tests that the switch properly fails HTLC's which // have incoming or outgoing links that breach their dust thresholds. func TestSwitchDustForwarding(t *testing.T) { @@ -5395,8 +5462,10 @@ func testSwitchAliasInterceptFail(t *testing.T, zeroConf bool) { interceptSwitch, err := NewInterceptableSwitch( &InterceptableSwitchConfig{ - Switch: s, - Notifier: notifier, + Switch: s, + Notifier: notifier, + CltvRejectDelta: 10, + CltvInterceptDelta: 13, }, ) require.NoError(t, err) diff --git a/lncfg/config.go b/lncfg/config.go index bc1b9ff59..2f3e014c8 100644 --- a/lncfg/config.go +++ b/lncfg/config.go @@ -40,6 +40,13 @@ const ( // push us in the broadcast window. DefaultFinalCltvRejectDelta = DefaultIncomingBroadcastDelta + 3 + // DefaultCltvInterceptDelta defines the number of blocks before the + // expiry of the htlc where we don't intercept anymore. This value must + // be greater than CltvRejectDelta, because we don't want to offer htlcs + // to the interceptor client for which there is no time left to resolve + // them anymore. + DefaultCltvInterceptDelta = DefaultFinalCltvRejectDelta + 3 + // DefaultOutgoingBroadcastDelta defines the number of blocks before the // expiry of an outgoing htlc at which we force close the channel. We // are not in a hurry to force close, because there is nothing to claim diff --git a/lnrpc/routerrpc/forward_interceptor.go b/lnrpc/routerrpc/forward_interceptor.go index 9fba60f1f..a636ca9fb 100644 --- a/lnrpc/routerrpc/forward_interceptor.go +++ b/lnrpc/routerrpc/forward_interceptor.go @@ -90,6 +90,7 @@ func (r *forwardInterceptor) onIntercept( IncomingExpiry: htlc.IncomingExpiry, CustomRecords: htlc.CustomRecords, OnionBlob: htlc.OnionBlob[:], + AutoFailHeight: htlc.AutoFailHeight, } return r.stream.Send(interceptionRequest) diff --git a/lnrpc/routerrpc/router.pb.go b/lnrpc/routerrpc/router.pb.go index 8cf6b3f27..cbfdd9a15 100644 --- a/lnrpc/routerrpc/router.pb.go +++ b/lnrpc/routerrpc/router.pb.go @@ -2590,6 +2590,9 @@ type ForwardHtlcInterceptRequest struct { CustomRecords map[uint64][]byte `protobuf:"bytes,8,rep,name=custom_records,json=customRecords,proto3" json:"custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` // The onion blob for the next hop OnionBlob []byte `protobuf:"bytes,9,opt,name=onion_blob,json=onionBlob,proto3" json:"onion_blob,omitempty"` + // The block height at which this htlc will be auto-failed to prevent the + // channel from force-closing. + AutoFailHeight int32 `protobuf:"varint,10,opt,name=auto_fail_height,json=autoFailHeight,proto3" json:"auto_fail_height,omitempty"` } func (x *ForwardHtlcInterceptRequest) Reset() { @@ -2687,6 +2690,13 @@ func (x *ForwardHtlcInterceptRequest) GetOnionBlob() []byte { return nil } +func (x *ForwardHtlcInterceptRequest) GetAutoFailHeight() int32 { + if x != nil { + return x.AutoFailHeight + } + return 0 +} + // * // ForwardHtlcInterceptResponse enables the caller to resolve a previously hold // forward. The caller can choose either to: @@ -3178,7 +3188,7 @@ var file_routerrpc_router_proto_rawDesc = []byte{ 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x06, 0x68, 0x74, 0x6c, 0x63, 0x49, 0x64, 0x22, 0xbf, 0x04, 0x0a, 0x1b, 0x46, 0x6f, 0x72, + 0x52, 0x06, 0x68, 0x74, 0x6c, 0x63, 0x49, 0x64, 0x22, 0xe9, 0x04, 0x0a, 0x1b, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x47, 0x0a, 0x14, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x5f, 0x6b, 0x65, 0x79, @@ -3210,194 +3220,196 @@ var file_routerrpc_router_proto_rawDesc = []byte{ 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x6e, 0x69, 0x6f, 0x6e, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x6f, - 0x6e, 0x69, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, 0x62, 0x1a, 0x40, 0x0a, 0x12, 0x43, 0x75, 0x73, 0x74, - 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xa8, 0x02, 0x0a, 0x1c, 0x46, - 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, - 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x14, 0x69, - 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x5f, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x6f, 0x75, 0x74, - 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, - 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, - 0x74, 0x4b, 0x65, 0x79, 0x12, 0x3b, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, - 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x48, 0x6f, 0x6c, 0x64, 0x46, 0x6f, 0x72, 0x77, - 0x61, 0x72, 0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x27, 0x0a, - 0x0f, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x4d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x3d, 0x0a, 0x0c, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, - 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x46, 0x61, 0x69, - 0x6c, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x0b, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, - 0x65, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x82, 0x01, 0x0a, 0x17, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, - 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x33, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, - 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x41, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x1a, 0x0a, 0x18, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x81, 0x04, 0x0a, 0x0d, 0x46, 0x61, 0x69, 0x6c, 0x75, - 0x72, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, - 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x4f, 0x5f, 0x44, 0x45, 0x54, 0x41, - 0x49, 0x4c, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x44, 0x45, - 0x43, 0x4f, 0x44, 0x45, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x4e, - 0x4f, 0x54, 0x5f, 0x45, 0x4c, 0x49, 0x47, 0x49, 0x42, 0x4c, 0x45, 0x10, 0x03, 0x12, 0x14, 0x0a, - 0x10, 0x4f, 0x4e, 0x5f, 0x43, 0x48, 0x41, 0x49, 0x4e, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, - 0x54, 0x10, 0x04, 0x12, 0x14, 0x0a, 0x10, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x45, 0x58, 0x43, 0x45, - 0x45, 0x44, 0x53, 0x5f, 0x4d, 0x41, 0x58, 0x10, 0x05, 0x12, 0x18, 0x0a, 0x14, 0x49, 0x4e, 0x53, - 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x42, 0x41, 0x4c, 0x41, 0x4e, 0x43, - 0x45, 0x10, 0x06, 0x12, 0x16, 0x0a, 0x12, 0x49, 0x4e, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, - 0x45, 0x5f, 0x46, 0x4f, 0x52, 0x57, 0x41, 0x52, 0x44, 0x10, 0x07, 0x12, 0x13, 0x0a, 0x0f, 0x48, - 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x44, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x08, - 0x12, 0x15, 0x0a, 0x11, 0x46, 0x4f, 0x52, 0x57, 0x41, 0x52, 0x44, 0x53, 0x5f, 0x44, 0x49, 0x53, - 0x41, 0x42, 0x4c, 0x45, 0x44, 0x10, 0x09, 0x12, 0x14, 0x0a, 0x10, 0x49, 0x4e, 0x56, 0x4f, 0x49, - 0x43, 0x45, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x45, 0x44, 0x10, 0x0a, 0x12, 0x15, 0x0a, - 0x11, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x55, 0x4e, 0x44, 0x45, 0x52, 0x50, 0x41, - 0x49, 0x44, 0x10, 0x0b, 0x12, 0x1b, 0x0a, 0x17, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, - 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, 0x5f, 0x54, 0x4f, 0x4f, 0x5f, 0x53, 0x4f, 0x4f, 0x4e, 0x10, - 0x0c, 0x12, 0x14, 0x0a, 0x10, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x4e, 0x4f, 0x54, - 0x5f, 0x4f, 0x50, 0x45, 0x4e, 0x10, 0x0d, 0x12, 0x17, 0x0a, 0x13, 0x4d, 0x50, 0x50, 0x5f, 0x49, - 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x0e, - 0x12, 0x14, 0x0a, 0x10, 0x41, 0x44, 0x44, 0x52, 0x45, 0x53, 0x53, 0x5f, 0x4d, 0x49, 0x53, 0x4d, - 0x41, 0x54, 0x43, 0x48, 0x10, 0x0f, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x45, 0x54, 0x5f, 0x54, 0x4f, - 0x54, 0x41, 0x4c, 0x5f, 0x4d, 0x49, 0x53, 0x4d, 0x41, 0x54, 0x43, 0x48, 0x10, 0x10, 0x12, 0x15, - 0x0a, 0x11, 0x53, 0x45, 0x54, 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, 0x5f, 0x54, 0x4f, 0x4f, 0x5f, - 0x4c, 0x4f, 0x57, 0x10, 0x11, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x45, 0x54, 0x5f, 0x4f, 0x56, 0x45, - 0x52, 0x50, 0x41, 0x49, 0x44, 0x10, 0x12, 0x12, 0x13, 0x0a, 0x0f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, - 0x57, 0x4e, 0x5f, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x10, 0x13, 0x12, 0x13, 0x0a, 0x0f, - 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4b, 0x45, 0x59, 0x53, 0x45, 0x4e, 0x44, 0x10, - 0x14, 0x12, 0x13, 0x0a, 0x0f, 0x4d, 0x50, 0x50, 0x5f, 0x49, 0x4e, 0x5f, 0x50, 0x52, 0x4f, 0x47, - 0x52, 0x45, 0x53, 0x53, 0x10, 0x15, 0x12, 0x12, 0x0a, 0x0e, 0x43, 0x49, 0x52, 0x43, 0x55, 0x4c, - 0x41, 0x52, 0x5f, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x10, 0x16, 0x2a, 0xae, 0x01, 0x0a, 0x0c, 0x50, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x49, - 0x4e, 0x5f, 0x46, 0x4c, 0x49, 0x47, 0x48, 0x54, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x55, - 0x43, 0x43, 0x45, 0x45, 0x44, 0x45, 0x44, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x46, 0x41, 0x49, - 0x4c, 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x02, 0x12, 0x13, 0x0a, - 0x0f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x4e, 0x4f, 0x5f, 0x52, 0x4f, 0x55, 0x54, 0x45, - 0x10, 0x03, 0x12, 0x10, 0x0a, 0x0c, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x45, 0x52, 0x52, - 0x4f, 0x52, 0x10, 0x04, 0x12, 0x24, 0x0a, 0x20, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x49, - 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, - 0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x53, 0x10, 0x05, 0x12, 0x1f, 0x0a, 0x1b, 0x46, 0x41, - 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, - 0x54, 0x5f, 0x42, 0x41, 0x4c, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x06, 0x2a, 0x3c, 0x0a, 0x18, 0x52, - 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x48, 0x6f, 0x6c, 0x64, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, - 0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x45, 0x54, 0x54, 0x4c, - 0x45, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x46, 0x41, 0x49, 0x4c, 0x10, 0x01, 0x12, 0x0a, 0x0a, - 0x06, 0x52, 0x45, 0x53, 0x55, 0x4d, 0x45, 0x10, 0x02, 0x2a, 0x35, 0x0a, 0x10, 0x43, 0x68, 0x61, - 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, - 0x06, 0x45, 0x4e, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x49, 0x53, - 0x41, 0x42, 0x4c, 0x45, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x55, 0x54, 0x4f, 0x10, 0x02, - 0x32, 0xb5, 0x0c, 0x0a, 0x06, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x12, 0x40, 0x0a, 0x0d, 0x53, - 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x32, 0x12, 0x1d, 0x2e, 0x72, - 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x42, 0x0a, - 0x0e, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x32, 0x12, - 0x1e, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x63, - 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x30, - 0x01, 0x12, 0x42, 0x0a, 0x0d, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, - 0x74, 0x73, 0x12, 0x1f, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x54, - 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, - 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x4b, 0x0a, 0x10, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, - 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x46, 0x65, 0x65, 0x12, 0x1a, 0x2e, 0x72, 0x6f, 0x75, 0x74, - 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, - 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x51, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, - 0x65, 0x12, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, - 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1e, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, - 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x03, 0x88, 0x02, 0x01, 0x12, 0x42, 0x0a, 0x0d, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, - 0x6f, 0x75, 0x74, 0x65, 0x56, 0x32, 0x12, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, - 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x54, - 0x4c, 0x43, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x12, 0x64, 0x0a, 0x13, 0x52, 0x65, 0x73, - 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, - 0x12, 0x25, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, - 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x6e, 0x69, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, 0x62, 0x12, 0x28, 0x0a, 0x10, 0x61, 0x75, 0x74, 0x6f, + 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x0a, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x0e, 0x61, 0x75, 0x74, 0x6f, 0x46, 0x61, 0x69, 0x6c, 0x48, 0x65, 0x69, 0x67, + 0x68, 0x74, 0x1a, 0x40, 0x0a, 0x12, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, + 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x3a, 0x02, 0x38, 0x01, 0x22, 0xa8, 0x02, 0x0a, 0x1c, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, + 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x14, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, + 0x67, 0x5f, 0x63, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, + 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6f, + 0x6d, 0x69, 0x6e, 0x67, 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x3b, + 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, + 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x6c, + 0x76, 0x65, 0x48, 0x6f, 0x6c, 0x64, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x41, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, + 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, + 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x66, 0x61, 0x69, 0x6c, 0x75, + 0x72, 0x65, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0e, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x12, 0x3d, 0x0a, 0x0c, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, + 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x43, 0x6f, + 0x64, 0x65, 0x52, 0x0b, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x22, + 0x82, 0x01, 0x0a, 0x17, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x0a, 0x63, + 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, + 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, + 0x33, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x1b, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x1a, 0x0a, 0x18, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, + 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x2a, 0x81, 0x04, 0x0a, 0x0d, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x44, 0x65, 0x74, 0x61, + 0x69, 0x6c, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, + 0x0d, 0x0a, 0x09, 0x4e, 0x4f, 0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x10, 0x01, 0x12, 0x10, + 0x0a, 0x0c, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x43, 0x4f, 0x44, 0x45, 0x10, 0x02, + 0x12, 0x15, 0x0a, 0x11, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x45, 0x4c, 0x49, + 0x47, 0x49, 0x42, 0x4c, 0x45, 0x10, 0x03, 0x12, 0x14, 0x0a, 0x10, 0x4f, 0x4e, 0x5f, 0x43, 0x48, + 0x41, 0x49, 0x4e, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x04, 0x12, 0x14, 0x0a, + 0x10, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x45, 0x58, 0x43, 0x45, 0x45, 0x44, 0x53, 0x5f, 0x4d, 0x41, + 0x58, 0x10, 0x05, 0x12, 0x18, 0x0a, 0x14, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, + 0x45, 0x4e, 0x54, 0x5f, 0x42, 0x41, 0x4c, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x06, 0x12, 0x16, 0x0a, + 0x12, 0x49, 0x4e, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x46, 0x4f, 0x52, 0x57, + 0x41, 0x52, 0x44, 0x10, 0x07, 0x12, 0x13, 0x0a, 0x0f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x44, + 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x08, 0x12, 0x15, 0x0a, 0x11, 0x46, 0x4f, + 0x52, 0x57, 0x41, 0x52, 0x44, 0x53, 0x5f, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x44, 0x10, + 0x09, 0x12, 0x14, 0x0a, 0x10, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x43, 0x41, 0x4e, + 0x43, 0x45, 0x4c, 0x45, 0x44, 0x10, 0x0a, 0x12, 0x15, 0x0a, 0x11, 0x49, 0x4e, 0x56, 0x4f, 0x49, + 0x43, 0x45, 0x5f, 0x55, 0x4e, 0x44, 0x45, 0x52, 0x50, 0x41, 0x49, 0x44, 0x10, 0x0b, 0x12, 0x1b, + 0x0a, 0x17, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, + 0x5f, 0x54, 0x4f, 0x4f, 0x5f, 0x53, 0x4f, 0x4f, 0x4e, 0x10, 0x0c, 0x12, 0x14, 0x0a, 0x10, 0x49, + 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x4f, 0x50, 0x45, 0x4e, 0x10, + 0x0d, 0x12, 0x17, 0x0a, 0x13, 0x4d, 0x50, 0x50, 0x5f, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, + 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x0e, 0x12, 0x14, 0x0a, 0x10, 0x41, 0x44, + 0x44, 0x52, 0x45, 0x53, 0x53, 0x5f, 0x4d, 0x49, 0x53, 0x4d, 0x41, 0x54, 0x43, 0x48, 0x10, 0x0f, + 0x12, 0x16, 0x0a, 0x12, 0x53, 0x45, 0x54, 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, 0x5f, 0x4d, 0x49, + 0x53, 0x4d, 0x41, 0x54, 0x43, 0x48, 0x10, 0x10, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x45, 0x54, 0x5f, + 0x54, 0x4f, 0x54, 0x41, 0x4c, 0x5f, 0x54, 0x4f, 0x4f, 0x5f, 0x4c, 0x4f, 0x57, 0x10, 0x11, 0x12, + 0x10, 0x0a, 0x0c, 0x53, 0x45, 0x54, 0x5f, 0x4f, 0x56, 0x45, 0x52, 0x50, 0x41, 0x49, 0x44, 0x10, + 0x12, 0x12, 0x13, 0x0a, 0x0f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x49, 0x4e, 0x56, + 0x4f, 0x49, 0x43, 0x45, 0x10, 0x13, 0x12, 0x13, 0x0a, 0x0f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, + 0x44, 0x5f, 0x4b, 0x45, 0x59, 0x53, 0x45, 0x4e, 0x44, 0x10, 0x14, 0x12, 0x13, 0x0a, 0x0f, 0x4d, + 0x50, 0x50, 0x5f, 0x49, 0x4e, 0x5f, 0x50, 0x52, 0x4f, 0x47, 0x52, 0x45, 0x53, 0x53, 0x10, 0x15, + 0x12, 0x12, 0x0a, 0x0e, 0x43, 0x49, 0x52, 0x43, 0x55, 0x4c, 0x41, 0x52, 0x5f, 0x52, 0x4f, 0x55, + 0x54, 0x45, 0x10, 0x16, 0x2a, 0xae, 0x01, 0x0a, 0x0c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x49, 0x4e, 0x5f, 0x46, 0x4c, 0x49, 0x47, + 0x48, 0x54, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x55, 0x43, 0x43, 0x45, 0x45, 0x44, 0x45, + 0x44, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x54, 0x49, + 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x46, 0x41, 0x49, 0x4c, 0x45, + 0x44, 0x5f, 0x4e, 0x4f, 0x5f, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x10, 0x03, 0x12, 0x10, 0x0a, 0x0c, + 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x12, 0x24, + 0x0a, 0x20, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, + 0x43, 0x54, 0x5f, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, + 0x4c, 0x53, 0x10, 0x05, 0x12, 0x1f, 0x0a, 0x1b, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x49, + 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x42, 0x41, 0x4c, 0x41, + 0x4e, 0x43, 0x45, 0x10, 0x06, 0x2a, 0x3c, 0x0a, 0x18, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, + 0x48, 0x6f, 0x6c, 0x64, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x45, 0x54, 0x54, 0x4c, 0x45, 0x10, 0x00, 0x12, 0x08, 0x0a, + 0x04, 0x46, 0x41, 0x49, 0x4c, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x52, 0x45, 0x53, 0x55, 0x4d, + 0x45, 0x10, 0x02, 0x2a, 0x35, 0x0a, 0x10, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x45, 0x4e, 0x41, 0x42, 0x4c, + 0x45, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x01, + 0x12, 0x08, 0x0a, 0x04, 0x41, 0x55, 0x54, 0x4f, 0x10, 0x02, 0x32, 0xb5, 0x0c, 0x0a, 0x06, 0x52, + 0x6f, 0x75, 0x74, 0x65, 0x72, 0x12, 0x40, 0x0a, 0x0d, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x32, 0x12, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, + 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0e, 0x54, 0x72, 0x61, 0x63, 0x6b, + 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x32, 0x12, 0x1e, 0x2e, 0x72, 0x6f, 0x75, 0x74, + 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0d, 0x54, + 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x2e, 0x72, + 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, + 0x4b, 0x0a, 0x10, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, + 0x46, 0x65, 0x65, 0x12, 0x1a, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, + 0x52, 0x6f, 0x75, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1b, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, + 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51, 0x0a, 0x0b, + 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x1d, 0x2e, 0x72, 0x6f, + 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, + 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x72, 0x6f, 0x75, + 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, + 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x12, + 0x42, 0x0a, 0x0d, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x56, 0x32, + 0x12, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, + 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x54, 0x4c, 0x43, 0x41, 0x74, 0x74, 0x65, + 0x6d, 0x70, 0x74, 0x12, 0x64, 0x0a, 0x13, 0x52, 0x65, 0x73, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, 0x25, 0x2e, 0x72, 0x6f, 0x75, + 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x26, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, + 0x73, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, 0x0a, 0x13, 0x51, 0x75, 0x65, + 0x72, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x12, 0x25, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, + 0x72, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, - 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x64, 0x0a, 0x13, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, - 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, 0x25, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, - 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, - 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, - 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, - 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x15, 0x58, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, - 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, 0x27, - 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x58, 0x49, 0x6d, 0x70, 0x6f, - 0x72, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, - 0x72, 0x70, 0x63, 0x2e, 0x58, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x70, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, - 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x29, 0x2e, 0x72, - 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, - 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, - 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x70, 0x0a, 0x17, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x29, - 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x4d, 0x69, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x72, 0x6f, 0x75, 0x74, - 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, 0x10, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, - 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x22, 0x2e, 0x72, 0x6f, 0x75, 0x74, - 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x62, 0x61, - 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, - 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, - 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0a, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x65, - 0x12, 0x1c, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, 0x69, - 0x6c, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, - 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, - 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, - 0x13, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x73, 0x12, 0x25, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, - 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x72, 0x6f, - 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x30, 0x01, 0x12, 0x4d, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, - 0x6e, 0x74, 0x12, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, - 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x03, 0x88, 0x02, 0x01, - 0x30, 0x01, 0x12, 0x4f, 0x0a, 0x0c, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, - 0x6e, 0x74, 0x12, 0x1e, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x54, - 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x50, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x03, 0x88, 0x02, - 0x01, 0x30, 0x01, 0x12, 0x66, 0x0a, 0x0f, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, - 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x27, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, - 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, - 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, - 0x26, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, - 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, 0x5b, 0x0a, 0x10, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, - 0x22, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, - 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x6a, 0x0a, 0x15, 0x58, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, 0x27, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, + 0x72, 0x72, 0x70, 0x63, 0x2e, 0x58, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x28, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x58, 0x49, + 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x70, 0x0a, 0x17, 0x47, + 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x29, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, + 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x2a, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, + 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x70, 0x0a, + 0x17, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x29, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, + 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, + 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, + 0x53, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x5b, 0x0a, 0x10, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, + 0x69, 0x74, 0x79, 0x12, 0x22, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, + 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, + 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, + 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0a, + 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x1c, 0x2e, 0x72, 0x6f, 0x75, + 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x6f, 0x75, 0x74, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, + 0x72, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x13, 0x53, 0x75, 0x62, 0x73, 0x63, + 0x72, 0x69, 0x62, 0x65, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x25, + 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, + 0x72, 0x69, 0x62, 0x65, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, + 0x63, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x4d, 0x0a, + 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1d, 0x2e, 0x72, + 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x6f, + 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x03, 0x88, 0x02, 0x01, 0x30, 0x01, 0x12, 0x4f, 0x0a, 0x0c, + 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1e, 0x2e, 0x72, + 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, + 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x03, 0x88, 0x02, 0x01, 0x30, 0x01, 0x12, 0x66, 0x0a, + 0x0f, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72, + 0x12, 0x27, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, + 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x26, 0x2e, 0x72, 0x6f, 0x75, 0x74, + 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, + 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, 0x5b, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, + 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x22, 0x2e, 0x72, 0x6f, 0x75, 0x74, + 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, + 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, + 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x72, 0x6f, 0x75, 0x74, + 0x65, 0x72, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/lnrpc/routerrpc/router.proto b/lnrpc/routerrpc/router.proto index 52b34eee5..7cef30586 100644 --- a/lnrpc/routerrpc/router.proto +++ b/lnrpc/routerrpc/router.proto @@ -792,6 +792,10 @@ message ForwardHtlcInterceptRequest { // The onion blob for the next hop bytes onion_blob = 9; + + // The block height at which this htlc will be auto-failed to prevent the + // channel from force-closing. + int32 auto_fail_height = 10; } /** diff --git a/lnrpc/routerrpc/router.swagger.json b/lnrpc/routerrpc/router.swagger.json index 8cfe8c4a6..37da975c1 100644 --- a/lnrpc/routerrpc/router.swagger.json +++ b/lnrpc/routerrpc/router.swagger.json @@ -1229,6 +1229,11 @@ "type": "string", "format": "byte", "title": "The onion blob for the next hop" + }, + "auto_fail_height": { + "type": "integer", + "format": "int32", + "description": "The block height at which this htlc will be auto-failed to prevent the\nchannel from force-closing." } } }, diff --git a/peer/test_utils.go b/peer/test_utils.go index 723a439bd..41f6d2c0f 100644 --- a/peer/test_utils.go +++ b/peer/test_utils.go @@ -368,8 +368,9 @@ func createTestPeer(t *testing.T, notifier chainntnfs.ChainNotifier, interceptableSwitch, err := htlcswitch.NewInterceptableSwitch( &htlcswitch.InterceptableSwitchConfig{ - CltvRejectDelta: testCltvRejectDelta, - Notifier: interceptableSwitchNotifier, + CltvRejectDelta: testCltvRejectDelta, + CltvInterceptDelta: testCltvRejectDelta + 3, + Notifier: interceptableSwitchNotifier, }, ) if err != nil { diff --git a/server.go b/server.go index 372e349ce..56d01c6e7 100644 --- a/server.go +++ b/server.go @@ -670,6 +670,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, &htlcswitch.InterceptableSwitchConfig{ Switch: s.htlcSwitch, CltvRejectDelta: lncfg.DefaultFinalCltvRejectDelta, + CltvInterceptDelta: lncfg.DefaultCltvInterceptDelta, RequireInterceptor: s.cfg.RequireInterceptor, Notifier: s.cc.ChainNotifier, },