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 new file mode 100644 index 000000000..3c412757f --- /dev/null +++ b/htlcswitch/held_htlc_set.go @@ -0,0 +1,89 @@ +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) +} + +// 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] + 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..2e8ee9cb4 --- /dev/null +++ b/htlcswitch/held_htlc_set_test.go @@ -0,0 +1,126 @@ +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) +} + +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 fb3a28458..87d761a3c 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. @@ -54,13 +57,32 @@ 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. 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 + + // 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{} } @@ -111,22 +133,57 @@ type fwdResolution struct { errChan chan error } +// 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 + + // 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 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 +} + // NewInterceptableSwitch returns an instance of InterceptableSwitch. -func NewInterceptableSwitch(s *Switch, cltvRejectDelta uint32, - requireInterceptor bool) *InterceptableSwitch { +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: s, + htlcSwitch: cfg.Switch, 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: requireInterceptor, - cltvRejectDelta: cltvRejectDelta, + requireInterceptor: cfg.RequireInterceptor, + cltvRejectDelta: cfg.CltvRejectDelta, + cltvInterceptDelta: cfg.CltvInterceptDelta, + notifier: cfg.Notifier, quit: make(chan struct{}), - } + }, nil } // SetInterceptor sets the ForwardInterceptor to be used. A nil argument @@ -144,11 +201,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 @@ -158,10 +224,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. @@ -171,7 +255,14 @@ func (s *InterceptableSwitch) run() { 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, ) @@ -192,16 +283,44 @@ func (s *InterceptableSwitch) run() { // 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) + case currentBlock, ok := <-s.blockEpochStream.Epochs: + if !ok { + return errBlockStreamStopped + } + + 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 + 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 { @@ -220,9 +339,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 } @@ -238,20 +355,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: @@ -338,19 +454,21 @@ 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{ htlc: htlc, packet: packet, htlcSwitch: s.htlcSwitch, + autoFailHeight: int32(packet.incomingTimeout - + s.cltvRejectDelta), } // Handle forwards that are too close to expiry. @@ -368,28 +486,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 @@ -397,7 +515,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 @@ -411,23 +529,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 @@ -435,8 +558,8 @@ func (s *InterceptableSwitch) forward( func (s *InterceptableSwitch) handleExpired(fwd *interceptedForward) ( bool, error) { - height := s.htlcSwitch.BestHeight() - if fwd.packet.incomingTimeout >= height+s.cltvRejectDelta { + height := uint32(s.currentHeight) + if fwd.packet.incomingTimeout >= height+s.cltvInterceptDelta { return false, nil } @@ -460,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. @@ -480,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 61c7514ce..8a6794741 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" @@ -3702,6 +3704,8 @@ func (m *mockForwardInterceptor) InterceptForwardHtlc( } func (m *mockForwardInterceptor) getIntercepted() InterceptedPacket { + m.t.Helper() + select { case p := <-m.interceptedChan: return p @@ -3750,6 +3754,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): @@ -3757,8 +3763,24 @@ 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 + cltvInterceptDelta uint32 + + forwardInterceptor *mockForwardInterceptor + aliceChannelLink *mockChannelLink + bobChannelLink *mockChannelLink + s *Switch +} + +func newInterceptableSwitchTestContext( + t *testing.T) *interceptableSwitchTestContext { chanID1, chanID2, aliceChanID, bobChanID := genIDs() @@ -3783,12 +3805,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, @@ -3804,72 +3820,106 @@ 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, - }, - } + ctx := &interceptableSwitchTestContext{ + 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), + }, + aliceChannelLink: aliceChannelLink, + bobChannelLink: bobChannelLink, + s: s, } - createSettlePacket := func(outgoingHTLCID uint64) *htlcPacket { - return &htlcPacket{ - outgoingChanID: bobChannelLink.ShortChanID(), - outgoingHTLCID: outgoingHTLCID, - amount: 1, - htlc: &lnwire.UpdateFulfillHTLC{ - PaymentPreimage: preimage, - }, - } - } + return ctx +} - forwardInterceptor := &mockForwardInterceptor{ - t: t, - interceptedChan: make(chan InterceptedPacket), +func (c *interceptableSwitchTestContext) createTestPacket() *htlcPacket { + c.incomingHtlcID++ + + return &htlcPacket{ + incomingChanID: c.aliceChannelLink.ShortChanID(), + incomingHTLCID: c.incomingHtlcID, + incomingTimeout: testStartingHeight + c.cltvInterceptDelta + 1, + outgoingChanID: c.bobChannelLink.ShortChanID(), + obfuscator: NewMockObfuscator(), + htlc: &lnwire.UpdateAddHTLC{ + PaymentHash: c.rhash, + Amount: 1, + OnionBlob: c.onionBlob, + }, } - switchForwardInterceptor := NewInterceptableSwitch( - s, cltvRejectDelta, false, +} + +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), + } + 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()) - 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) 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") @@ -3877,137 +3927,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, } @@ -4016,39 +4066,53 @@ 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()) // Test always-on interception. - switchForwardInterceptor = NewInterceptableSwitch(s, cltvRejectDelta, true) + 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, + RequireInterceptor: true, + Notifier: notifier, + }, + ) + require.NoError(t, err) require.NoError(t, switchForwardInterceptor.Start()) // 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 @@ -4056,54 +4120,115 @@ 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: } } +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) { @@ -5329,7 +5454,21 @@ func testSwitchAliasInterceptFail(t *testing.T, zeroConf bool) { t: t, interceptedChan: make(chan InterceptedPacket), } - interceptSwitch := NewInterceptableSwitch(s, 0, false) + + notifier := &mock.ChainNotifier{ + EpochChan: make(chan *chainntnfs.BlockEpoch, 1), + } + notifier.EpochChan <- &chainntnfs.BlockEpoch{Height: testStartingHeight} + + interceptSwitch, err := NewInterceptableSwitch( + &InterceptableSwitchConfig{ + Switch: s, + Notifier: notifier, + CltvRejectDelta: 10, + CltvInterceptDelta: 13, + }, + ) + require.NoError(t, err) require.NoError(t, interceptSwitch.Start()) interceptSwitch.SetInterceptor(forwardInterceptor.InterceptForwardHtlc) 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 938c3e51c..41f6d2c0f 100644 --- a/peer/test_utils.go +++ b/peer/test_utils.go @@ -359,6 +359,24 @@ 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, + } + + interceptableSwitch, err := htlcswitch.NewInterceptableSwitch( + &htlcswitch.InterceptableSwitchConfig{ + CltvRejectDelta: testCltvRejectDelta, + CltvInterceptDelta: testCltvRejectDelta + 3, + Notifier: interceptableSwitchNotifier, + }, + ) + if err != nil { + return nil, nil, err + } + cfg := &Config{ Addr: cfgAddr, PubKeyBytes: pubKey, @@ -366,16 +384,14 @@ func createTestPeer(t *testing.T, notifier chainntnfs.ChainNotifier, ChainIO: chainIO, Switch: mockSwitch, ChanActiveTimeout: chanActiveTimeout, - InterceptSwitch: htlcswitch.NewInterceptableSwitch( - nil, testCltvRejectDelta, false, - ), - 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 e3b86da6c..56d01c6e7 100644 --- a/server.go +++ b/server.go @@ -666,10 +666,18 @@ func newServer(cfg *Config, listenAddrs []net.Addr, if err != nil { return nil, err } - s.interceptableSwitch = htlcswitch.NewInterceptableSwitch( - s.htlcSwitch, lncfg.DefaultFinalCltvRejectDelta, - s.cfg.RequireInterceptor, + s.interceptableSwitch, err = htlcswitch.NewInterceptableSwitch( + &htlcswitch.InterceptableSwitchConfig{ + Switch: s.htlcSwitch, + CltvRejectDelta: lncfg.DefaultFinalCltvRejectDelta, + CltvInterceptDelta: lncfg.DefaultCltvInterceptDelta, + RequireInterceptor: s.cfg.RequireInterceptor, + Notifier: s.cc.ChainNotifier, + }, ) + if err != nil { + return nil, err + } s.witnessBeacon = newPreimageBeacon( dbs.ChanStateDB.NewWitnessCache(),