diff --git a/accessman.go b/accessman.go index 9667fbc2ec1..971132ff7ec 100644 --- a/accessman.go +++ b/accessman.go @@ -21,20 +21,24 @@ type accessMan struct { // the server mutex. banScoreMtx sync.RWMutex - // peerCounts is a mapping from remote public key to {bool, uint64} - // where the bool indicates that we have an open/closed channel with - // the peer and where the uint64 indicates the number of pending-open + // peerChanInfo is a mapping from remote public key to {bool, uint64} + // where the bool indicates that we have an open/closed channel with the + // peer and where the uint64 indicates the number of pending-open // channels we currently have with them. This mapping will be used to // determine access permissions for the peer. The map key is the // string-version of the serialized public key. // // NOTE: This MUST be accessed with the banScoreMtx held. - peerCounts map[string]channeldb.ChanCount + peerChanInfo map[string]channeldb.ChanCount // peerScores stores each connected peer's access status. The map key // is the string-version of the serialized public key. // // NOTE: This MUST be accessed with the banScoreMtx held. + // + // TODO(yy): unify `peerScores` and `peerChanInfo` - there's no need to + // create two maps tracking essentially the same info. `numRestricted` + // can also be derived from `peerChanInfo`. peerScores map[string]peerSlotStatus // numRestricted tracks the number of peers with restricted access in @@ -44,7 +48,7 @@ type accessMan struct { type accessManConfig struct { // initAccessPerms checks the channeldb for initial access permissions - // and then populates the peerCounts and peerScores maps. + // and then populates the peerChanInfo and peerScores maps. initAccessPerms func() (map[string]channeldb.ChanCount, error) // shouldDisconnect determines whether we should disconnect a peer or @@ -57,9 +61,9 @@ type accessManConfig struct { func newAccessMan(cfg *accessManConfig) (*accessMan, error) { a := &accessMan{ - cfg: cfg, - peerCounts: make(map[string]channeldb.ChanCount), - peerScores: make(map[string]peerSlotStatus), + cfg: cfg, + peerChanInfo: make(map[string]channeldb.ChanCount), + peerScores: make(map[string]peerSlotStatus), } counts, err := a.cfg.initAccessPerms() @@ -67,16 +71,58 @@ func newAccessMan(cfg *accessManConfig) (*accessMan, error) { return nil, err } - // We'll populate the server's peerCounts map with the counts fetched + // We'll populate the server's peerChanInfo map with the counts fetched // via initAccessPerms. Also note that we haven't yet connected to the // peers. - maps.Copy(a.peerCounts, counts) + maps.Copy(a.peerChanInfo, counts) acsmLog.Info("Access Manager initialized") return a, nil } +// hasPeer checks whether a given peer already exists in the internal maps. +func (a *accessMan) hasPeer(ctx context.Context, + pub string) (peerAccessStatus, bool) { + + // Lock banScoreMtx for reading so that we can read the banning maps + // below. + a.banScoreMtx.RLock() + defer a.banScoreMtx.RUnlock() + + count, found := a.peerChanInfo[pub] + if found { + if count.HasOpenOrClosedChan { + acsmLog.DebugS(ctx, "Peer has open/closed channel, "+ + "assigning protected access") + + // Exit early if the peer is no longer restricted. + return peerStatusProtected, true + } + + if count.PendingOpenCount != 0 { + acsmLog.DebugS(ctx, "Peer has pending channel(s), "+ + "assigning temporary access") + + // Exit early if the peer is no longer restricted. + return peerStatusTemporary, true + } + + return peerStatusRestricted, true + } + + // Check if the peer is found in the scores map. + status, found := a.peerScores[pub] + if found { + acsmLog.DebugS(ctx, "Peer already has access", "access", + status.state) + + return status.state, true + } + + return peerStatusRestricted, false +} + // assignPeerPerms assigns a new peer its permissions. This does not track the // access in the maps. This is intentional. func (a *accessMan) assignPeerPerms(remotePub *btcec.PublicKey) ( @@ -91,31 +137,15 @@ func (a *accessMan) assignPeerPerms(remotePub *btcec.PublicKey) ( acsmLog.DebugS(ctx, "Assigning permissions") // Default is restricted unless the below filters say otherwise. - access := peerStatusRestricted - - // Lock banScoreMtx for reading so that we can update the banning maps - // below. - a.banScoreMtx.RLock() - if count, found := a.peerCounts[peerMapKey]; found { - if count.HasOpenOrClosedChan { - acsmLog.DebugS(ctx, "Peer has open/closed channel, "+ - "assigning protected access") - - access = peerStatusProtected - } else if count.PendingOpenCount != 0 { - acsmLog.DebugS(ctx, "Peer has pending channel(s), "+ - "assigning temporary access") + access, peerExist := a.hasPeer(ctx, peerMapKey) - access = peerStatusTemporary - } - } - a.banScoreMtx.RUnlock() - - // Exit early if the peer status is no longer restricted. + // Exit early if the peer is not restricted. if access != peerStatusRestricted { return access, nil } + // If we are here, it means the peer has peerStatusRestricted. + // // Check whether this peer is banned. shouldDisconnect, err := a.cfg.shouldDisconnect(remotePub) if err != nil { @@ -138,6 +168,12 @@ func (a *accessMan) assignPeerPerms(remotePub *btcec.PublicKey) ( // peer. acsmLog.DebugS(ctx, "Peer has no channels, assigning restricted access") + // If this is an existing peer, there's no need to check for slot limit. + if peerExist { + acsmLog.DebugS(ctx, "Skipped slot check for existing peer") + return access, nil + } + a.banScoreMtx.RLock() defer a.banScoreMtx.RUnlock() @@ -187,11 +223,11 @@ func (a *accessMan) newPendingOpenChan(remotePub *btcec.PublicKey) error { case peerStatusTemporary: // If this peer's access status is temporary, we'll need to - // update the peerCounts map. The peer's access status will stay - // temporary. - peerCount, found := a.peerCounts[peerMapKey] + // update the peerChanInfo map. The peer's access status will + // stay temporary. + peerCount, found := a.peerChanInfo[peerMapKey] if !found { - // Error if we did not find any info in peerCounts. + // Error if we did not find any info in peerChanInfo. acsmLog.ErrorS(ctx, "Pending peer info not found", ErrNoPendingPeerInfo) @@ -200,7 +236,7 @@ func (a *accessMan) newPendingOpenChan(remotePub *btcec.PublicKey) error { // Increment the pending channel amount. peerCount.PendingOpenCount += 1 - a.peerCounts[peerMapKey] = peerCount + a.peerChanInfo[peerMapKey] = peerCount acsmLog.DebugS(ctx, "Peer is temporary, incremented "+ "pending count", @@ -210,13 +246,13 @@ func (a *accessMan) newPendingOpenChan(remotePub *btcec.PublicKey) error { // If the peer's access status is restricted, then we can // transition it to a temporary-access peer. We'll need to // update numRestricted and also peerScores. We'll also need to - // update peerCounts. + // update peerChanInfo. peerCount := channeldb.ChanCount{ HasOpenOrClosedChan: false, PendingOpenCount: 1, } - a.peerCounts[peerMapKey] = peerCount + a.peerChanInfo[peerMapKey] = peerCount // A restricted-access slot has opened up. oldRestricted := a.numRestricted @@ -277,12 +313,12 @@ func (a *accessMan) newPendingCloseChan(remotePub *btcec.PublicKey) error { case peerStatusTemporary: // If this peer is temporary, we need to check if it will // revert to a restricted-access peer. - peerCount, found := a.peerCounts[peerMapKey] + peerCount, found := a.peerChanInfo[peerMapKey] if !found { acsmLog.ErrorS(ctx, "Pending peer info not found", ErrNoPendingPeerInfo) - // Error if we did not find any info in peerCounts. + // Error if we did not find any info in peerChanInfo. return ErrNoPendingPeerInfo } @@ -293,8 +329,8 @@ func (a *accessMan) newPendingCloseChan(remotePub *btcec.PublicKey) error { "pending_count", currentNumPending) if currentNumPending == 0 { - // Remove the entry from peerCounts. - delete(a.peerCounts, peerMapKey) + // Remove the entry from peerChanInfo. + delete(a.peerChanInfo, peerMapKey) // If this is the only pending-open channel for this // peer and it's getting removed, attempt to demote @@ -334,7 +370,7 @@ func (a *accessMan) newPendingCloseChan(remotePub *btcec.PublicKey) error { // Else, we don't need to demote this peer since it has other // pending-open channels with us. peerCount.PendingOpenCount = currentNumPending - a.peerCounts[peerMapKey] = peerCount + a.peerChanInfo[peerMapKey] = peerCount acsmLog.DebugS(ctx, "Peer still has other pending channels", "pending_count", currentNumPending) @@ -394,9 +430,9 @@ func (a *accessMan) newOpenChan(remotePub *btcec.PublicKey) error { case peerStatusTemporary: // If the peer's state is temporary, we'll upgrade the peer to // a protected peer. - peerCount, found := a.peerCounts[peerMapKey] + peerCount, found := a.peerChanInfo[peerMapKey] if !found { - // Error if we did not find any info in peerCounts. + // Error if we did not find any info in peerChanInfo. acsmLog.ErrorS(ctx, "Pending peer info not found", ErrNoPendingPeerInfo) @@ -404,7 +440,9 @@ func (a *accessMan) newOpenChan(remotePub *btcec.PublicKey) error { } peerCount.HasOpenOrClosedChan = true - a.peerCounts[peerMapKey] = peerCount + peerCount.PendingOpenCount -= 1 + + a.peerChanInfo[peerMapKey] = peerCount newStatus := peerSlotStatus{ state: peerStatusProtected, @@ -443,11 +481,15 @@ func (a *accessMan) newOpenChan(remotePub *btcec.PublicKey) error { } } -// checkIncomingConnBanScore checks whether, given the remote's public hex- +// checkAcceptIncomingConn checks whether, given the remote's public hex- // encoded key, we should not accept this incoming connection or immediately // disconnect. This does not assign to the server's peerScores maps. This is // just an inbound filter that the brontide listeners use. -func (a *accessMan) checkIncomingConnBanScore(remotePub *btcec.PublicKey) ( +// +// TODO(yy): We should also consider removing this `checkAcceptIncomingConn` +// check as a) it doesn't check for ban score; and b) we should, and already +// have this check when we handle incoming connection in `InboundPeerConnected`. +func (a *accessMan) checkAcceptIncomingConn(remotePub *btcec.PublicKey) ( bool, error) { ctx := btclog.WithCtx( @@ -461,42 +503,51 @@ func (a *accessMan) checkIncomingConnBanScore(remotePub *btcec.PublicKey) ( a.banScoreMtx.RLock() defer a.banScoreMtx.RUnlock() - if _, found := a.peerCounts[peerMapKey]; !found { - acsmLog.DebugS(ctx, "Peer not found in counts, "+ - "checking restricted slots") + _, found := a.peerChanInfo[peerMapKey] - // Check numRestricted to see if there is an available slot. In - // the future, it's possible to add better heuristics. - if a.numRestricted < a.cfg.maxRestrictedSlots { - // There is an available slot. - acsmLog.DebugS(ctx, "Restricted slot available, "+ - "accepting", - "num_restricted", a.numRestricted, - "max_restricted", a.cfg.maxRestrictedSlots) + // Exit early if found. + if found { + acsmLog.DebugS(ctx, "Peer found (protected/temporary), "+ + "accepting") - return true, nil - } + return true, nil + } - // If there are no slots left, then we reject this connection. - acsmLog.WarnS(ctx, "No restricted slots available, "+ - "rejecting", - ErrNoMoreRestrictedAccessSlots, - "num_restricted", a.numRestricted, - "max_restricted", a.cfg.maxRestrictedSlots) + _, found = a.peerScores[peerMapKey] - return false, ErrNoMoreRestrictedAccessSlots + // Exit early if found. + if found { + acsmLog.DebugS(ctx, "Found existing peer, accepting") + + return true, nil } - // Else, the peer is either protected or temporary. - acsmLog.DebugS(ctx, "Peer found (protected/temporary), accepting") + acsmLog.DebugS(ctx, "Peer not found in counts, checking restricted "+ + "slots") + + // Check numRestricted to see if there is an available slot. In + // the future, it's possible to add better heuristics. + if a.numRestricted < a.cfg.maxRestrictedSlots { + // There is an available slot. + acsmLog.DebugS(ctx, "Restricted slot available, accepting ", + "num_restricted", a.numRestricted, "max_restricted", + a.cfg.maxRestrictedSlots) + + return true, nil + } - return true, nil + // If there are no slots left, then we reject this connection. + acsmLog.WarnS(ctx, "No restricted slots available, rejecting ", + ErrNoMoreRestrictedAccessSlots, "num_restricted", + a.numRestricted, "max_restricted", a.cfg.maxRestrictedSlots) + + return false, ErrNoMoreRestrictedAccessSlots } // addPeerAccess tracks a peer's access in the maps. This should be called when // the peer has fully connected. func (a *accessMan) addPeerAccess(remotePub *btcec.PublicKey, - access peerAccessStatus) { + access peerAccessStatus, inbound bool) { ctx := btclog.WithCtx( context.TODO(), lnutils.LogPubKey("peer", remotePub), @@ -510,34 +561,63 @@ func (a *accessMan) addPeerAccess(remotePub *btcec.PublicKey, peerMapKey := string(remotePub.SerializeCompressed()) + // Exit early if this is an existing peer, which means it won't take + // another slot. + _, found := a.peerScores[peerMapKey] + if found { + acsmLog.DebugS(ctx, "Skipped taking restricted slot for "+ + "existing peer") + + return + } + a.peerScores[peerMapKey] = peerSlotStatus{state: access} - // Increment numRestricted. - if access == peerStatusRestricted { + // Exit early if this is not a restricted peer. + if access != peerStatusRestricted { + acsmLog.DebugS(ctx, "Skipped taking restricted slot as peer "+ + "already has access", "access", access) + + return + } + + // Increment numRestricted if this is an inbound connection. + if inbound { oldRestricted := a.numRestricted a.numRestricted++ acsmLog.DebugS(ctx, "Incremented restricted slots", "old_restricted", oldRestricted, "new_restricted", a.numRestricted) + + return + } + + // Otherwise, this is a newly created outbound connection. We won't + // place any restriction on it, instead, we will do a hot upgrade here + // to move it from restricted to temporary. + peerCount := channeldb.ChanCount{ + HasOpenOrClosedChan: false, + PendingOpenCount: 0, + } + + a.peerChanInfo[peerMapKey] = peerCount + a.peerScores[peerMapKey] = peerSlotStatus{ + state: peerStatusTemporary, } + + acsmLog.InfoS(ctx, "Upgraded outbound peer: restricted -> temporary") } // removePeerAccess removes the peer's access from the maps. This should be // called when the peer has been disconnected. -func (a *accessMan) removePeerAccess(remotePub *btcec.PublicKey) { +func (a *accessMan) removePeerAccess(ctx context.Context, peerPubKey string) { + acsmLog.DebugS(ctx, "Removing access:") + a.banScoreMtx.Lock() defer a.banScoreMtx.Unlock() - ctx := btclog.WithCtx( - context.TODO(), lnutils.LogPubKey("peer", remotePub), - ) - - acsmLog.DebugS(ctx, "Removing peer access") - - peerMapKey := string(remotePub.SerializeCompressed()) - - status, found := a.peerScores[peerMapKey] + status, found := a.peerScores[peerPubKey] if !found { acsmLog.InfoS(ctx, "Peer score not found during removal") return @@ -554,7 +634,31 @@ func (a *accessMan) removePeerAccess(remotePub *btcec.PublicKey) { "new_restricted", a.numRestricted) } - acsmLog.TraceS(ctx, "Deleting peer from peerScores") + acsmLog.TraceS(ctx, "Deleting from peerScores:") + + delete(a.peerScores, peerPubKey) + + // We now check whether this peer has channels with us or not. + info, found := a.peerChanInfo[peerPubKey] + if !found { + acsmLog.DebugS(ctx, "Chan info not found during removal:") + return + } + + // Exit early if the peer has channel(s) with us. + if info.HasOpenOrClosedChan { + acsmLog.DebugS(ctx, "Skipped removing peer with channels:") + return + } + + // Skip removing the peer if it has pending open/close with us. + if info.PendingOpenCount != 0 { + acsmLog.DebugS(ctx, "Skipped removing peer with pending "+ + "channels:") + return + } - delete(a.peerScores, peerMapKey) + // Given this peer has no channels with us, we can now remove it. + delete(a.peerChanInfo, peerPubKey) + acsmLog.TraceS(ctx, "Removed peer from peerChanInfo:") } diff --git a/accessman_test.go b/accessman_test.go index 0663a9b4e96..06adab1cd3a 100644 --- a/accessman_test.go +++ b/accessman_test.go @@ -1,6 +1,7 @@ package lnd import ( + "context" "testing" "github.com/btcsuite/btcd/btcec/v2" @@ -15,7 +16,7 @@ func assertInboundConnection(t *testing.T, a *accessMan, remotePubSer := string(remotePub.SerializeCompressed()) - isSlotAvailable, err := a.checkIncomingConnBanScore(remotePub) + isSlotAvailable, err := a.checkAcceptIncomingConn(remotePub) require.NoError(t, err) require.True(t, isSlotAvailable) @@ -23,7 +24,7 @@ func assertInboundConnection(t *testing.T, a *accessMan, require.NoError(t, err) require.Equal(t, status, peerAccess) - a.addPeerAccess(remotePub, peerAccess) + a.addPeerAccess(remotePub, peerAccess, true) peerScore, ok := a.peerScores[remotePubSer] require.True(t, ok) require.Equal(t, status, peerScore.state) @@ -63,18 +64,25 @@ func TestAccessManRestrictedSlots(t *testing.T) { peerKey3 := peerPriv3.PubKey() peerKeySer3 := string(peerKey3.SerializeCompressed()) + var ( + peer1PendingCount = 0 + peer2PendingCount = 1 + peer3PendingCount = 1 + ) + initPerms := func() (map[string]channeldb.ChanCount, error) { return map[string]channeldb.ChanCount{ peerKeySer1: { HasOpenOrClosedChan: true, + PendingOpenCount: uint64(peer1PendingCount), }, peerKeySer2: { HasOpenOrClosedChan: true, - PendingOpenCount: 1, + PendingOpenCount: uint64(peer2PendingCount), }, peerKeySer3: { HasOpenOrClosedChan: false, - PendingOpenCount: 1, + PendingOpenCount: uint64(peer3PendingCount), }, }, nil } @@ -92,25 +100,25 @@ func TestAccessManRestrictedSlots(t *testing.T) { a, err := newAccessMan(cfg) require.NoError(t, err) - // Check that the peerCounts map is correctly populated with three + // Check that the peerChanInfo map is correctly populated with three // peers. require.Equal(t, 0, int(a.numRestricted)) - require.Equal(t, 3, len(a.peerCounts)) + require.Equal(t, 3, len(a.peerChanInfo)) - peerCount1, ok := a.peerCounts[peerKeySer1] + peerCount1, ok := a.peerChanInfo[peerKeySer1] require.True(t, ok) require.True(t, peerCount1.HasOpenOrClosedChan) - require.Equal(t, 0, int(peerCount1.PendingOpenCount)) + require.Equal(t, peer1PendingCount, int(peerCount1.PendingOpenCount)) - peerCount2, ok := a.peerCounts[peerKeySer2] + peerCount2, ok := a.peerChanInfo[peerKeySer2] require.True(t, ok) require.True(t, peerCount2.HasOpenOrClosedChan) - require.Equal(t, 1, int(peerCount2.PendingOpenCount)) + require.Equal(t, peer2PendingCount, int(peerCount2.PendingOpenCount)) - peerCount3, ok := a.peerCounts[peerKeySer3] + peerCount3, ok := a.peerChanInfo[peerKeySer3] require.True(t, ok) require.False(t, peerCount3.HasOpenOrClosedChan) - require.Equal(t, 1, int(peerCount3.PendingOpenCount)) + require.Equal(t, peer3PendingCount, int(peerCount3.PendingOpenCount)) // We'll now start to connect the peers. We'll add a new fourth peer // that will take up the restricted slot. The first three peers should @@ -120,7 +128,7 @@ func TestAccessManRestrictedSlots(t *testing.T) { peerKey4 := peerPriv4.PubKey() // Follow the normal process of an incoming connection. We check if we - // can accommodate this peer in checkIncomingConnBanScore and then we + // can accommodate this peer in checkAcceptIncomingConn and then we // assign its access permissions and then insert into the map. assertInboundConnection(t, a, peerKey4, peerStatusRestricted) @@ -134,11 +142,26 @@ func TestAccessManRestrictedSlots(t *testing.T) { require.NoError(t, err) assertAccessState(t, a, peerKey4, peerStatusTemporary) + // Assert that accessman's internal state is updated with peer4. We + // expect this new peer to have 1 pending open count. + peerCount4, ok := a.peerChanInfo[string(peerKey4.SerializeCompressed())] + require.True(t, ok) + require.False(t, peerCount4.HasOpenOrClosedChan) + require.Equal(t, 1, int(peerCount4.PendingOpenCount)) + // Check that an open channel promotes the temporary peer. err = a.newOpenChan(peerKey3) require.NoError(t, err) assertAccessState(t, a, peerKey3, peerStatusProtected) + // Assert that accessman's internal state is updated with peer3. We + // expect this existing peer to decrement its pending open count and the + // flag `HasOpenOrClosedChan` should be true. + peerCount3, ok = a.peerChanInfo[peerKeySer3] + require.True(t, ok) + require.True(t, peerCount3.HasOpenOrClosedChan) + require.Equal(t, peer3PendingCount-1, int(peerCount3.PendingOpenCount)) + // We should be able to accommodate a new peer. peerPriv5, err := btcec.NewPrivateKey() require.NoError(t, err) @@ -150,6 +173,10 @@ func TestAccessManRestrictedSlots(t *testing.T) { // peer. err = a.newPendingCloseChan(peerKey4) require.ErrorIs(t, err, ErrNoMoreRestrictedAccessSlots) + + // Assert that peer4 is removed. + _, ok = a.peerChanInfo[string(peerKey4.SerializeCompressed())] + require.False(t, ok) } // TestAssignPeerPerms asserts that the peer's access status is correctly @@ -250,9 +277,8 @@ func TestAssignPeerPerms(t *testing.T) { expectedErr: ErrGossiperBan, }, // peer6 has no channel with us, and we expect it to have a - // restricted status. We also expect the error - // `ErrNoMoreRestrictedAccessSlots` to be returned given - // we only allow 1 restricted peer in this test. + // restricted status. Since this peer is seen, we don't expect + // the error `ErrNoMoreRestrictedAccessSlots` to be returned. { name: "peer with no channels and restricted", peerPub: genPeerPub(), @@ -264,7 +290,7 @@ func TestAssignPeerPerms(t *testing.T) { numRestricted: 1, expectedStatus: peerStatusRestricted, - expectedErr: ErrNoMoreRestrictedAccessSlots, + expectedErr: nil, }, } @@ -394,3 +420,395 @@ func TestAssignPeerPermsBypassRestriction(t *testing.T) { }) } } + +// TestAssignPeerPermsBypassExisting asserts that when the peer is a +// pre-existing peer, it won't be restricted. +func TestAssignPeerPermsBypassExisting(t *testing.T) { + t.Parallel() + + // genPeerPub is a helper closure that generates a random public key. + genPeerPub := func() *btcec.PublicKey { + peerPriv, err := btcec.NewPrivateKey() + require.NoError(t, err) + + return peerPriv.PubKey() + } + + // peer1 exists in `peerChanInfo` map. + peer1 := genPeerPub() + peer1Str := string(peer1.SerializeCompressed()) + + // peer2 exists in `peerScores` map. + peer2 := genPeerPub() + peer2Str := string(peer2.SerializeCompressed()) + + // peer3 is a new peer. + peer3 := genPeerPub() + + // Create params to init the accessman. + initPerms := func() (map[string]channeldb.ChanCount, error) { + return map[string]channeldb.ChanCount{ + peer1Str: {}, + }, nil + } + + disconnect := func(*btcec.PublicKey) (bool, error) { + return false, nil + } + + cfg := &accessManConfig{ + initAccessPerms: initPerms, + shouldDisconnect: disconnect, + maxRestrictedSlots: 0, + } + + a, err := newAccessMan(cfg) + require.NoError(t, err) + + // Add peer2 to the `peerScores`. + a.peerScores[peer2Str] = peerSlotStatus{ + state: peerStatusTemporary, + } + + // Assigning to peer1 should not return an error. + status, err := a.assignPeerPerms(peer1) + require.NoError(t, err) + require.Equal(t, peerStatusRestricted, status) + + // Assigning to peer2 should not return an error. + status, err = a.assignPeerPerms(peer2) + require.NoError(t, err) + require.Equal(t, peerStatusTemporary, status) + + // Assigning to peer3 should return an error. + status, err = a.assignPeerPerms(peer3) + require.ErrorIs(t, err, ErrNoMoreRestrictedAccessSlots) + require.Equal(t, peerStatusRestricted, status) +} + +// TestHasPeer asserts `hasPeer` returns the correct results. +func TestHasPeer(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + // Create a testing accessMan. + a := &accessMan{ + peerChanInfo: make(map[string]channeldb.ChanCount), + peerScores: make(map[string]peerSlotStatus), + } + + // peer1 exists with an open channel. + peer1 := "peer1" + a.peerChanInfo[peer1] = channeldb.ChanCount{ + HasOpenOrClosedChan: true, + } + peer1Access := peerStatusProtected + + // peer2 exists with a pending channel. + peer2 := "peer2" + a.peerChanInfo[peer2] = channeldb.ChanCount{ + PendingOpenCount: 1, + } + peer2Access := peerStatusTemporary + + // peer3 exists without any channels. + peer3 := "peer3" + a.peerChanInfo[peer3] = channeldb.ChanCount{} + peer3Access := peerStatusRestricted + + // peer4 exists with a score. + peer4 := "peer4" + peer4Access := peerStatusTemporary + a.peerScores[peer4] = peerSlotStatus{state: peer4Access} + + // peer5 doesn't exist. + peer5 := "peer5" + + // We now assert `hasPeer` returns the correct results. + // + // peer1 should be found with peerStatusProtected. + access, found := a.hasPeer(ctx, peer1) + require.True(t, found) + require.Equal(t, peer1Access, access) + + // peer2 should be found with peerStatusTemporary. + access, found = a.hasPeer(ctx, peer2) + require.True(t, found) + require.Equal(t, peer2Access, access) + + // peer3 should be found with peerStatusRestricted. + access, found = a.hasPeer(ctx, peer3) + require.True(t, found) + require.Equal(t, peer3Access, access) + + // peer4 should be found with peerStatusTemporary. + access, found = a.hasPeer(ctx, peer4) + require.True(t, found) + require.Equal(t, peer4Access, access) + + // peer5 should NOT be found. + access, found = a.hasPeer(ctx, peer5) + require.False(t, found) + require.Equal(t, peerStatusRestricted, access) +} + +// TestAddPeerAccessInbound asserts the num of slots is correctly incremented +// only for a new inbound peer with restricted access. +func TestAddPeerAccessInbound(t *testing.T) { + t.Parallel() + + // Create a testing accessMan. + a := &accessMan{ + peerChanInfo: make(map[string]channeldb.ChanCount), + peerScores: make(map[string]peerSlotStatus), + } + + // Create a testing key. + priv, err := btcec.NewPrivateKey() + require.NoError(t, err) + pub := priv.PubKey() + pubStr := string(pub.SerializeCompressed()) + + // Add this peer as an inbound peer with peerStatusRestricted. + a.addPeerAccess(pub, peerStatusRestricted, true) + + // Assert the accessMan's internal state. + // + // We expect to see one peer found in the score map, and one slot is + // taken, and this peer is not found in the counts map. + require.Len(t, a.peerScores, 1) + require.Equal(t, int64(1), a.numRestricted) + require.NotContains(t, a.peerChanInfo, pubStr) + + // The peer should be found in the score map. + score, ok := a.peerScores[pubStr] + require.True(t, ok) + + expecedScore := peerSlotStatus{state: peerStatusRestricted} + require.Equal(t, expecedScore, score) + + // Add this peer again, we expect the available slots to stay unchanged. + a.addPeerAccess(pub, peerStatusRestricted, true) + + // Assert the internal state is not changed. + require.Len(t, a.peerScores, 1) + require.Equal(t, int64(1), a.numRestricted) + require.NotContains(t, a.peerChanInfo, pubStr) + + // Reset the accessMan. + a = &accessMan{ + peerChanInfo: make(map[string]channeldb.ChanCount), + peerScores: make(map[string]peerSlotStatus), + } + + // Add this peer as an inbound peer with peerStatusTemporary. + a.addPeerAccess(pub, peerStatusTemporary, true) + + // Assert the accessMan's internal state. + // + // We expect to see one peer found in the score map, and no slot is + // taken since this peer is not restricted. + require.Len(t, a.peerScores, 1) + require.Equal(t, int64(0), a.numRestricted) + + // NOTE: in reality this is not possible as the peer must have been put + // into the map `peerChanInfo` before its perm can be upgraded. + require.NotContains(t, a.peerChanInfo, pubStr) + + // The peer should be found in the score map. + score, ok = a.peerScores[pubStr] + require.True(t, ok) + + expecedScore = peerSlotStatus{state: peerStatusTemporary} + require.Equal(t, expecedScore, score) +} + +// TestAddPeerAccessOutbound asserts that outbound peer is not restricted and +// its perm is upgraded when it has peerStatusRestricted. +func TestAddPeerAccessOutbound(t *testing.T) { + t.Parallel() + + // Create a testing accessMan. + a := &accessMan{ + peerChanInfo: make(map[string]channeldb.ChanCount), + peerScores: make(map[string]peerSlotStatus), + } + + // Create a testing key. + priv, err := btcec.NewPrivateKey() + require.NoError(t, err) + pub := priv.PubKey() + pubStr := string(pub.SerializeCompressed()) + + // Add this peer as an outbound peer with peerStatusRestricted. + a.addPeerAccess(pub, peerStatusRestricted, false) + + // Assert the accessMan's internal state. + // + // We expect to see one peer found in the score map, and no slot is + // taken, and this peer is found in the counts map. + require.Len(t, a.peerScores, 1) + require.Equal(t, int64(0), a.numRestricted) + require.Contains(t, a.peerChanInfo, pubStr) + + // The peer should be found in the score map. + score, ok := a.peerScores[pubStr] + require.True(t, ok) + + // Its perm should be upgraded to temporary. + expecedScore := peerSlotStatus{state: peerStatusTemporary} + require.Equal(t, expecedScore, score) + + // The peer should be found in the peer counts map. + count, ok := a.peerChanInfo[pubStr] + require.True(t, ok) + + // The peer's count should be initialized correctly. + require.Zero(t, count.PendingOpenCount) + require.False(t, count.HasOpenOrClosedChan) + + // Add this peer again, we expect the available slots to stay unchanged. + a.addPeerAccess(pub, peerStatusRestricted, true) + + // Assert the internal state is not changed. + require.Len(t, a.peerScores, 1) + require.Equal(t, int64(0), a.numRestricted) + require.Contains(t, a.peerChanInfo, pubStr) + + // Reset the accessMan. + a = &accessMan{ + peerChanInfo: make(map[string]channeldb.ChanCount), + peerScores: make(map[string]peerSlotStatus), + } + + // Add this peer as an inbound peer with peerStatusTemporary. + a.addPeerAccess(pub, peerStatusTemporary, true) + + // Assert the accessMan's internal state. + // + // We expect to see one peer found in the score map, and no slot is + // taken since this peer is not restricted. + require.Len(t, a.peerScores, 1) + require.Equal(t, int64(0), a.numRestricted) + + // NOTE: in reality this is not possible as the peer must have been put + // into the map `peerChanInfo` before its perm can be upgraded. + require.NotContains(t, a.peerChanInfo, pubStr) + + // The peer should be found in the score map. + score, ok = a.peerScores[pubStr] + require.True(t, ok) + + expecedScore = peerSlotStatus{state: peerStatusTemporary} + require.Equal(t, expecedScore, score) +} + +// TestRemovePeerAccess asserts `removePeerAccess` correctly update the +// accessman's internal state based on the peer's status. +func TestRemovePeerAccess(t *testing.T) { + t.Parallel() + ctx := context.Background() + + // Create a testing accessMan. + a := &accessMan{ + peerChanInfo: make(map[string]channeldb.ChanCount), + peerScores: make(map[string]peerSlotStatus), + } + + // numRestrictedExpected specifies the final value to expect once the + // test finishes. + var numRestrictedExpected int + + // peer1 exists with an open channel, which should not be removed. Since + // it has protected status, the numRestricted should stay unchanged. + peer1 := "peer1" + a.peerChanInfo[peer1] = channeldb.ChanCount{ + HasOpenOrClosedChan: true, + } + peer1Access := peerStatusProtected + a.peerScores[peer1] = peerSlotStatus{state: peer1Access} + + // peer2 exists with a pending channel, which should not be removed. + // Since it has temporary status, the numRestricted should stay + // unchanged. + peer2 := "peer2" + a.peerChanInfo[peer2] = channeldb.ChanCount{ + PendingOpenCount: 1, + } + peer2Access := peerStatusTemporary + a.peerScores[peer2] = peerSlotStatus{state: peer2Access} + + // peer3 exists without any channels, which will be removed. Since it + // has restricted status, the numRestricted should be decremented. + peer3 := "peer3" + a.peerChanInfo[peer3] = channeldb.ChanCount{} + peer3Access := peerStatusRestricted + a.peerScores[peer3] = peerSlotStatus{state: peer3Access} + numRestrictedExpected-- + + // peer4 exists with a score and a temporary status, which will be + // removed. + peer4 := "peer4" + peer4Access := peerStatusTemporary + a.peerScores[peer4] = peerSlotStatus{state: peer4Access} + + // peer5 doesn't exist, removing it will be a NOOP. + peer5 := "peer5" + + // We now assert `removePeerAccess` behaves as expected. + // + // Remove peer1 should change nothing. + a.removePeerAccess(ctx, peer1) + + // peer1 should be removed from peerScores but not peerChanInfo. + _, found := a.peerScores[peer1] + require.False(t, found) + _, found = a.peerChanInfo[peer1] + require.True(t, found) + + // Remove peer2 should change nothing. + a.removePeerAccess(ctx, peer2) + + // peer2 should be removed from peerScores but not peerChanInfo. + _, found = a.peerScores[peer2] + require.False(t, found) + _, found = a.peerChanInfo[peer2] + require.True(t, found) + + // Remove peer3 should remove it from the maps. + a.removePeerAccess(ctx, peer3) + + // peer3 should be removed from peerScores and peerChanInfo. + _, found = a.peerScores[peer3] + require.False(t, found) + _, found = a.peerChanInfo[peer3] + require.False(t, found) + + // Remove peer4 should remove it from the maps. + a.removePeerAccess(ctx, peer4) + + // peer4 should be removed from peerScores and NOT be found in + // peerChanInfo. + _, found = a.peerScores[peer4] + require.False(t, found) + _, found = a.peerChanInfo[peer4] + require.False(t, found) + + // Remove peer5 should be NOOP. + a.removePeerAccess(ctx, peer5) + + // peer5 should NOT be found. + _, found = a.peerScores[peer5] + require.False(t, found) + _, found = a.peerChanInfo[peer5] + require.False(t, found) + + // Finally, assert the numRestricted is decremented as expected. Given + // we only have peer3 which has restricted access, it should decrement + // once. + // + // NOTE: The value is actually negative here, which is allowed in + // accessman. + require.EqualValues(t, numRestrictedExpected, a.numRestricted) +} diff --git a/build/version.go b/build/version.go index d7d07b7491e..a9e9affab38 100644 --- a/build/version.go +++ b/build/version.go @@ -47,11 +47,11 @@ const ( AppMinor uint = 19 // AppPatch defines the application patch for this binary. - AppPatch uint = 1 + AppPatch uint = 2 // AppPreRelease MUST only contain characters from semanticAlphabet per // the semantic versioning spec. - AppPreRelease = "beta" + AppPreRelease = "beta.rc1" ) func init() { diff --git a/chainio/dispatcher.go b/chainio/dispatcher.go index f1fce35b531..0d56752b8e8 100644 --- a/chainio/dispatcher.go +++ b/chainio/dispatcher.go @@ -135,6 +135,14 @@ func (b *BlockbeatDispatcher) Stop() { } func (b *BlockbeatDispatcher) log() btclog.Logger { + // There's no guarantee that the `b.beat` is initialized when the + // dispatcher shuts down, especially in the case where the node is + // running as a remote signer, which doesn't have a chainbackend. In + // that case we will use the package logger. + if b.beat == nil { + return clog + } + return b.beat.logger() } diff --git a/chainntnfs/bitcoindnotify/bitcoind.go b/chainntnfs/bitcoindnotify/bitcoind.go index 1dae6899559..3cf53978c96 100644 --- a/chainntnfs/bitcoindnotify/bitcoind.go +++ b/chainntnfs/bitcoindnotify/bitcoind.go @@ -833,8 +833,16 @@ func (b *BitcoindNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, return nil, err } - if uint32(blockHeight) > ntfn.HistoricalDispatch.StartHeight { - ntfn.HistoricalDispatch.StartHeight = uint32(blockHeight) + spentHeight := uint32(blockHeight) + chainntnfs.Log.Debugf("Outpoint(%v) has spent at height %v", + outpoint, spentHeight) + + // Since the tx has already been spent at spentHeight, the + // heightHint specified by the caller is no longer relevant. We + // now update the starting height to be the spent height to make + // sure we won't miss it in the rescan. + if spentHeight != ntfn.HistoricalDispatch.StartHeight { + ntfn.HistoricalDispatch.StartHeight = spentHeight } } diff --git a/chainntnfs/btcdnotify/btcd.go b/chainntnfs/btcdnotify/btcd.go index 3c7843133a9..91178044cfb 100644 --- a/chainntnfs/btcdnotify/btcd.go +++ b/chainntnfs/btcdnotify/btcd.go @@ -933,15 +933,25 @@ func (b *BtcdNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, "block %v: %v", blockHash, err) } - if uint32(blockHeader.Height) > ntfn.HistoricalDispatch.StartHeight { + spentHeight := uint32(blockHeader.Height) + chainntnfs.Log.Debugf("Outpoint(%v) has spent at height %v", + outpoint, spentHeight) + + // Since the tx has already been spent at spentHeight, the + // heightHint specified by the caller is no longer relevant. We + // now update the starting height to be the spent height to make + // sure we won't miss it in the rescan. + if spentHeight != ntfn.HistoricalDispatch.StartHeight { startHash, err = b.chainConn.GetBlockHash( - int64(blockHeader.Height), + int64(spentHeight), ) if err != nil { return nil, fmt.Errorf("unable to get block "+ "hash for height %d: %v", blockHeader.Height, err) } + + ntfn.HistoricalDispatch.StartHeight = spentHeight } } diff --git a/chainntnfs/interface.go b/chainntnfs/interface.go index 1b8a5acb50f..798a2f4277b 100644 --- a/chainntnfs/interface.go +++ b/chainntnfs/interface.go @@ -70,28 +70,28 @@ func (t TxConfStatus) String() string { } } -// notifierOptions is a set of functional options that allow callers to further +// NotifierOptions is a set of functional options that allow callers to further // modify the type of chain event notifications they receive. -type notifierOptions struct { - // includeBlock if true, then the dispatched confirmation notification +type NotifierOptions struct { + // IncludeBlock if true, then the dispatched confirmation notification // will include the block that mined the transaction. - includeBlock bool + IncludeBlock bool } -// defaultNotifierOptions returns the set of default options for the notifier. -func defaultNotifierOptions() *notifierOptions { - return ¬ifierOptions{} +// DefaultNotifierOptions returns the set of default options for the notifier. +func DefaultNotifierOptions() *NotifierOptions { + return &NotifierOptions{} } // NotifierOption is a functional option that allows a caller to modify the // events received from the notifier. -type NotifierOption func(*notifierOptions) +type NotifierOption func(*NotifierOptions) -// WithIncludeBlock is an optional argument that allows the calelr to specify +// WithIncludeBlock is an optional argument that allows the caller to specify // that the block that mined a transaction should be included in the response. func WithIncludeBlock() NotifierOption { - return func(o *notifierOptions) { - o.includeBlock = true + return func(o *NotifierOptions) { + o.IncludeBlock = true } } diff --git a/chainntnfs/txnotifier.go b/chainntnfs/txnotifier.go index 12366abf955..8b5b8dc8be5 100644 --- a/chainntnfs/txnotifier.go +++ b/chainntnfs/txnotifier.go @@ -559,7 +559,7 @@ func NewTxNotifier(startHeight uint32, reorgSafetyLimit uint32, // and register a confirmation notification. func (n *TxNotifier) newConfNtfn(txid *chainhash.Hash, pkScript []byte, numConfs, heightHint uint32, - opts *notifierOptions) (*ConfNtfn, error) { + opts *NotifierOptions) (*ConfNtfn, error) { // An accompanying output script must always be provided. if len(pkScript) == 0 { @@ -593,7 +593,7 @@ func (n *TxNotifier) newConfNtfn(txid *chainhash.Hash, n.CancelConf(confRequest, confID) }), HeightHint: heightHint, - includeBlock: opts.includeBlock, + includeBlock: opts.IncludeBlock, numConfsLeft: numConfs, }, nil } @@ -616,7 +616,7 @@ func (n *TxNotifier) RegisterConf(txid *chainhash.Hash, pkScript []byte, default: } - opts := defaultNotifierOptions() + opts := DefaultNotifierOptions() for _, optFunc := range optFuncs { optFunc(opts) } diff --git a/chainreg/chainregistry.go b/chainreg/chainregistry.go index 3f9f738b26a..6ee274d68ab 100644 --- a/chainreg/chainregistry.go +++ b/chainreg/chainregistry.go @@ -878,8 +878,8 @@ var ( ChainDNSSeeds = map[chainhash.Hash][][2]string{ BitcoinMainnetGenesis: { { - "nodes.lightning.directory", - "soa.nodes.lightning.directory", + "nodes.lightning.wiki", + "soa.nodes.lightning.wiki", }, { "lseed.bitcoinstats.com", @@ -888,14 +888,22 @@ var ( BitcoinTestnetGenesis: { { - "test.nodes.lightning.directory", - "soa.nodes.lightning.directory", + "test.nodes.lightning.wiki", + "soa.nodes.lightning.wiki", + }, + }, + + BitcoinTestnet4Genesis: { + { + "test4.nodes.lightning.wiki", + "soa.nodes.lightning.wiki", }, }, BitcoinSignetGenesis: { { - "ln.signet.secp.tech", + "signet.nodes.lightning.wiki", + "soa.nodes.lightning.wiki", }, }, } diff --git a/channeldb/db.go b/channeldb/db.go index 6b92f2287fb..6b698507d61 100644 --- a/channeldb/db.go +++ b/channeldb/db.go @@ -3,6 +3,7 @@ package channeldb import ( "bytes" "encoding/binary" + "errors" "fmt" "net" "os" @@ -11,7 +12,6 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/walletdb" - "github.com/go-errors/errors" mig "github.com/lightningnetwork/lnd/channeldb/migration" "github.com/lightningnetwork/lnd/channeldb/migration12" "github.com/lightningnetwork/lnd/channeldb/migration13" @@ -28,6 +28,7 @@ import ( "github.com/lightningnetwork/lnd/channeldb/migration31" "github.com/lightningnetwork/lnd/channeldb/migration32" "github.com/lightningnetwork/lnd/channeldb/migration33" + "github.com/lightningnetwork/lnd/channeldb/migration34" "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11" "github.com/lightningnetwork/lnd/clock" graphdb "github.com/lightningnetwork/lnd/graph/db" @@ -73,12 +74,14 @@ type mandatoryVersion struct { // optional migrations. type MigrationConfig interface { migration30.MigrateRevLogConfig + migration34.MigrationConfig } // MigrationConfigImpl is a super set of all the various migration configs and // an implementation of MigrationConfig. type MigrationConfigImpl struct { migration30.MigrateRevLogConfigImpl + migration34.MigrationConfigImpl } // optionalMigration defines an optional migration function. When a migration @@ -307,13 +310,23 @@ var ( // to determine its state. optionalVersions = []optionalVersion{ { - name: "prune revocation log", + name: "prune_revocation_log", migration: func(db kvdb.Backend, cfg MigrationConfig) error { return migration30.MigrateRevocationLog(db, cfg) }, }, + { + name: "gc_decayed_log", + migration: func(db kvdb.Backend, + cfg MigrationConfig) error { + + return migration34.MigrateDecayedLog( + db, cfg, + ) + }, + }, } // Big endian is the preferred byte order, due to cursor scans over @@ -479,6 +492,12 @@ func initChannelDB(db kvdb.Backend) error { return err } + for _, tlb := range dbTopLevelBuckets { + if _, err := tx.CreateTopLevelBucket(tlb); err != nil { + return err + } + } + meta := &Meta{} // Check if DB is already initialized. err := FetchMeta(meta, tx) @@ -486,12 +505,6 @@ func initChannelDB(db kvdb.Backend) error { return nil } - for _, tlb := range dbTopLevelBuckets { - if _, err := tx.CreateTopLevelBucket(tlb); err != nil { - return err - } - } - meta.DbVersionNumber = getLatestDBVersion(dbVersions) return putMeta(meta, tx) }, func() {}) @@ -742,7 +755,7 @@ type ChanCount struct { func (c *ChannelStateDB) FetchPermAndTempPeers( chainHash []byte) (map[string]ChanCount, error) { - peerCounts := make(map[string]ChanCount) + peerChanInfo := make(map[string]ChanCount) err := kvdb.View(c.backend, func(tx kvdb.RTx) error { openChanBucket := tx.ReadBucket(openChannelBucket) @@ -828,7 +841,7 @@ func (c *ChannelStateDB) FetchPermAndTempPeers( HasOpenOrClosedChan: isPermPeer, PendingOpenCount: pendingOpenCount, } - peerCounts[string(nodePub)] = peerCount + peerChanInfo[string(nodePub)] = peerCount return nil }) @@ -892,15 +905,15 @@ func (c *ChannelStateDB) FetchPermAndTempPeers( remoteSer := remotePub.SerializeCompressed() remoteKey := string(remoteSer) - count, exists := peerCounts[remoteKey] + count, exists := peerChanInfo[remoteKey] if exists { count.HasOpenOrClosedChan = true - peerCounts[remoteKey] = count + peerChanInfo[remoteKey] = count } else { peerCount := ChanCount{ HasOpenOrClosedChan: true, } - peerCounts[remoteKey] = peerCount + peerChanInfo[remoteKey] = peerCount } } @@ -912,10 +925,10 @@ func (c *ChannelStateDB) FetchPermAndTempPeers( return nil }, func() { - clear(peerCounts) + clear(peerChanInfo) }) - return peerCounts, err + return peerChanInfo, err } // channelSelector describes a function that takes a chain-hash bucket from @@ -1728,10 +1741,8 @@ func (d *DB) syncVersions(versions []mandatoryVersion) error { }, func() {}) } -// applyOptionalVersions takes a config to determine whether the optional -// migrations will be applied. -// -// NOTE: only support the prune_revocation_log optional migration atm. +// applyOptionalVersions applies the optional migrations to the database if +// specified in the config. func (d *DB) applyOptionalVersions(cfg OptionalMiragtionConfig) error { // TODO(yy): need to design the db to support dry run for optional // migrations. @@ -1748,50 +1759,71 @@ func (d *DB) applyOptionalVersions(cfg OptionalMiragtionConfig) error { Versions: make(map[uint64]string), } } else { - return err + return fmt.Errorf("unable to fetch optional "+ + "meta: %w", err) } } - log.Infof("Checking for optional update: prune_revocation_log=%v, "+ - "db_version=%s", cfg.PruneRevocationLog, om) - - // Exit early if the optional migration is not specified. - if !cfg.PruneRevocationLog { - return nil - } - - // Exit early if the optional migration has already been applied. - if _, ok := om.Versions[0]; ok { - return nil - } - - // Get the optional version. - version := optionalVersions[0] - log.Infof("Performing database optional migration: %s", version.name) - + // migrationCfg is the parent configuration which implements the config + // interfaces of all the single optional migrations. migrationCfg := &MigrationConfigImpl{ migration30.MigrateRevLogConfigImpl{ NoAmountData: d.noRevLogAmtData, }, + migration34.MigrationConfigImpl{ + DecayedLog: cfg.DecayedLog, + }, } - // Migrate the data. - if err := version.migration(d, migrationCfg); err != nil { - log.Errorf("Unable to apply optional migration: %s, error: %v", - version.name, err) - return err - } + log.Infof("Applying %d optional migrations", len(optionalVersions)) - // Update the optional meta. Notice that unlike the mandatory db - // migrations where we perform the migration and updating meta in a - // single db transaction, we use different transactions here. Even when - // the following update is failed, we should be fine here as we would - // re-run the optional migration again, which is a noop, during next - // startup. - om.Versions[0] = version.name - if err := d.putOptionalMeta(om); err != nil { - log.Errorf("Unable to update optional meta: %v", err) - return err + // Apply the optional migrations if requested. + for number, version := range optionalVersions { + log.Infof("Checking for optional update: name=%v", version.name) + + // Exit early if the optional migration is not specified. + if !cfg.MigrationFlags[number] { + log.Debugf("Skipping optional migration: name=%s as "+ + "it is not specified in the config", + version.name) + + continue + } + + // Exit early if the optional migration has already been + // applied. + if _, ok := om.Versions[uint64(number)]; ok { + log.Debugf("Skipping optional migration: name=%s as "+ + "it has already been applied", version.name) + + continue + } + + log.Infof("Performing database optional migration: %s", + version.name) + + // Call the migration function for the specific optional + // migration. + if err := version.migration(d, migrationCfg); err != nil { + log.Errorf("Unable to apply optional migration: %s, "+ + "error: %v", version.name, err) + return err + } + + // Update the optional meta. Notice that unlike the mandatory db + // migrations where we perform the migration and updating meta + // in a single db transaction, we use different transactions + // here. Even when the following update is failed, we should be + // fine here as we would re-run the optional migration again, + // which is a noop, during next startup. + om.Versions[uint64(number)] = version.name + if err := d.putOptionalMeta(om); err != nil { + log.Errorf("Unable to update optional meta: %v", err) + return err + } + + log.Infof("Successfully applied optional migration: %s", + version.name) } return nil diff --git a/channeldb/db_test.go b/channeldb/db_test.go index e175ea1fb1e..98a530457bb 100644 --- a/channeldb/db_test.go +++ b/channeldb/db_test.go @@ -768,16 +768,16 @@ func TestFetchPermTempPeer(t *testing.T) { ) // Fetch the ChanCount for our peers. - peerCounts, err := cdb.FetchPermAndTempPeers(key[:]) + peerChanInfo, err := cdb.FetchPermAndTempPeers(key[:]) require.NoError(t, err, "unable to fetch perm and temp peers") // There should only be three entries. - require.Len(t, peerCounts, 3) + require.Len(t, peerChanInfo, 3) // The first entry should have OpenClosed set to true and Pending set // to 0. - count1, found := peerCounts[string(pubKey1.SerializeCompressed())] - require.True(t, found, "unable to find peer 1 in peerCounts") + count1, found := peerChanInfo[string(pubKey1.SerializeCompressed())] + require.True(t, found, "unable to find peer 1 in peerChanInfo") require.True( t, count1.HasOpenOrClosedChan, "couldn't find peer 1's channels", @@ -787,15 +787,15 @@ func TestFetchPermTempPeer(t *testing.T) { "peer 1 doesn't have 0 pending-open", ) - count2, found := peerCounts[string(pubKey2.SerializeCompressed())] - require.True(t, found, "unable to find peer 2 in peerCounts") + count2, found := peerChanInfo[string(pubKey2.SerializeCompressed())] + require.True(t, found, "unable to find peer 2 in peerChanInfo") require.False( t, count2.HasOpenOrClosedChan, "found erroneous channels", ) require.Equal(t, uint64(1), count2.PendingOpenCount) - count3, found := peerCounts[string(pubKey3.SerializeCompressed())] - require.True(t, found, "unable to find peer 3 in peerCounts") + count3, found := peerChanInfo[string(pubKey3.SerializeCompressed())] + require.True(t, found, "unable to find peer 3 in peerChanInfo") require.True( t, count3.HasOpenOrClosedChan, "couldn't find peer 3's channels", diff --git a/channeldb/forwarding_log.go b/channeldb/forwarding_log.go index f527b78dd01..18cc6cf3277 100644 --- a/channeldb/forwarding_log.go +++ b/channeldb/forwarding_log.go @@ -2,11 +2,13 @@ package channeldb import ( "bytes" + "errors" "io" "sort" "time" "github.com/btcsuite/btcwallet/walletdb" + "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnwire" ) @@ -25,11 +27,12 @@ const ( // is as follows: // // * 8 byte incoming chan ID || 8 byte outgoing chan ID || 8 byte value in - // || 8 byte value out + // || 8 byte value out || 8 byte incoming htlc id || 8 byte + // outgoing htlc id // // From the value in and value out, callers can easily compute the // total fee extract from a forwarding event. - forwardingEventSize = 32 + forwardingEventSize = 48 // MaxResponseEvents is the max number of forwarding events that will // be returned by a single query response. This size was selected to @@ -78,14 +81,44 @@ type ForwardingEvent struct { // AmtOut is the amount of the outgoing HTLC. Subtracting the incoming // amount from this gives the total fees for this payment circuit. AmtOut lnwire.MilliSatoshi + + // IncomingHtlcID is the ID of the incoming HTLC in the payment circuit. + // If this is not set, the value will be nil. This field is added in + // v0.20 and is made optional to make it backward compatible with + // existing forwarding events created before it's introduction. + IncomingHtlcID fn.Option[uint64] + + // OutgoingHtlcID is the ID of the outgoing HTLC in the payment circuit. + // If this is not set, the value will be nil. This field is added in + // v0.20 and is made optional to make it backward compatible with + // existing forwarding events created before it's introduction. + OutgoingHtlcID fn.Option[uint64] } // encodeForwardingEvent writes out the target forwarding event to the passed // io.Writer, using the expected DB format. Note that the timestamp isn't // serialized as this will be the key value within the bucket. func encodeForwardingEvent(w io.Writer, f *ForwardingEvent) error { + // We check for the HTLC IDs if they are set. If they are not, + // from v0.20 upward, we return an error to make it clear they are + // required. + incomingID, err := f.IncomingHtlcID.UnwrapOrErr( + errors.New("incoming HTLC ID must be set"), + ) + if err != nil { + return err + } + + outgoingID, err := f.OutgoingHtlcID.UnwrapOrErr( + errors.New("outgoing HTLC ID must be set"), + ) + if err != nil { + return err + } + return WriteElements( w, f.IncomingChanID, f.OutgoingChanID, f.AmtIn, f.AmtOut, + incomingID, outgoingID, ) } @@ -94,9 +127,32 @@ func encodeForwardingEvent(w io.Writer, f *ForwardingEvent) error { // won't be decoded, as the caller is expected to set this due to the bucket // structure of the forwarding log. func decodeForwardingEvent(r io.Reader, f *ForwardingEvent) error { - return ReadElements( + // Decode the original fields of the forwarding event. + err := ReadElements( r, &f.IncomingChanID, &f.OutgoingChanID, &f.AmtIn, &f.AmtOut, ) + if err != nil { + return err + } + + // Decode the incoming and outgoing htlc IDs. For backward compatibility + // with older records that don't have these fields, we handle EOF by + // setting the ID to nil. Any other error is treated as a read failure. + var incomingHtlcID, outgoingHtlcID uint64 + err = ReadElements(r, &incomingHtlcID, &outgoingHtlcID) + switch { + case err == nil: + f.IncomingHtlcID = fn.Some(incomingHtlcID) + f.OutgoingHtlcID = fn.Some(outgoingHtlcID) + + return nil + + case errors.Is(err, io.EOF): + return nil + + default: + return err + } } // AddForwardingEvents adds a series of forwarding events to the database. diff --git a/channeldb/forwarding_log_test.go b/channeldb/forwarding_log_test.go index 7ac2dfbc5de..c86de52dbab 100644 --- a/channeldb/forwarding_log_test.go +++ b/channeldb/forwarding_log_test.go @@ -1,12 +1,15 @@ package channeldb import ( + "bytes" "math/rand" "reflect" "testing" "time" "github.com/davecgh/go-spew/spew" + "github.com/lightningnetwork/lnd/fn/v2" + "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnwire" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -41,6 +44,8 @@ func TestForwardingLogBasicStorageAndQuery(t *testing.T) { OutgoingChanID: lnwire.NewShortChanIDFromInt(uint64(rand.Int63())), AmtIn: lnwire.MilliSatoshi(rand.Int63()), AmtOut: lnwire.MilliSatoshi(rand.Int63()), + IncomingHtlcID: fn.Some(uint64(i)), + OutgoingHtlcID: fn.Some(uint64(i)), } timestamp = timestamp.Add(time.Minute * 10) @@ -109,6 +114,8 @@ func TestForwardingLogQueryOptions(t *testing.T) { OutgoingChanID: lnwire.NewShortChanIDFromInt(uint64(rand.Int63())), AmtIn: lnwire.MilliSatoshi(rand.Int63()), AmtOut: lnwire.MilliSatoshi(rand.Int63()), + IncomingHtlcID: fn.Some(uint64(i)), + OutgoingHtlcID: fn.Some(uint64(i)), } endTime = endTime.Add(time.Minute * 10) @@ -208,6 +215,8 @@ func TestForwardingLogQueryLimit(t *testing.T) { OutgoingChanID: lnwire.NewShortChanIDFromInt(uint64(rand.Int63())), AmtIn: lnwire.MilliSatoshi(rand.Int63()), AmtOut: lnwire.MilliSatoshi(rand.Int63()), + IncomingHtlcID: fn.Some(uint64(i)), + OutgoingHtlcID: fn.Some(uint64(i)), } endTime = endTime.Add(time.Minute * 10) @@ -317,6 +326,8 @@ func TestForwardingLogStoreEvent(t *testing.T) { OutgoingChanID: lnwire.NewShortChanIDFromInt(uint64(rand.Int63())), AmtIn: lnwire.MilliSatoshi(rand.Int63()), AmtOut: lnwire.MilliSatoshi(rand.Int63()), + IncomingHtlcID: fn.Some(uint64(i)), + OutgoingHtlcID: fn.Some(uint64(i)), } } @@ -360,3 +371,107 @@ func TestForwardingLogStoreEvent(t *testing.T) { } } } + +// TestForwardingLogDecodeForwardingEvent tests that we're able to decode +// forwarding events that don't have the new incoming and outgoing htlc +// indices. +func TestForwardingLogDecodeForwardingEvent(t *testing.T) { + t.Parallel() + + // First, we'll set up a test database, and use that to instantiate the + // forwarding event log that we'll be using for the duration of the + // test. + db, err := MakeTestDB(t) + require.NoError(t, err) + + log := ForwardingLog{ + db: db, + } + + initialTime := time.Unix(1234, 0) + endTime := time.Unix(1234, 0) + + // We'll create forwarding events that don't have the incoming and + // outgoing htlc indices. + numEvents := 10 + events := make([]ForwardingEvent, numEvents) + for i := range numEvents { + events[i] = ForwardingEvent{ + Timestamp: endTime, + IncomingChanID: lnwire.NewShortChanIDFromInt( + uint64(rand.Int63()), + ), + OutgoingChanID: lnwire.NewShortChanIDFromInt( + uint64(rand.Int63()), + ), + AmtIn: lnwire.MilliSatoshi(rand.Int63()), + AmtOut: lnwire.MilliSatoshi(rand.Int63()), + } + + endTime = endTime.Add(time.Minute * 10) + } + + // Now that all of our events are constructed, we'll add them to the + // database. + err = writeOldFormatEvents(db, events) + require.NoError(t, err) + + // With all of our events added, we'll now query for them and ensure + // that the incoming and outgoing htlc indices are set to 0 (default + // value) for all events. + eventQuery := ForwardingEventQuery{ + StartTime: initialTime, + EndTime: endTime, + IndexOffset: 0, + NumMaxEvents: uint32(numEvents * 3), + } + timeSlice, err := log.Query(eventQuery) + require.NoError(t, err) + require.Equal(t, numEvents, len(timeSlice.ForwardingEvents)) + + for _, event := range timeSlice.ForwardingEvents { + require.Equal(t, fn.None[uint64](), event.IncomingHtlcID) + require.Equal(t, fn.None[uint64](), event.OutgoingHtlcID) + } +} + +// writeOldFormatEvents writes forwarding events to the database in the old +// format (without incoming and outgoing htlc indices). This is used to test +// backward compatibility. +func writeOldFormatEvents(db *DB, events []ForwardingEvent) error { + return kvdb.Batch(db.Backend, func(tx kvdb.RwTx) error { + bucket, err := tx.CreateTopLevelBucket(forwardingLogBucket) + if err != nil { + return err + } + + for _, event := range events { + var timestamp [8]byte + byteOrder.PutUint64(timestamp[:], uint64( + event.Timestamp.UnixNano(), + )) + + // Use the old event size (32 bytes) for writing old + // format events. + var eventBytes [32]byte + eventBuf := bytes.NewBuffer(eventBytes[0:0:32]) + + // Write only the original fields without incoming and + // outgoing htlc indices. + if err := WriteElements( + eventBuf, event.IncomingChanID, + event.OutgoingChanID, event.AmtIn, event.AmtOut, + ); err != nil { + return err + } + + if err := bucket.Put( + timestamp[:], eventBuf.Bytes(), + ); err != nil { + return err + } + } + + return nil + }) +} diff --git a/channeldb/log.go b/channeldb/log.go index b423154d3ed..fb2a85d0179 100644 --- a/channeldb/log.go +++ b/channeldb/log.go @@ -12,6 +12,7 @@ import ( "github.com/lightningnetwork/lnd/channeldb/migration31" "github.com/lightningnetwork/lnd/channeldb/migration32" "github.com/lightningnetwork/lnd/channeldb/migration33" + "github.com/lightningnetwork/lnd/channeldb/migration34" "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11" "github.com/lightningnetwork/lnd/kvdb" ) @@ -46,5 +47,6 @@ func UseLogger(logger btclog.Logger) { migration31.UseLogger(logger) migration32.UseLogger(logger) migration33.UseLogger(logger) + migration34.UseLogger(logger) kvdb.UseLogger(logger) } diff --git a/channeldb/meta.go b/channeldb/meta.go index a32ab81ed17..127acf51fc6 100644 --- a/channeldb/meta.go +++ b/channeldb/meta.go @@ -4,6 +4,8 @@ import ( "bytes" "errors" "fmt" + "slices" + "strings" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/tlv" @@ -30,6 +32,10 @@ var ( // ErrMarkerNotPresent is the error that is returned if the queried // marker is not present in the given database. ErrMarkerNotPresent = errors.New("marker not present") + + // ErrInvalidOptionalVersion is the error that is returned if the + // optional version persisted in the database is invalid. + ErrInvalidOptionalVersion = errors.New("invalid optional version") ) // Meta structure holds the database meta information. @@ -104,15 +110,28 @@ type OptionalMeta struct { Versions map[uint64]string } +// String returns a string representation of the optional meta. func (om *OptionalMeta) String() string { - s := "" - for index, name := range om.Versions { - s += fmt.Sprintf("%d: %s", index, name) + if len(om.Versions) == 0 { + return "empty" + } + + // Create a slice of indices to sort + indices := make([]uint64, 0, len(om.Versions)) + for index := range om.Versions { + indices = append(indices, index) } - if s == "" { - s = "empty" + + // Sort the indices in ascending order. + slices.Sort(indices) + + // Create the string parts in sorted order. + parts := make([]string, len(indices)) + for i, index := range indices { + parts[i] = fmt.Sprintf("%d: %s", index, om.Versions[index]) } - return s + + return strings.Join(parts, ", ") } // fetchOptionalMeta reads the optional meta from the database. @@ -146,7 +165,20 @@ func (d *DB) fetchOptionalMeta() (*OptionalMeta, error) { if err != nil { return err } - om.Versions[version] = optionalVersions[i].name + + // This check would not allow to downgrade LND software + // to a version with an optional migration when an + // optional migration not known to the current version + // has already been applied. + if version >= uint64(len(optionalVersions)) { + return fmt.Errorf("optional version read "+ + "from db is %d, but only optional "+ + "migrations up to %d are known: %w", + version, len(optionalVersions)-1, + ErrInvalidOptionalVersion) + } + + om.Versions[version] = optionalVersions[version].name } return nil @@ -174,8 +206,12 @@ func (d *DB) putOptionalMeta(om *OptionalMeta) error { return err } - // Write the version indexes. + // Write the version indexes of the single migrations. for v := range om.Versions { + if v >= uint64(len(optionalVersions)) { + return ErrInvalidOptionalVersion + } + err := tlv.WriteVarInt(&b, v, &[8]byte{}) if err != nil { return err diff --git a/channeldb/meta_test.go b/channeldb/meta_test.go index 5b6bd29a940..0a9f1ba27bc 100644 --- a/channeldb/meta_test.go +++ b/channeldb/meta_test.go @@ -2,10 +2,11 @@ package channeldb import ( "bytes" + "errors" + "fmt" "testing" "github.com/btcsuite/btcwallet/walletdb" - "github.com/go-errors/errors" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/kvdb" "github.com/stretchr/testify/require" @@ -56,7 +57,7 @@ func applyMigration(t *testing.T, beforeMigration, afterMigration func(d *DB), if dryRun && r != ErrDryRunMigrationOK { t.Fatalf("expected dry run migration OK") } - err = errors.New(r) + err = fmt.Errorf("%v", r) } if err == nil && shouldFail { @@ -506,6 +507,7 @@ func TestOptionalMeta(t *testing.T) { om = &OptionalMeta{ Versions: map[uint64]string{ 0: optionalVersions[0].name, + 1: optionalVersions[1].name, }, } err = db.putOptionalMeta(om) @@ -514,29 +516,40 @@ func TestOptionalMeta(t *testing.T) { om1, err := db.fetchOptionalMeta() require.NoError(t, err, "error getting optional meta") require.Equal(t, om, om1, "unexpected empty versions") - require.Equal(t, "0: prune revocation log", om.String()) + require.Equal( + t, "0: prune_revocation_log, 1: gc_decayed_log", + om1.String(), + ) } // TestApplyOptionalVersions checks that the optional migration is applied as // expected based on the config. +// +// NOTE: Cannot be run in parallel because we alter the optionalVersions +// global variable which could be used by other tests. func TestApplyOptionalVersions(t *testing.T) { - t.Parallel() - db, err := MakeTestDB(t) require.NoError(t, err) - // Overwrite the migration function so we can count how many times the - // migration has happened. - migrateCount := 0 - optionalVersions[0].migration = func(_ kvdb.Backend, - _ MigrationConfig) error { + // migrateCount is the number of migrations that have been run. It + // counts the number of times a migration function is called. + var migrateCount int - migrateCount++ - return nil + // Modify all migrations to track their execution. + for i := range optionalVersions { + optionalVersions[i].migration = func(_ kvdb.Backend, + _ MigrationConfig) error { + + migrateCount++ + + return nil + } } - // Test that when the flag is false, no migration happens. - cfg := OptionalMiragtionConfig{} + // All migrations are disabled by default. + cfg := NewOptionalMiragtionConfig() + + // Run the optional migrations. err = db.applyOptionalVersions(cfg) require.NoError(t, err, "failed to apply optional migration") require.Equal(t, 0, migrateCount, "expected no migration") @@ -544,13 +557,18 @@ func TestApplyOptionalVersions(t *testing.T) { // Check the optional meta is not updated. om, err := db.fetchOptionalMeta() require.NoError(t, err, "error getting optional meta") - require.Empty(t, om.Versions, "expected empty versions") - // Test that when specified, the optional migration is applied. - cfg.PruneRevocationLog = true + // Enable all optional migrations. + for i := range cfg.MigrationFlags { + cfg.MigrationFlags[i] = true + } + err = db.applyOptionalVersions(cfg) require.NoError(t, err, "failed to apply optional migration") - require.Equal(t, 1, migrateCount, "expected migration") + require.Equal( + t, len(optionalVersions), migrateCount, + "expected all migrations to be run", + ) // Fetch the updated optional meta. om, err = db.fetchOptionalMeta() @@ -560,16 +578,20 @@ func TestApplyOptionalVersions(t *testing.T) { omExpected := &OptionalMeta{ Versions: map[uint64]string{ 0: optionalVersions[0].name, + 1: optionalVersions[1].name, }, } require.Equal(t, omExpected, om, "unexpected empty versions") - // Test that though specified, the optional migration is not run since - // it's already been applied. - cfg.PruneRevocationLog = true + // We make sure running the migrations again does not call the + // migrations again because the meta data should signal that they have + // already been run. err = db.applyOptionalVersions(cfg) require.NoError(t, err, "failed to apply optional migration") - require.Equal(t, 1, migrateCount, "expected no migration") + require.Equal( + t, len(optionalVersions), migrateCount, + "expected all migrations to be run", + ) } // TestFetchMeta tests that the FetchMeta returns the latest DB version for a diff --git a/channeldb/migration/lnwire21/lnwire.go b/channeldb/migration/lnwire21/lnwire.go index a88c5e50b59..e3f96e2cdab 100644 --- a/channeldb/migration/lnwire21/lnwire.go +++ b/channeldb/migration/lnwire21/lnwire.go @@ -3,6 +3,7 @@ package lnwire import ( "bytes" "encoding/binary" + "errors" "fmt" "image/color" "io" @@ -13,7 +14,6 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/tor" ) diff --git a/channeldb/migration/lnwire21/onion_error.go b/channeldb/migration/lnwire21/onion_error.go index e9d1f8f0cb1..1f5628212c7 100644 --- a/channeldb/migration/lnwire21/onion_error.go +++ b/channeldb/migration/lnwire21/onion_error.go @@ -9,7 +9,6 @@ import ( "io" "github.com/davecgh/go-spew/spew" - "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/tlv" ) @@ -1451,7 +1450,7 @@ func makeEmptyOnionError(code FailCode) (FailureMessage, error) { return &FailInvalidBlinding{}, nil default: - return nil, errors.Errorf("unknown error code: %v", code) + return nil, fmt.Errorf("unknown error code: %v", code) } } diff --git a/channeldb/migration34/log.go b/channeldb/migration34/log.go new file mode 100644 index 00000000000..891ed2b19f3 --- /dev/null +++ b/channeldb/migration34/log.go @@ -0,0 +1,14 @@ +package migration34 + +import ( + "github.com/btcsuite/btclog/v2" +) + +// log is a logger that is initialized as disabled. This means the package will +// not perform any logging by default until a logger is set. +var log = btclog.Disabled + +// UseLogger uses a specified Logger to output package logging info. +func UseLogger(logger btclog.Logger) { + log = logger +} diff --git a/channeldb/migration34/migration.go b/channeldb/migration34/migration.go new file mode 100644 index 00000000000..da5df488c35 --- /dev/null +++ b/channeldb/migration34/migration.go @@ -0,0 +1,76 @@ +package migration34 + +import ( + "errors" + "fmt" + + "github.com/lightningnetwork/lnd/kvdb" +) + +// Migration34 is an optional migration that garbage collects the decayed log +// in particular the `batch-replay` bucket. However we did choose to use an +// optional migration which defaults to true because the decayed log db is +// separate from the channeldb and if we would have implemented it as a +// required migration, then it would have required a bigger change to the +// codebase. +// +// Most of the decayed log db will shrink significantly after this migration +// because the other bucket called `shared-secrets` is garbage collected +// continuously and the `batch-replay` bucket will be deleted. + +var ( + // batchReplayBucket is a bucket that maps batch identifiers to + // serialized ReplaySets. This is used to give idempotency in the event + // that a batch is processed more than once. + batchReplayBucket = []byte("batch-replay") +) + +// MigrationConfig is the interface for the migration configuration. +type MigrationConfig interface { + GetDecayedLog() kvdb.Backend +} + +// MigrationConfigImpl is the implementation of the migration configuration. +type MigrationConfigImpl struct { + DecayedLog kvdb.Backend +} + +// GetDecayedLog returns the decayed log backend. +func (c *MigrationConfigImpl) GetDecayedLog() kvdb.Backend { + return c.DecayedLog +} + +// MigrateDecayedLog migrates the decayed log. The migration deletes the +// `batch-replay` bucket, which is no longer used. +// +// NOTE: This migration is idempotent. If the bucket does not exist, then this +// migration is a no-op. +func MigrateDecayedLog(db kvdb.Backend, cfg MigrationConfig) error { + decayedLog := cfg.GetDecayedLog() + + // Make sure we have a reference to the decayed log. + if decayedLog == nil { + return fmt.Errorf("decayed log backend is not available") + } + + log.Info("Migrating decayed log...") + err := decayedLog.Update(func(tx kvdb.RwTx) error { + err := tx.DeleteTopLevelBucket(batchReplayBucket) + if err != nil && !errors.Is(err, kvdb.ErrBucketNotFound) { + return fmt.Errorf("deleting top level bucket %s: %w", + batchReplayBucket, err) + } + + log.Debugf("top level bucket %s deleted", batchReplayBucket) + + return nil + }, func() {}) + + if err != nil { + return fmt.Errorf("failed to migrate decayed log: %w", err) + } + + log.Info("Decayed log migrated successfully") + + return nil +} diff --git a/channeldb/migration_01_to_11/meta_test.go b/channeldb/migration_01_to_11/meta_test.go index 513b90d565c..6c0016610b6 100644 --- a/channeldb/migration_01_to_11/meta_test.go +++ b/channeldb/migration_01_to_11/meta_test.go @@ -1,9 +1,9 @@ package migration_01_to_11 import ( + "fmt" "testing" - "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/kvdb" ) @@ -33,7 +33,7 @@ func applyMigration(t *testing.T, beforeMigration, afterMigration func(d *DB), defer func() { if r := recover(); r != nil { - err = errors.New(r) + err = fmt.Errorf("%v", r) } if err == nil && shouldFail { diff --git a/channeldb/migration_01_to_11/migrations_test.go b/channeldb/migration_01_to_11/migrations_test.go index 9c47bdcdf2b..7c28d203903 100644 --- a/channeldb/migration_01_to_11/migrations_test.go +++ b/channeldb/migration_01_to_11/migrations_test.go @@ -4,6 +4,7 @@ import ( "bytes" "crypto/sha256" "encoding/binary" + "errors" "fmt" "math/rand" "reflect" @@ -12,7 +13,6 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/davecgh/go-spew/spew" - "github.com/go-errors/errors" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lntypes" diff --git a/channeldb/options.go b/channeldb/options.go index 6e631e2cb9c..b00ba1f59aa 100644 --- a/channeldb/options.go +++ b/channeldb/options.go @@ -2,6 +2,7 @@ package channeldb import ( "github.com/lightningnetwork/lnd/clock" + "github.com/lightningnetwork/lnd/kvdb" ) const ( @@ -25,9 +26,26 @@ const ( // OptionalMiragtionConfig defines the flags used to signal whether a // particular migration needs to be applied. type OptionalMiragtionConfig struct { - // PruneRevocationLog specifies that the revocation log migration needs - // to be applied. - PruneRevocationLog bool + // MigrationFlags is an array of booleans indicating which optional + // migrations should be run. The index in the array corresponds to the + // migration number in optionalVersions. + MigrationFlags []bool + + // DecayedLog is a reference to the decayed log database. The channeldb + // is inherently part of the optional migration flow so there is no need + // to specify it here. The DecayedLog is a separate database in case the + // kvdb backend is set to `bbolt`. And also for the kvdb SQL backend + // case it is a separate table therefore we need to reference it here + // as well to use the right query to access the decayed log. + DecayedLog kvdb.Backend +} + +// NewOptionalMiragtionConfig creates a new OptionalMiragtionConfig with the +// default migration flags. +func NewOptionalMiragtionConfig() OptionalMiragtionConfig { + return OptionalMiragtionConfig{ + MigrationFlags: make([]bool, len(optionalVersions)), + } } // Options holds parameters for tuning and customizing a channeldb.DB. @@ -62,7 +80,7 @@ type Options struct { // DefaultOptions returns an Options populated with default values. func DefaultOptions() Options { return Options{ - OptionalMiragtionConfig: OptionalMiragtionConfig{}, + OptionalMiragtionConfig: NewOptionalMiragtionConfig(), NoMigration: false, clock: clock.NewDefaultClock(), } @@ -124,6 +142,24 @@ func OptionStoreFinalHtlcResolutions( // revocation logs needs to be applied or not. func OptionPruneRevocationLog(prune bool) OptionModifier { return func(o *Options) { - o.OptionalMiragtionConfig.PruneRevocationLog = prune + o.OptionalMiragtionConfig.MigrationFlags[0] = prune + } +} + +// OptionWithDecayedLogDB sets the decayed log database reference which might +// be used for some migrations because generally we only touch the channeldb +// databases in the migrations, this is a way to allow also access to the +// decayed log database. +func OptionWithDecayedLogDB(decayedLog kvdb.Backend) OptionModifier { + return func(o *Options) { + o.OptionalMiragtionConfig.DecayedLog = decayedLog + } +} + +// OptionGcDecayedLog specifies whether the decayed log migration has to +// take place. +func OptionGcDecayedLog(noGc bool) OptionModifier { + return func(o *Options) { + o.OptionalMiragtionConfig.MigrationFlags[1] = !noGc } } diff --git a/channeldb/revocation_log.go b/channeldb/revocation_log.go index ea6eaf13f22..5a7f7a76bea 100644 --- a/channeldb/revocation_log.go +++ b/channeldb/revocation_log.go @@ -2,6 +2,7 @@ package channeldb import ( "bytes" + "encoding/binary" "errors" "io" "math" @@ -165,7 +166,7 @@ type HTLCEntry struct { CustomBlob tlv.OptionalRecordT[tlv.TlvType5, tlv.Blob] // HtlcIndex is the index of the HTLC in the channel. - HtlcIndex tlv.OptionalRecordT[tlv.TlvType6, uint16] + HtlcIndex tlv.OptionalRecordT[tlv.TlvType6, tlv.BigSizeT[uint64]] } // toTlvStream converts an HTLCEntry record into a tlv representation. @@ -182,7 +183,9 @@ func (h *HTLCEntry) toTlvStream() (*tlv.Stream, error) { records = append(records, r.Record()) }) - h.HtlcIndex.WhenSome(func(r tlv.RecordT[tlv.TlvType6, uint16]) { + h.HtlcIndex.WhenSome(func(r tlv.RecordT[tlv.TlvType6, + tlv.BigSizeT[uint64]]) { + records = append(records, r.Record()) }) @@ -207,8 +210,8 @@ func NewHTLCEntryFromHTLC(htlc HTLC) (*HTLCEntry, error) { Amt: tlv.NewRecordT[tlv.TlvType4]( tlv.NewBigSizeT(htlc.Amt.ToSatoshis()), ), - HtlcIndex: tlv.SomeRecordT(tlv.NewPrimitiveRecord[tlv.TlvType6]( - uint16(htlc.HtlcIndex), + HtlcIndex: tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType6]( + tlv.NewBigSizeT(htlc.HtlcIndex), )), } @@ -510,13 +513,22 @@ func deserializeRevocationLog(r io.Reader) (RevocationLog, error) { // deserializeHTLCEntries deserializes a list of HTLC entries based on tlv // format. func deserializeHTLCEntries(r io.Reader) ([]*HTLCEntry, error) { - var htlcs []*HTLCEntry + var ( + htlcs []*HTLCEntry + + // htlcIndexBlob defines the tlv record type to be used when + // decoding from the disk. We use it instead of the one defined + // in `HTLCEntry.HtlcIndex` as previously this field was encoded + // using `uint16`, thus we will read it as raw bytes and + // deserialize it further below. + htlcIndexBlob tlv.OptionalRecordT[tlv.TlvType6, tlv.Blob] + ) for { var htlc HTLCEntry customBlob := htlc.CustomBlob.Zero() - htlcIndex := htlc.HtlcIndex.Zero() + htlcIndex := htlcIndexBlob.Zero() // Create the tlv stream. records := []tlv.Record{ @@ -549,7 +561,14 @@ func deserializeHTLCEntries(r io.Reader) ([]*HTLCEntry, error) { } if t, ok := parsedTypes[htlcIndex.TlvType()]; ok && t == nil { - htlc.HtlcIndex = tlv.SomeRecordT(htlcIndex) + record, err := deserializeHtlcIndexCompatible( + htlcIndex.Val, + ) + if err != nil { + return nil, err + } + + htlc.HtlcIndex = record } // Append the entry. @@ -559,6 +578,55 @@ func deserializeHTLCEntries(r io.Reader) ([]*HTLCEntry, error) { return htlcs, nil } +// deserializeHtlcIndexCompatible takes raw bytes and decodes it into an +// optional record that's assigned to the entry's HtlcIndex. +// +// NOTE: previously this `HtlcIndex` was a tlv record that used `uint16` to +// encode its value. Given now its value is encoded using BigSizeT, and for any +// BigSizeT, its possible length values are 1, 3, 5, and 8. This means if the +// tlv record has a length of 2, we know for sure it must be an old record +// whose value was encoded using uint16. +func deserializeHtlcIndexCompatible(rawBytes []byte) ( + tlv.OptionalRecordT[tlv.TlvType6, tlv.BigSizeT[uint64]], error) { + + var ( + // record defines the record that's used by the HtlcIndex in the + // entry. + record tlv.OptionalRecordT[ + tlv.TlvType6, tlv.BigSizeT[uint64], + ] + + // htlcIndexVal is the decoded uint64 value. + htlcIndexVal uint64 + ) + + // If the length of the tlv record is 2, it must be encoded using uint16 + // as the BigSizeT encoding cannot have this length. + if len(rawBytes) == 2 { + // Decode the raw bytes into uint16 and convert it into uint64. + htlcIndexVal = uint64(binary.BigEndian.Uint16(rawBytes)) + } else { + // This value is encoded using BigSizeT, we now use the decoder + // to deserialize the raw bytes. + r := bytes.NewBuffer(rawBytes) + + // Create a buffer to be used in the decoding process. + buf := [8]byte{} + + // Use the BigSizeT's decoder. + err := tlv.DBigSize(r, &htlcIndexVal, &buf, 8) + if err != nil { + return record, err + } + } + + record = tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType6]( + tlv.NewBigSizeT(htlcIndexVal), + )) + + return record, nil +} + // writeTlvStream is a helper function that encodes the tlv stream into the // writer. func writeTlvStream(w io.Writer, s *tlv.Stream) error { diff --git a/channeldb/revocation_log_test.go b/channeldb/revocation_log_test.go index dcce16a034b..6e7afb9a3c3 100644 --- a/channeldb/revocation_log_test.go +++ b/channeldb/revocation_log_test.go @@ -2,6 +2,7 @@ package channeldb import ( "bytes" + "encoding/binary" "io" "math" "math/rand" @@ -59,13 +60,13 @@ var ( CustomBlob: tlv.SomeRecordT( tlv.NewPrimitiveRecord[tlv.TlvType5](blobBytes), ), - HtlcIndex: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType6, uint16](0x33), - ), + HtlcIndex: tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType6]( + tlv.NewBigSizeT(uint64(0x33)), + )), } testHTLCEntryBytes = []byte{ - // Body length 45. - 0x2d, + // Body length 44. + 0x2c, // Rhash tlv. 0x0, 0x0, // RefundTimeout tlv. @@ -79,8 +80,8 @@ var ( // Custom blob tlv. 0x5, 0x11, 0xfe, 0x00, 0x01, 0x00, 0x01, 0x0b, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x20, 0x64, 0x61, 0x74, 0x61, - // HLTC index tlv. - 0x6, 0x2, 0x0, 0x33, + // HTLC index tlv. + 0x6, 0x1, 0x33, } testHTLCEntryHash = HTLCEntry{ @@ -133,7 +134,7 @@ var ( OutputIndex: int32(testHTLCEntry.OutputIndex.Val), HtlcIndex: uint64( testHTLCEntry.HtlcIndex.ValOpt(). - UnsafeFromSome(), + UnsafeFromSome().Int(), ), Incoming: testHTLCEntry.Incoming.Val, Amt: lnwire.NewMSatFromSatoshis( @@ -303,7 +304,7 @@ func TestSerializeHTLCEntries(t *testing.T) { partialBytes := testHTLCEntryBytes[3:] // Write the total length and RHash tlv. - expectedBytes := []byte{0x4d, 0x0, 0x20} + expectedBytes := []byte{0x4c, 0x0, 0x20} expectedBytes = append(expectedBytes, rHashBytes...) // Append the rest. @@ -785,3 +786,63 @@ func createTestRevocationLogBuckets(tx kvdb.RwTx) (kvdb.RwBucket, return chanBucket, logBucket, nil } + +// TestDeserializeHTLCEntriesLegacy checks that the legacy encoding of the +// HtlcIndex can be correctly read as BigSizeT. The field `HtlcIndex` was +// encoded using `uint16` and should now be deserialized into BigSizeT +// on-the-fly. +func TestDeserializeHTLCEntriesLegacy(t *testing.T) { + t.Parallel() + + // rawBytes defines the bytes read from the disk for the testing HTLC + // entry. + rawBytes := []byte{ + // Body length 45. + 0x2d, + // Rhash tlv. + 0x0, 0x0, + // RefundTimeout tlv. + 0x1, 0x4, 0x0, 0xb, 0x4a, 0xa0, + // OutputIndex tlv. + 0x2, 0x2, 0x0, 0xa, + // Incoming tlv. + 0x3, 0x1, 0x1, + // Amt tlv. + 0x4, 0x5, 0xfe, 0x0, 0xf, 0x42, 0x40, + // Custom blob tlv. + 0x5, 0x11, 0xfe, 0x00, 0x01, 0x00, 0x01, 0x0b, 0x63, 0x75, 0x73, + 0x74, 0x6f, 0x6d, 0x20, 0x64, 0x61, 0x74, 0x61, + + // HTLC index tlv. + // + // NOTE: We are missing two bytes in the end, which is appended + // below. + 0x6, 0x2, + } + + // Iterate through all possible values encoded using uint16. They should + // be correctly read as uint16 and converted to BigSizeT. + for i := range math.MaxUint16 + 1 { + // Encode the index using two bytes. + rawHtlcIndex := make([]byte, 2) + binary.BigEndian.PutUint16(rawHtlcIndex, uint16(i)) + + // Copy the raw bytes and append the htlc index. + rawEntry := bytes.Clone(rawBytes) + rawEntry = append(rawEntry, rawHtlcIndex...) + + // Read the tlv stream. + buf := bytes.NewBuffer(rawEntry) + htlcs, err := deserializeHTLCEntries(buf) + require.NoError(t, err) + + // Check the bytes are read as expected. + require.Len(t, htlcs, 1) + + // Assert that the uint16 is converted to BigSizeT. + record := tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType6]( + tlv.NewBigSizeT(uint64(i)), + )) + require.Equal(t, record, htlcs[0].HtlcIndex) + } +} diff --git a/channeldb/waitingproof.go b/channeldb/waitingproof.go index faefc620cee..0c3913f9bac 100644 --- a/channeldb/waitingproof.go +++ b/channeldb/waitingproof.go @@ -3,11 +3,11 @@ package channeldb import ( "bytes" "encoding/binary" + "errors" "fmt" "io" "sync" - "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnwire" ) diff --git a/channeldb/waitingproof_test.go b/channeldb/waitingproof_test.go index d7113d9e754..7155a6c9954 100644 --- a/channeldb/waitingproof_test.go +++ b/channeldb/waitingproof_test.go @@ -1,11 +1,11 @@ package channeldb import ( + "errors" "reflect" "testing" "github.com/davecgh/go-spew/spew" - "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/lnwire" "github.com/stretchr/testify/require" ) diff --git a/config.go b/config.go index c66c7c9193b..53f8f36e571 100644 --- a/config.go +++ b/config.go @@ -238,8 +238,9 @@ const ( // defaultHTTPHeaderTimeout is the default timeout for HTTP requests. DefaultHTTPHeaderTimeout = 5 * time.Second - // DefaultNumRestrictedSlots is the default number of restricted slots - // we'll allocate in the server. + // DefaultNumRestrictedSlots is the default max number of incoming + // connections allowed in the server. Outbound connections are not + // restricted. DefaultNumRestrictedSlots = 100 // BitcoinChainName is a string that represents the Bitcoin blockchain. @@ -529,9 +530,9 @@ type Config struct { // before timing out reading the headers of an HTTP request. HTTPHeaderTimeout time.Duration `long:"http-header-timeout" description:"The maximum duration that the server will wait before timing out reading the headers of an HTTP request."` - // NumRestrictedSlots is the number of restricted slots we'll allocate - // in the server. - NumRestrictedSlots uint64 `long:"num-restricted-slots" description:"The number of restricted slots we'll allocate in the server."` + // NumRestrictedSlots is the max number of incoming connections allowed + // in the server. Outbound connections are not restricted. + NumRestrictedSlots uint64 `long:"num-restricted-slots" description:"The max number of incoming connections allowed in the server. Outbound connections are not restricted."` // NoDisconnectOnPongFailure controls if we'll disconnect if a peer // doesn't respond to a pong in time. diff --git a/config_builder.go b/config_builder.go index 2214250e7c1..55843ec6c48 100644 --- a/config_builder.go +++ b/config_builder.go @@ -1069,6 +1069,8 @@ func (d *DefaultDatabaseBuilder) BuildDatabase( ), channeldb.OptionPruneRevocationLog(cfg.DB.PruneRevocation), channeldb.OptionNoRevLogAmtData(cfg.DB.NoRevLogAmtData), + channeldb.OptionGcDecayedLog(cfg.DB.NoGcDecayedLog), + channeldb.OptionWithDecayedLogDB(dbs.DecayedLogDB), } // Otherwise, we'll open two instances, one for the state we only need diff --git a/contractcourt/breach_arbitrator_test.go b/contractcourt/breach_arbitrator_test.go index 99ed852696c..ac647769218 100644 --- a/contractcourt/breach_arbitrator_test.go +++ b/contractcourt/breach_arbitrator_test.go @@ -5,6 +5,7 @@ import ( crand "crypto/rand" "crypto/sha256" "encoding/binary" + "errors" "fmt" "io" "math/rand" @@ -19,7 +20,6 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/fn/v2" diff --git a/discovery/gossiper.go b/discovery/gossiper.go index 0b59c8af996..1df4857560c 100644 --- a/discovery/gossiper.go +++ b/discovery/gossiper.go @@ -411,6 +411,10 @@ type processedNetworkMsg struct { // cachedNetworkMsg is a wrapper around a network message that can be used with // *lru.Cache. +// +// NOTE: This struct is not thread safe which means you need to assure no +// concurrent read write access to it and all its contents which are pointers +// as well. type cachedNetworkMsg struct { msgs []*processedNetworkMsg } @@ -2234,7 +2238,7 @@ func (d *AuthenticatedGossiper) isMsgStale(_ context.Context, } if err != nil { log.Debugf("Unable to retrieve channel=%v from graph: "+ - "%v", chanInfo.ChannelID, err) + "%v", msg.ShortChannelID, err) return false } @@ -3134,7 +3138,9 @@ func (d *AuthenticatedGossiper) handleChanUpdate(ctx context.Context, // NOTE: We don't return anything on the error channel for this // message, as we expect that will be done when this - // ChannelUpdate is later reprocessed. + // ChannelUpdate is later reprocessed. This might never happen + // if the corresponding ChannelAnnouncement is never received + // or the LRU cache is filled up and the entry is evicted. return nil, false default: diff --git a/discovery/gossiper_test.go b/discovery/gossiper_test.go index 39901099164..1a61ff71f3f 100644 --- a/discovery/gossiper_test.go +++ b/discovery/gossiper_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/hex" + "errors" "fmt" prand "math/rand" "net" @@ -20,7 +21,6 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/davecgh/go-spew/spew" - "github.com/go-errors/errors" "github.com/lightninglabs/neutrino/cache" "github.com/lightningnetwork/lnd/batch" "github.com/lightningnetwork/lnd/chainntnfs" diff --git a/discovery/validation_barrier.go b/discovery/validation_barrier.go index bd3bc703924..7153dd49e9e 100644 --- a/discovery/validation_barrier.go +++ b/discovery/validation_barrier.go @@ -1,11 +1,11 @@ package discovery import ( + "errors" "fmt" "sync" "sync/atomic" - "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" diff --git a/docs/release-notes/release-notes-0.19.2.md b/docs/release-notes/release-notes-0.19.2.md new file mode 100644 index 00000000000..fd7a728f9b1 --- /dev/null +++ b/docs/release-notes/release-notes-0.19.2.md @@ -0,0 +1,126 @@ +# Release Notes +- [Bug Fixes](#bug-fixes) +- [New Features](#new-features) + - [Functional Enhancements](#functional-enhancements) + - [RPC Additions](#rpc-additions) + - [lncli Additions](#lncli-additions) +- [Improvements](#improvements) + - [Functional Updates](#functional-updates) + - [RPC Updates](#rpc-updates) + - [lncli Updates](#lncli-updates) + - [Breaking Changes](#breaking-changes) + - [Performance Improvements](#performance-improvements) + - [Deprecations](#deprecations) +- [Technical and Architectural Updates](#technical-and-architectural-updates) + - [BOLT Spec Updates](#bolt-spec-updates) + - [Testing](#testing) + - [Database](#database) + - [Code Health](#code-health) + - [Tooling and Documentation](#tooling-and-documentation) + +# Bug Fixes + +- [Use](https://github.com/lightningnetwork/lnd/pull/9889) `BigSizeT` instead of + `uint16` for the htlc index that's used in the revocation log. + +- [Fixed](https://github.com/lightningnetwork/lnd/pull/9921) a case where the + spending notification of an output may be missed if wrong height hint is used. + +- [Fixed](https://github.com/lightningnetwork/lnd/pull/9962) a case where the + node may panic if it's running in the remote signer mode. + +- [Fixed the `historical channel bucket has not yet been created` error on + startup](https://github.com/lightningnetwork/lnd/pull/9653). + +- [Fixed](https://github.com/lightningnetwork/lnd/pull/9978) a deadlock which + can happen when the peer start-up has not yet completed but a another p2p + connection attempt tries to disconnect the peer. + +- [Fixed](https://github.com/lightningnetwork/lnd/pull/10012) a case which + could lead to a memory issues due to a goroutine leak in the peer/gossiper + code. + +- [Fixed](https://github.com/lightningnetwork/lnd/pull/10035) a deadlock (writer starvation) in the switch. + +# New Features + +## Functional Enhancements + +- [Adds](https://github.com/lightningnetwork/lnd/pull/9989) a method + `FeeForWeightRoundUp` to the `chainfee` package which rounds up a calculated + fee value to the nearest satoshi. + +- [Update](https://github.com/lightningnetwork/lnd/pull/9996) lnd to point at + the new deployment of the lightning seed service which is used to provide + candidate peers during initial network bootstrap. The lseed service now + supports `testnet4` and `signet` networks as well. + +## RPC Additions + +- When querying + [`ForwardingEvents`](https://github.com/lightningnetwork/lnd/pull/9813) logs, + the response now includes the incoming and outgoing htlc indices of the + payment circuit. The indices are only available for forwarding events saved + after `v0.19.2`. + +## lncli Additions + +# Improvements + +## Functional Updates + +- [Improved](https://github.com/lightningnetwork/lnd/pull/9880) the connection + restriction logic enforced by `accessman`. In addition, the restriction placed + on outbound connections is now lifted. + +- [Enhanced](https://github.com/lightningnetwork/lnd/pull/9980) the aux traffic + shaper to now accept the first hop peer pub key as an argument. This can + affect the reported aux bandwidth and also the custom records that are + produced. + +## RPC Updates + +## lncli Updates + +## Code Health + +- [Add Optional Migration](https://github.com/lightningnetwork/lnd/pull/9945) + which garbage collects the `decayed log` also known as `sphinxreplay.db`. + This will lower disk and memory requirements for nodes significantly. + +## Breaking Changes + +## Performance Improvements + +- The replay protection is + [optimized](https://github.com/lightningnetwork/lnd/pull/9929) to use less + disk space such that the `sphinxreplay.db` or the `decayedlogdb_kv` table will + grow much more slowly. + +## Deprecations + +# Technical and Architectural Updates + +## BOLT Spec Updates + +## Testing + +## Database + +## Code Health + +## Tooling and Documentation + +# Contributors (Alphabetical Order) + +* Abdulkbk +* Calvin Zachman +* djkazic +* Elle Mouton +* ffranr +* George Tsagkarelis +* hieblmi +* Oliver Gugger +* Olaoluwa Osuntokun +* Yong Yu +* Ziggie diff --git a/docs/release-notes/release-notes-0.20.0.md b/docs/release-notes/release-notes-0.20.0.md index 1ee2e05fab0..4f261c7d483 100644 --- a/docs/release-notes/release-notes-0.20.0.md +++ b/docs/release-notes/release-notes-0.20.0.md @@ -25,6 +25,10 @@ ## Functional Enhancements ## RPC Additions +* When querying [`ForwardingEvents`](https://github.com/lightningnetwork/lnd/pull/9813) +logs, the response now include the incoming and outgoing htlc indices of the payment +circuit. The indices are only available for forwarding events saved after v0.20. + ## lncli Additions diff --git a/funding/manager.go b/funding/manager.go index fb3b599d1df..c78bf8ddd14 100644 --- a/funding/manager.go +++ b/funding/manager.go @@ -3,6 +3,7 @@ package funding import ( "bytes" "encoding/binary" + "errors" "fmt" "io" "sync" @@ -18,7 +19,6 @@ import ( "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/davecgh/go-spew/spew" - "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/chanacceptor" "github.com/lightningnetwork/lnd/channeldb" @@ -511,7 +511,7 @@ type Config struct { // NotifyOpenChannelEvent informs the ChannelNotifier when channels // transition from pending open to open. - NotifyOpenChannelEvent func(wire.OutPoint, *btcec.PublicKey) error + NotifyOpenChannelEvent func(wire.OutPoint, *btcec.PublicKey) // OpenChannelPredicate is a predicate on the lnwire.OpenChannel message // and on the requesting node's public key that returns a bool which @@ -521,13 +521,13 @@ type Config struct { // NotifyPendingOpenChannelEvent informs the ChannelNotifier when // channels enter a pending state. NotifyPendingOpenChannelEvent func(wire.OutPoint, - *channeldb.OpenChannel, *btcec.PublicKey) error + *channeldb.OpenChannel, *btcec.PublicKey) // NotifyFundingTimeout informs the ChannelNotifier when a pending-open // channel times out because the funding transaction hasn't confirmed. // This is only called for the fundee and only if the channel is // zero-conf. - NotifyFundingTimeout func(wire.OutPoint, *btcec.PublicKey) error + NotifyFundingTimeout func(wire.OutPoint, *btcec.PublicKey) // EnableUpfrontShutdown specifies whether the upfront shutdown script // is enabled. @@ -1319,13 +1319,9 @@ func (f *Manager) advancePendingChannelState(channel *channeldb.OpenChannel, // Inform the ChannelNotifier that the channel has transitioned // from pending open to open. - if err := f.cfg.NotifyOpenChannelEvent( + f.cfg.NotifyOpenChannelEvent( channel.FundingOutpoint, channel.IdentityPub, - ); err != nil { - log.Errorf("Unable to notify open channel event for "+ - "ChannelPoint(%v): %v", - channel.FundingOutpoint, err) - } + ) // Find and close the discoverySignal for this channel such // that ChannelReady messages will be processed. @@ -2666,12 +2662,9 @@ func (f *Manager) fundeeProcessFundingCreated(peer lnpeer.Peer, // Inform the ChannelNotifier that the channel has entered // pending open state. - if err := f.cfg.NotifyPendingOpenChannelEvent( + f.cfg.NotifyPendingOpenChannelEvent( fundingOut, completeChan, completeChan.IdentityPub, - ); err != nil { - log.Errorf("Unable to send pending-open channel event for "+ - "ChannelPoint(%v) %v", fundingOut, err) - } + ) // At this point we have sent our last funding message to the // initiating peer before the funding transaction will be broadcast. @@ -2891,13 +2884,9 @@ func (f *Manager) funderProcessFundingSigned(peer lnpeer.Peer, case resCtx.updates <- upd: // Inform the ChannelNotifier that the channel has entered // pending open state. - if err := f.cfg.NotifyPendingOpenChannelEvent( + f.cfg.NotifyPendingOpenChannelEvent( *fundingPoint, completeChan, completeChan.IdentityPub, - ); err != nil { - log.Errorf("Unable to send pending-open channel "+ - "event for ChannelPoint(%v) %v", fundingPoint, - err) - } + ) case <-f.quit: return @@ -2955,11 +2944,7 @@ func (f *Manager) fundingTimeout(c *channeldb.OpenChannel, } // Notify other subsystems about the funding timeout. - err := f.cfg.NotifyFundingTimeout(c.FundingOutpoint, c.IdentityPub) - if err != nil { - log.Errorf("failed to notify of funding timeout for "+ - "ChanPoint(%v): %v", c.FundingOutpoint, err) - } + f.cfg.NotifyFundingTimeout(c.FundingOutpoint, c.IdentityPub) timeoutErr := fmt.Errorf("timeout waiting for funding tx (%v) to "+ "confirm", c.FundingOutpoint) @@ -3341,13 +3326,9 @@ func (f *Manager) handleFundingConfirmation( // Inform the ChannelNotifier that the channel has transitioned from // pending open to open. - if err := f.cfg.NotifyOpenChannelEvent( + f.cfg.NotifyOpenChannelEvent( completeChan.FundingOutpoint, completeChan.IdentityPub, - ); err != nil { - log.Errorf("Unable to notify open channel event for "+ - "ChannelPoint(%v): %v", completeChan.FundingOutpoint, - err) - } + ) // Close the discoverySignal channel, indicating to a separate // goroutine that the channel now is marked as open in the database @@ -4455,8 +4436,8 @@ func (f *Manager) newChanAnnouncement(localPubKey, // channel announcement. storedFwdingPolicy, err := f.getInitialForwardingPolicy(chanID) if err != nil && !errors.Is(err, channeldb.ErrChannelNotFound) { - return nil, errors.Errorf("unable to generate channel "+ - "update announcement: %v", err) + return nil, fmt.Errorf("unable to generate channel "+ + "update announcement: %w", err) } switch { @@ -4499,13 +4480,13 @@ func (f *Manager) newChanAnnouncement(localPubKey, } sig, err := f.cfg.SignMessage(f.cfg.IDKeyLoc, chanUpdateMsg, true) if err != nil { - return nil, errors.Errorf("unable to generate channel "+ - "update announcement signature: %v", err) + return nil, fmt.Errorf("unable to generate channel "+ + "update announcement signature: %w", err) } chanUpdateAnn.Signature, err = lnwire.NewSigFromSignature(sig) if err != nil { - return nil, errors.Errorf("unable to generate channel "+ - "update announcement signature: %v", err) + return nil, fmt.Errorf("unable to generate channel "+ + "update announcement signature: %w", err) } // The channel existence proofs itself is currently announced in @@ -4521,15 +4502,15 @@ func (f *Manager) newChanAnnouncement(localPubKey, } nodeSig, err := f.cfg.SignMessage(f.cfg.IDKeyLoc, chanAnnMsg, true) if err != nil { - return nil, errors.Errorf("unable to generate node "+ - "signature for channel announcement: %v", err) + return nil, fmt.Errorf("unable to generate node "+ + "signature for channel announcement: %w", err) } bitcoinSig, err := f.cfg.SignMessage( localFundingKey.KeyLocator, chanAnnMsg, true, ) if err != nil { - return nil, errors.Errorf("unable to generate bitcoin "+ - "signature for node public key: %v", err) + return nil, fmt.Errorf("unable to generate bitcoin "+ + "signature for node public key: %w", err) } // Finally, we'll generate the announcement proof which we'll use to @@ -5191,13 +5172,13 @@ func (f *Manager) cancelReservationCtx(peerKey *btcec.PublicKey, nodeReservations, ok := f.activeReservations[peerIDKey] if !ok { // No reservations for this node. - return nil, errors.Errorf("no active reservations for peer(%x)", + return nil, fmt.Errorf("no active reservations for peer(%x)", peerIDKey[:]) } ctx, ok := nodeReservations[pendingChanID] if !ok { - return nil, errors.Errorf("unknown channel (id: %x) for "+ + return nil, fmt.Errorf("unknown channel (id: %x) for "+ "peer(%x)", pendingChanID[:], peerIDKey[:]) } @@ -5210,8 +5191,7 @@ func (f *Manager) cancelReservationCtx(peerKey *btcec.PublicKey, } if err := ctx.reservation.Cancel(); err != nil { - return nil, errors.Errorf("unable to cancel reservation: %v", - err) + return nil, fmt.Errorf("unable to cancel reservation: %w", err) } delete(nodeReservations, pendingChanID) @@ -5258,7 +5238,7 @@ func (f *Manager) getReservationCtx(peerKey *btcec.PublicKey, f.resMtx.RUnlock() if !ok { - return nil, errors.Errorf("unknown channel (id: %x) for "+ + return nil, fmt.Errorf("unknown channel (id: %x) for "+ "peer(%x)", pendingChanID[:], peerIDKey[:]) } diff --git a/funding/manager_test.go b/funding/manager_test.go index 6a1b35ed220..72a60e02ab0 100644 --- a/funding/manager_test.go +++ b/funding/manager_test.go @@ -232,29 +232,23 @@ type mockChanEvent struct { } func (m *mockChanEvent) NotifyOpenChannelEvent(outpoint wire.OutPoint, - remotePub *btcec.PublicKey) error { + remotePub *btcec.PublicKey) { m.openEvent <- outpoint - - return nil } func (m *mockChanEvent) NotifyPendingOpenChannelEvent(outpoint wire.OutPoint, pendingChannel *channeldb.OpenChannel, - remotePub *btcec.PublicKey) error { + remotePub *btcec.PublicKey) { m.pendingOpenEvent <- channelnotifier.PendingOpenChannelEvent{ ChannelPoint: &outpoint, PendingChannel: pendingChannel, } - - return nil } func (m *mockChanEvent) NotifyFundingTimeout(outpoint wire.OutPoint, - remotePub *btcec.PublicKey) error { - - return nil + remotePub *btcec.PublicKey) { } // mockZeroConfAcceptor always accepts the channel open request for zero-conf diff --git a/go.mod b/go.mod index 443388459ea..22b48c7a87e 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,6 @@ require ( github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f github.com/davecgh/go-spew v1.1.1 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 - github.com/go-errors/errors v1.0.1 github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/gorilla/websocket v1.5.0 @@ -48,7 +47,7 @@ require ( github.com/lightningnetwork/lnd/tor v1.1.6 github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 github.com/miekg/dns v1.1.43 - github.com/pkg/errors v0.9.1 + github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.11.1 github.com/stretchr/testify v1.9.0 github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02 diff --git a/go.sum b/go.sum index a77dd2bb394..6d960df0fad 100644 --- a/go.sum +++ b/go.sum @@ -156,8 +156,6 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= -github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= diff --git a/graph/builder.go b/graph/builder.go index 3350eeb3319..cd3a91bb61b 100644 --- a/graph/builder.go +++ b/graph/builder.go @@ -1,6 +1,7 @@ package graph import ( + "errors" "fmt" "sync" "sync/atomic" @@ -8,7 +9,6 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/wire" - "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/batch" "github.com/lightningnetwork/lnd/chainntnfs" graphdb "github.com/lightningnetwork/lnd/graph/db" @@ -874,8 +874,8 @@ func (b *Builder) assertNodeAnnFreshness(node route.Vertex, // already have. lastUpdate, exists, err := b.cfg.Graph.HasLightningNode(node) if err != nil { - return errors.Errorf("unable to query for the "+ - "existence of node: %v", err) + return fmt.Errorf("unable to query for the "+ + "existence of node: %w", err) } if !exists { return NewErrf(ErrIgnored, "Ignoring node announcement"+ @@ -998,8 +998,8 @@ func (b *Builder) addNode(node *models.LightningNode, } if err := b.cfg.Graph.AddLightningNode(node, op...); err != nil { - return errors.Errorf("unable to add node %x to the "+ - "graph: %v", node.PubKeyBytes, err) + return fmt.Errorf("unable to add node %x to the "+ + "graph: %w", node.PubKeyBytes, err) } log.Tracef("Updated vertex data for node=%x", node.PubKeyBytes) @@ -1045,7 +1045,7 @@ func (b *Builder) addEdge(edge *models.ChannelEdgeInfo, edge.ChannelID, ) if err != nil && !errors.Is(err, graphdb.ErrGraphNoEdgesFound) { - return errors.Errorf("unable to check for edge existence: %v", + return fmt.Errorf("unable to check for edge existence: %w", err) } if isZombie { @@ -1103,8 +1103,7 @@ func (b *Builder) addEdge(edge *models.ChannelEdgeInfo, err = b.cfg.ChainView.UpdateFilter(filterUpdate, b.bestHeight.Load()) if err != nil { - return errors.Errorf("unable to update chain "+ - "view: %v", err) + return fmt.Errorf("unable to update chain view: %w", err) } return nil @@ -1146,8 +1145,7 @@ func (b *Builder) updateEdge(policy *models.ChannelEdgePolicy, edge1Timestamp, edge2Timestamp, exists, isZombie, err := b.cfg.Graph.HasChannelEdge(policy.ChannelID) if err != nil && !errors.Is(err, graphdb.ErrGraphNoEdgesFound) { - return errors.Errorf("unable to check for edge existence: %v", - err) + return fmt.Errorf("unable to check for edge existence: %w", err) } // If the channel is marked as a zombie in our database, and @@ -1206,7 +1204,7 @@ func (b *Builder) updateEdge(policy *models.ChannelEdgePolicy, // Now that we know this isn't a stale update, we'll apply the new edge // policy to the proper directional edge within the channel graph. if err = b.cfg.Graph.UpdateEdgePolicy(policy, op...); err != nil { - err := errors.Errorf("unable to add channel: %v", err) + err := fmt.Errorf("unable to add channel: %w", err) log.Error(err) return err } diff --git a/graph/builder_test.go b/graph/builder_test.go index b813c17e22a..63d4fa01d9b 100644 --- a/graph/builder_test.go +++ b/graph/builder_test.go @@ -5,6 +5,7 @@ import ( "crypto/sha256" "encoding/hex" "encoding/json" + "errors" "fmt" "image/color" "math/rand" @@ -18,7 +19,6 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/fn/v2" graphdb "github.com/lightningnetwork/lnd/graph/db" diff --git a/graph/db/notifications.go b/graph/db/notifications.go index 8b69b7d8520..4c08759a100 100644 --- a/graph/db/notifications.go +++ b/graph/db/notifications.go @@ -1,6 +1,7 @@ package graphdb import ( + "errors" "fmt" "image/color" "net" @@ -10,7 +11,6 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" - "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/lnutils" "github.com/lightningnetwork/lnd/lnwire" @@ -407,8 +407,7 @@ func (c *ChannelGraph) addToTopologyChange(update *TopologyChange, // being connected. edgeInfo, _, _, err := c.FetchChannelEdgesByID(m.ChannelID) if err != nil { - return errors.Errorf("unable fetch channel edge: %v", - err) + return fmt.Errorf("unable fetch channel edge: %w", err) } // If the flag is one, then the advertising node is actually diff --git a/graph/errors.go b/graph/errors.go index 72910741929..5a81c9d05e2 100644 --- a/graph/errors.go +++ b/graph/errors.go @@ -1,6 +1,8 @@ package graph -import "github.com/go-errors/errors" +import ( + "fmt" +) // ErrorCode is used to represent the various errors that can occur within this // package. @@ -21,7 +23,7 @@ const ( // this structure carries additional information about error code in order to // be able distinguish errors outside of the current package. type Error struct { - err *errors.Error + err error code ErrorCode } @@ -39,7 +41,7 @@ var _ error = (*Error)(nil) func NewErrf(code ErrorCode, format string, a ...interface{}) *Error { return &Error{ code: code, - err: errors.Errorf(format, a...), + err: fmt.Errorf(format, a...), } } diff --git a/htlcswitch/circuit_map.go b/htlcswitch/circuit_map.go index 076d6f28409..15d4b5ffca4 100644 --- a/htlcswitch/circuit_map.go +++ b/htlcswitch/circuit_map.go @@ -2,10 +2,10 @@ package htlcswitch import ( "bytes" + "errors" "fmt" "sync" - "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/htlcswitch/hop" "github.com/lightningnetwork/lnd/kvdb" diff --git a/htlcswitch/decayedlog.go b/htlcswitch/decayedlog.go index ca7e19e7d85..e92a45160b8 100644 --- a/htlcswitch/decayedlog.go +++ b/htlcswitch/decayedlog.go @@ -1,7 +1,6 @@ package htlcswitch import ( - "bytes" "encoding/binary" "errors" "fmt" @@ -24,11 +23,6 @@ var ( // bytes of a received HTLC's hashed shared secret as the key and the HTLC's // CLTV expiry as the value. sharedHashBucket = []byte("shared-hash") - - // batchReplayBucket is a bucket that maps batch identifiers to - // serialized ReplaySets. This is used to give idempotency in the event - // that a batch is processed more than once. - batchReplayBucket = []byte("batch-replay") ) var ( @@ -138,11 +132,6 @@ func (d *DecayedLog) initBuckets() error { return ErrDecayedLogInit } - _, err = tx.CreateTopLevelBucket(batchReplayBucket) - if err != nil { - return ErrDecayedLogInit - } - return nil }, func() {}) } @@ -329,11 +318,8 @@ func (d *DecayedLog) Put(hash *sphinx.HashPrefix, cltv uint32) error { // PutBatch accepts a pending batch of hashed secret entries to write to disk. // Each hashed secret is inserted with a corresponding time value, dictating // when the entry will be evicted from the log. -// NOTE: This method enforces idempotency by writing the replay set obtained -// from the first attempt for a particular batch ID, and decoding the return -// value to subsequent calls. For the indices of the replay set to be aligned -// properly, the batch MUST be constructed identically to the first attempt, -// pruning will cause the indices to become invalid. +// +// TODO(yy): remove this method and use `Put` instead. func (d *DecayedLog) PutBatch(b *sphinx.Batch) (*sphinx.ReplaySet, error) { // Since batched boltdb txns may be executed multiple times before // succeeding, we will create a new replay set for each invocation to @@ -348,25 +334,6 @@ func (d *DecayedLog) PutBatch(b *sphinx.Batch) (*sphinx.ReplaySet, error) { return ErrDecayedLogCorrupted } - // Load the batch replay bucket, which will be used to either - // retrieve the result of previously processing this batch, or - // to write the result of this operation. - batchReplayBkt := tx.ReadWriteBucket(batchReplayBucket) - if batchReplayBkt == nil { - return ErrDecayedLogCorrupted - } - - // Check for the existence of this batch's id in the replay - // bucket. If a non-nil value is found, this indicates that we - // have already processed this batch before. We deserialize the - // resulting and return it to ensure calls to put batch are - // idempotent. - replayBytes := batchReplayBkt.Get(b.ID) - if replayBytes != nil { - replays = sphinx.NewReplaySet() - return replays.Decode(bytes.NewReader(replayBytes)) - } - // The CLTV will be stored into scratch and then stored into the // sharedHashBucket. var scratch [4]byte @@ -394,17 +361,7 @@ func (d *DecayedLog) PutBatch(b *sphinx.Batch) (*sphinx.ReplaySet, error) { // batch's construction. replays.Merge(b.ReplaySet) - // Write the replay set under the batch identifier to the batch - // replays bucket. This can be used during recovery to test (1) - // that a particular batch was successfully processed and (2) - // recover the indexes of the adds that were rejected as - // replays. - var replayBuf bytes.Buffer - if err := replays.Encode(&replayBuf); err != nil { - return err - } - - return batchReplayBkt.Put(b.ID, replayBuf.Bytes()) + return nil }); err != nil { return nil, err } diff --git a/htlcswitch/hop/iterator.go b/htlcswitch/hop/iterator.go index 2cf2adb543a..553c4921dbc 100644 --- a/htlcswitch/hop/iterator.go +++ b/htlcswitch/hop/iterator.go @@ -745,7 +745,8 @@ func (r *DecodeHopIteratorResponse) Result() (Iterator, lnwire.FailCode) { // the presented readers and rhashes *NEVER* deviate across invocations for the // same id. func (p *OnionProcessor) DecodeHopIterators(id []byte, - reqs []DecodeHopIteratorRequest) ([]DecodeHopIteratorResponse, error) { + reqs []DecodeHopIteratorRequest, + reforward bool) ([]DecodeHopIteratorResponse, error) { var ( batchSize = len(reqs) @@ -783,6 +784,8 @@ func (p *OnionProcessor) DecodeHopIterators(id []byte, b.Val, )) }) + + // TODO(yy): use `p.router.ProcessOnionPacket` instead. err = tx.ProcessOnionPacket( seqNum, onionPkt, req.RHash, req.IncomingCltv, opts..., ) @@ -864,11 +867,12 @@ func (p *OnionProcessor) DecodeHopIterators(id []byte, continue } - // If this index is contained in the replay set, mark it with a - // temporary channel failure error code. We infer that the - // offending error was due to a replayed packet because this - // index was found in the replay set. - if replays.Contains(uint16(i)) { + // If this index is contained in the replay set, and it is not a + // reforwarding on startup, mark it with a permanent channel + // failure error code. We infer that the offending error was due + // to a replayed packet because this index was found in the + // replay set. + if !reforward && replays.Contains(uint16(i)) { log.Errorf("unable to process onion packet: %v", sphinx.ErrReplayedPacket) diff --git a/htlcswitch/interceptable_switch.go b/htlcswitch/interceptable_switch.go index 6414c9f8021..3d0bd90ed45 100644 --- a/htlcswitch/interceptable_switch.go +++ b/htlcswitch/interceptable_switch.go @@ -2,11 +2,11 @@ package htlcswitch import ( "crypto/sha256" + "errors" "fmt" "sync" "sync/atomic" - "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/graph/db/models" diff --git a/htlcswitch/interfaces.go b/htlcswitch/interfaces.go index b0b67fc3404..4739afff6ec 100644 --- a/htlcswitch/interfaces.go +++ b/htlcswitch/interfaces.go @@ -14,6 +14,7 @@ import ( "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" + "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/tlv" ) @@ -495,8 +496,9 @@ type AuxHtlcModifier interface { // data blob of an HTLC, may produce a different blob or modify the // amount of bitcoin this htlc should carry. ProduceHtlcExtraData(totalAmount lnwire.MilliSatoshi, - htlcCustomRecords lnwire.CustomRecords) (lnwire.MilliSatoshi, - lnwire.CustomRecords, error) + htlcCustomRecords lnwire.CustomRecords, + peer route.Vertex) (lnwire.MilliSatoshi, lnwire.CustomRecords, + error) } // AuxTrafficShaper is an interface that allows the sender to determine if a @@ -520,7 +522,8 @@ type AuxTrafficShaper interface { PaymentBandwidth(fundingBlob, htlcBlob, commitmentBlob fn.Option[tlv.Blob], linkBandwidth, htlcAmt lnwire.MilliSatoshi, - htlcView lnwallet.AuxHtlcView) (lnwire.MilliSatoshi, error) + htlcView lnwallet.AuxHtlcView, + peer route.Vertex) (lnwire.MilliSatoshi, error) // IsCustomHTLC returns true if the HTLC carries the set of relevant // custom records to put it under the purview of the traffic shaper, diff --git a/htlcswitch/link.go b/htlcswitch/link.go index f14abd9aba1..3aa47caf30c 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -31,6 +31,7 @@ import ( "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/queue" "github.com/lightningnetwork/lnd/record" + "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/ticker" "github.com/lightningnetwork/lnd/tlv" ) @@ -108,8 +109,10 @@ type ChannelLinkConfig struct { // blobs, which are then used to inform how to forward an HTLC. // // NOTE: This function assumes the same set of readers and preimages - // are always presented for the same identifier. - DecodeHopIterators func([]byte, []hop.DecodeHopIteratorRequest) ( + // are always presented for the same identifier. The last boolean is + // used to decide whether this is a reforwarding or not - when it's + // reforwarding, we skip the replay check enforced in our decay log. + DecodeHopIterators func([]byte, []hop.DecodeHopIteratorRequest, bool) ( []hop.DecodeHopIteratorResponse, error) // ExtractErrorEncrypter function is responsible for decoding HTLC @@ -3501,11 +3504,19 @@ func (l *channelLink) AuxBandwidth(amount lnwire.MilliSatoshi, }) } + peerBytes := l.cfg.Peer.PubKey() + + peer, err := route.NewVertexFromBytes(peerBytes[:]) + if err != nil { + return fn.Err[OptionalBandwidth](fmt.Errorf("failed to decode "+ + "peer pub key: %v", err)) + } + // Ask for a specific bandwidth to be used for the channel. commitmentBlob := l.CommitmentCustomBlob() auxBandwidth, err := ts.PaymentBandwidth( fundingBlob, htlcBlob, commitmentBlob, l.Bandwidth(), amount, - l.channel.FetchLatestAuxHTLCView(), + l.channel.FetchLatestAuxHTLCView(), peer, ) if err != nil { return fn.Err[OptionalBandwidth](fmt.Errorf("failed to get "+ @@ -3624,6 +3635,16 @@ func (l *channelLink) updateChannelFee(ctx context.Context, // the switch. func (l *channelLink) processRemoteSettleFails(fwdPkg *channeldb.FwdPkg) { if len(fwdPkg.SettleFails) == 0 { + l.log.Trace("fwd package has no settle/fails to process " + + "exiting early") + + return + } + + // Exit early if the fwdPkg is already processed. + if fwdPkg.State == channeldb.FwdStateCompleted { + l.log.Debugf("skipped processing completed fwdPkg %v", fwdPkg) + return } @@ -3728,13 +3749,43 @@ func (l *channelLink) processRemoteSettleFails(fwdPkg *channeldb.FwdPkg) { // //nolint:funlen func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg) { + // Exit early if there are no adds to process. + if len(fwdPkg.Adds) == 0 { + l.log.Trace("fwd package has no adds to process exiting early") + + return + } + + // Exit early if the fwdPkg is already processed. + if fwdPkg.State == channeldb.FwdStateCompleted { + l.log.Debugf("skipped processing completed fwdPkg %v", fwdPkg) + + return + } + l.log.Tracef("processing %d remote adds for height %d", len(fwdPkg.Adds), fwdPkg.Height) - decodeReqs := make( - []hop.DecodeHopIteratorRequest, 0, len(fwdPkg.Adds), - ) - for _, update := range fwdPkg.Adds { + // decodeReqs is a list of requests sent to the onion decoder. We expect + // the same length of responses to be returned. + decodeReqs := make([]hop.DecodeHopIteratorRequest, 0, len(fwdPkg.Adds)) + + // unackedAdds is a list of ADDs that's waiting for the remote's + // settle/fail update. + unackedAdds := make([]*lnwire.UpdateAddHTLC, 0, len(fwdPkg.Adds)) + + for i, update := range fwdPkg.Adds { + // If this index is already found in the ack filter, the + // response to this forwarding decision has already been + // committed by one of our commitment txns. ADDs in this state + // are waiting for the rest of the fwding package to get acked + // before being garbage collected. + if fwdPkg.State == channeldb.FwdStateProcessed && + fwdPkg.AckFilter.Contains(uint16(i)) { + + continue + } + if msg, ok := update.UpdateMsg.(*lnwire.UpdateAddHTLC); ok { // Before adding the new htlc to the state machine, // parse the onion object in order to obtain the @@ -3751,15 +3802,20 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg) { } decodeReqs = append(decodeReqs, req) + unackedAdds = append(unackedAdds, msg) } } + // If the fwdPkg has already been processed, it means we are + // reforwarding the packets again, which happens only on a restart. + reforward := fwdPkg.State == channeldb.FwdStateProcessed + // Atomically decode the incoming htlcs, simultaneously checking for // replay attempts. A particular index in the returned, spare list of // channel iterators should only be used if the failure code at the // same index is lnwire.FailCodeNone. decodeResps, sphinxErr := l.cfg.DecodeHopIterators( - fwdPkg.ID(), decodeReqs, + fwdPkg.ID(), decodeReqs, reforward, ) if sphinxErr != nil { l.failf(LinkFailureError{code: ErrInternalError}, @@ -3769,23 +3825,10 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg) { var switchPackets []*htlcPacket - for i, update := range fwdPkg.Adds { + for i, update := range unackedAdds { idx := uint16(i) - - //nolint:forcetypeassert - add := *update.UpdateMsg.(*lnwire.UpdateAddHTLC) sourceRef := fwdPkg.SourceRef(idx) - - if fwdPkg.State == channeldb.FwdStateProcessed && - fwdPkg.AckFilter.Contains(idx) { - - // If this index is already found in the ack filter, - // the response to this forwarding decision has already - // been committed by one of our commitment txns. ADDs - // in this state are waiting for the rest of the fwding - // package to get acked before being garbage collected. - continue - } + add := *update // An incoming HTLC add has been full-locked in. As a result we // can now examine the forwarding details of the HTLC, and the @@ -3805,8 +3848,10 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg) { add.ID, failureCode, add.OnionBlob, &sourceRef, ) - l.log.Errorf("unable to decode onion hop "+ - "iterator: %v", failureCode) + l.log.Errorf("unable to decode onion hop iterator "+ + "for htlc(id=%v, hash=%x): %v", add.ID, + add.PaymentHash, failureCode) + continue } @@ -4110,17 +4155,15 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg) { return } - replay := fwdPkg.State != channeldb.FwdStateLockedIn - - l.log.Debugf("forwarding %d packets to switch: replay=%v", - len(switchPackets), replay) + l.log.Debugf("forwarding %d packets to switch: reforward=%v", + len(switchPackets), reforward) // NOTE: This call is made synchronous so that we ensure all circuits // are committed in the exact order that they are processed in the link. // Failing to do this could cause reorderings/gaps in the range of // opened circuits, which violates assumptions made by the circuit // trimming. - l.forwardBatch(replay, switchPackets...) + l.forwardBatch(reforward, switchPackets...) } // experimentalEndorsement returns the value to set for our outgoing diff --git a/htlcswitch/linkfailure.go b/htlcswitch/linkfailure.go index 495bd46fc26..a2ce7305f38 100644 --- a/htlcswitch/linkfailure.go +++ b/htlcswitch/linkfailure.go @@ -1,6 +1,6 @@ package htlcswitch -import "github.com/go-errors/errors" +import "errors" var ( // ErrLinkShuttingDown signals that the link is shutting down. diff --git a/htlcswitch/mock.go b/htlcswitch/mock.go index cb814e95c5b..70bd73c37d2 100644 --- a/htlcswitch/mock.go +++ b/htlcswitch/mock.go @@ -5,6 +5,7 @@ import ( "context" "crypto/sha256" "encoding/binary" + "errors" "fmt" "io" "net" @@ -18,7 +19,6 @@ import ( "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" - "github.com/go-errors/errors" sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" @@ -522,7 +522,7 @@ func (p *mockIteratorDecoder) DecodeHopIterator(r io.Reader, rHash []byte, } func (p *mockIteratorDecoder) DecodeHopIterators(id []byte, - reqs []hop.DecodeHopIteratorRequest) ( + reqs []hop.DecodeHopIteratorRequest, _ bool) ( []hop.DecodeHopIteratorResponse, error) { idHash := sha256.Sum256(id) diff --git a/htlcswitch/resolution_store.go b/htlcswitch/resolution_store.go index 20ac4e5d8a9..e2d1b9fab88 100644 --- a/htlcswitch/resolution_store.go +++ b/htlcswitch/resolution_store.go @@ -2,9 +2,9 @@ package htlcswitch import ( "bytes" + "errors" "io" - "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/contractcourt" "github.com/lightningnetwork/lnd/kvdb" diff --git a/htlcswitch/sequencer.go b/htlcswitch/sequencer.go index 3f37414dacf..82ccc83f8e8 100644 --- a/htlcswitch/sequencer.go +++ b/htlcswitch/sequencer.go @@ -1,9 +1,9 @@ package htlcswitch import ( + "errors" "sync" - "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/kvdb" ) diff --git a/htlcswitch/switch.go b/htlcswitch/switch.go index d034e732ebe..f89dbb4d212 100644 --- a/htlcswitch/switch.go +++ b/htlcswitch/switch.go @@ -880,7 +880,6 @@ func (s *Switch) getLocalLink(pkt *htlcPacket, htlc *lnwire.UpdateAddHTLC) ( // Try to find links by node destination. s.indexMtx.RLock() link, err := s.getLinkByShortID(pkt.outgoingChanID) - defer s.indexMtx.RUnlock() if err != nil { // If the link was not found for the outgoingChanID, an outside // subsystem may be using the confirmed SCID of a zero-conf @@ -892,6 +891,7 @@ func (s *Switch) getLocalLink(pkt *htlcPacket, htlc *lnwire.UpdateAddHTLC) ( // do that upon receiving the packet. baseScid, ok := s.baseIndex[pkt.outgoingChanID] if !ok { + s.indexMtx.RUnlock() log.Errorf("Link %v not found", pkt.outgoingChanID) return nil, NewLinkError(&lnwire.FailUnknownNextPeer{}) } @@ -900,10 +900,15 @@ func (s *Switch) getLocalLink(pkt *htlcPacket, htlc *lnwire.UpdateAddHTLC) ( // link. link, err = s.getLinkByShortID(baseScid) if err != nil { + s.indexMtx.RUnlock() log.Errorf("Link %v not found", baseScid) return nil, NewLinkError(&lnwire.FailUnknownNextPeer{}) } } + // We finished looking up the indexes, so we can unlock the mutex before + // performing the link operations which might also acquire the lock + // in case e.g. failAliasUpdate is called. + s.indexMtx.RUnlock() if !link.EligibleToForward() { log.Errorf("Link %v is not available to forward", @@ -928,6 +933,7 @@ func (s *Switch) getLocalLink(pkt *htlcPacket, htlc *lnwire.UpdateAddHTLC) ( "satisfied", pkt.outgoingChanID) return nil, htlcErr } + return link, nil } @@ -3074,6 +3080,12 @@ func (s *Switch) handlePacketSettle(packet *htlcPacket) error { OutgoingChanID: circuit.Outgoing.ChanID, AmtIn: circuit.IncomingAmount, AmtOut: circuit.OutgoingAmount, + IncomingHtlcID: fn.Some( + circuit.Incoming.HtlcID, + ), + OutgoingHtlcID: fn.Some( + circuit.Outgoing.HtlcID, + ), }, ) s.fwdEventMtx.Unlock() diff --git a/htlcswitch/switch_test.go b/htlcswitch/switch_test.go index 88093214607..9a96275799f 100644 --- a/htlcswitch/switch_test.go +++ b/htlcswitch/switch_test.go @@ -4,6 +4,7 @@ import ( "context" "crypto/rand" "crypto/sha256" + "errors" "fmt" "io" mrand "math/rand" @@ -13,7 +14,6 @@ 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" diff --git a/htlcswitch/test_utils.go b/htlcswitch/test_utils.go index 43902dc3368..bdb365d3c93 100644 --- a/htlcswitch/test_utils.go +++ b/htlcswitch/test_utils.go @@ -7,6 +7,7 @@ import ( "crypto/sha256" "encoding/binary" "encoding/hex" + "errors" "fmt" "net" "os" @@ -21,7 +22,6 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/go-errors/errors" sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/contractcourt" @@ -395,12 +395,12 @@ func createTestChannel(t *testing.T, alicePrivKey, bobPrivKey []byte, aliceStoredChannels, err = dbAlice.ChannelStateDB(). FetchOpenChannels(aliceKeyPub) if err != nil { - return nil, errors.Errorf("unable to fetch alice "+ - "channel: %v", err) + return nil, fmt.Errorf("unable to fetch alice "+ + "channel: %w", err) } default: - return nil, errors.Errorf("unable to fetch alice channel: "+ - "%v", err) + return nil, fmt.Errorf("unable to fetch alice "+ + "channel: %w", err) } var aliceStoredChannel *channeldb.OpenChannel @@ -421,8 +421,8 @@ func createTestChannel(t *testing.T, alicePrivKey, bobPrivKey []byte, lnwallet.WithAuxSigner(signerMock), ) if err != nil { - return nil, errors.Errorf("unable to create new channel: %v", - err) + return nil, fmt.Errorf("unable to create new "+ + "channel: %w", err) } return newAliceChannel, nil @@ -436,19 +436,19 @@ func createTestChannel(t *testing.T, alicePrivKey, bobPrivKey []byte, case kvdb.ErrDatabaseNotOpen: dbBob = channeldb.OpenForTesting(t, dbBob.Path()) if err != nil { - return nil, errors.Errorf("unable to reopen bob "+ - "db: %v", err) + return nil, fmt.Errorf("unable to reopen bob "+ + "db: %w", err) } bobStoredChannels, err = dbBob.ChannelStateDB(). FetchOpenChannels(bobKeyPub) if err != nil { - return nil, errors.Errorf("unable to fetch bob "+ - "channel: %v", err) + return nil, fmt.Errorf("unable to fetch bob "+ + "channel: %w", err) } default: - return nil, errors.Errorf("unable to fetch bob channel: "+ - "%v", err) + return nil, fmt.Errorf("unable to fetch bob channel: "+ + "%w", err) } var bobStoredChannel *channeldb.OpenChannel @@ -469,8 +469,8 @@ func createTestChannel(t *testing.T, alicePrivKey, bobPrivKey []byte, lnwallet.WithAuxSigner(signerMock), ) if err != nil { - return nil, errors.Errorf("unable to create new channel: %v", - err) + return nil, fmt.Errorf("unable to create new "+ + "channel: %w", err) } return newBobChannel, nil } @@ -881,16 +881,16 @@ func createClusterChannels(t *testing.T, aliceToBob, bobToCarol btcutil.Amount) bobPrivKey, aliceToBob, aliceToBob, 0, 0, firstChanID, ) if err != nil { - return nil, nil, errors.Errorf("unable to create "+ - "alice<->bob channel: %v", err) + return nil, nil, fmt.Errorf("unable to create "+ + "alice<->bob channel: %w", err) } secondBobChannel, carolChannel, err := createTestChannel(t, bobPrivKey, carolPrivKey, bobToCarol, bobToCarol, 0, 0, secondChanID, ) if err != nil { - return nil, nil, errors.Errorf("unable to create "+ - "bob<->carol channel: %v", err) + return nil, nil, fmt.Errorf("unable to create "+ + "bob<->carol channel: %w", err) } restoreFromDb := func() (*clusterChannels, error) { @@ -1070,8 +1070,8 @@ func createMirroredChannel(t *testing.T, aliceToBob, aliceToBob, bobToAlice, 0, 0, firstChanID, ) if err != nil { - return nil, nil, errors.Errorf("unable to create "+ - "alice<->bob channel: %v", err) + return nil, nil, fmt.Errorf("unable to create "+ + "alice<->bob channel: %w", err) } return alice, bob, nil diff --git a/itest/list_on_test.go b/itest/list_on_test.go index 5dacc0b1cf2..1c3bcdf258e 100644 --- a/itest/list_on_test.go +++ b/itest/list_on_test.go @@ -691,10 +691,6 @@ var allTestCases = []*lntest.TestCase{ Name: "funding manager funding timeout", TestFunc: testFundingManagerFundingTimeout, }, - { - Name: "access perm", - TestFunc: testAccessPerm, - }, { Name: "rbf coop close", TestFunc: testCoopCloseRbf, @@ -778,6 +774,9 @@ func init() { "coop close with external delivery", allTestCases, coopCloseWithExternalTestCases, ) + allTestCases = appendPrefixed( + "peer conn", allTestCases, peerConnTestCases, + ) // Prepare the test cases for windows to exclude some of the flaky // ones. diff --git a/itest/lnd_access_perm_test.go b/itest/lnd_access_perm_test.go index 6ff0fbb2428..956bdaed098 100644 --- a/itest/lnd_access_perm_test.go +++ b/itest/lnd_access_perm_test.go @@ -1,93 +1,341 @@ package itest import ( - "strconv" - - "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lntest" + "github.com/stretchr/testify/require" ) -// testAccessPerm tests that the number of restricted slots is upheld when -// connecting to the server from a restrictedd peer. -func testAccessPerm(ht *lntest.HarnessTest) { +var peerConnTestCases = []*lntest.TestCase{ + { + Name: "restricted on inbound", + TestFunc: testPeerConnRestrictedOnInbound, + }, + { + Name: "already connected", + TestFunc: testPeerConnAlreadyConnected, + }, + { + Name: "unlimited outbound", + TestFunc: testPeerConnUnlimitedOutbound, + }, + { + Name: "upgrade access perm", + TestFunc: testPeerConnUpgradeAccessPerm, + }, + { + Name: "node restart", + TestFunc: testPeerConnNodeRestart, + }, + { + Name: "peer reconnect", + TestFunc: testPeerConnPeerReconnect, + }, +} + +// testPeerConnRestrictedOnInbound checks that when the `num-restricted-slots` +// is reached, no more inbound connection is allowed. In addition, when a slot +// becomes available, new inbound connection should be allowed. +func testPeerConnRestrictedOnInbound(ht *lntest.HarnessTest) { + args := []string{"--num-restricted-slots=1"} + + // Create a new node with only one slot available. + alice := ht.NewNode("Alice", args) + + // Create two nodes Bob and Carol. Bob will connect to Alice first, + // taking up Alice's only slot, and we expect the connection from Carol + // to Alice to be failed. + bob := ht.NewNode("Bob", nil) + carol := ht.NewNode("Carol", nil) + + // Bob now connects to Alice - from Alice's PoV, this is her inbound + // connection from Bob. We expect Bob to connect to Alice successfully. + _, err := ht.ConnectNodesNoAssert(bob, alice) + require.NoError(ht, err) + ht.AssertConnected(alice, bob) + + // Carol now connects to Alice - from Alice's PoV, this is her inbound + // connection from Carol. Carol's connection should be failed. + _, err = ht.ConnectNodesNoAssert(carol, alice) + require.NoError(ht, err) + ht.AssertNotConnected(alice, carol) + + // Bob now disconnects, which will free up Alice's slot. + ht.DisconnectNodes(bob, alice) + + // Carol connects again - since Alice's slot is freed, this connection + // should succeed. + _, err = ht.ConnectNodesNoAssert(carol, alice) + require.NoError(ht, err) + + ht.AssertConnected(alice, carol) + ht.AssertNotConnected(alice, bob) +} + +// testPeerConnAlreadyConnected checks that when there's already a connection +// alive, another attempt to connect doesn't take up the slot or kill the +// existing connection, in specific, +// - When Alice has an inbound connection from Bob, another inbound connection +// from Bob will be noop. +// - When Bob has an outbound connection to Alice, another outbound connection +// to Alice will be noop. +// +// In this test we will create nodes using the dev flag `unsafeconnect` to mimic +// the behaviour of persistent/permanent peer connections, where it's likely +// inbound and outbound connections are happening at the same time. +func testPeerConnAlreadyConnected(ht *lntest.HarnessTest) { args := []string{ - "--minbackoff=5m", - "--maxbackoff=5m", - "--num-restricted-slots=5", + "--num-restricted-slots=1", } - alice := ht.NewNodeWithCoins("Alice", args) - bob := ht.NewNodeWithCoins("Bob", args) - ht.ConnectNodes(alice, bob) - - // Open a confirmed channel to Bob. Bob will have protected access. - chanPoint1 := ht.OpenChannel( - alice, bob, lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - defer ht.CloseChannel(alice, chanPoint1) - - // Open and close channel to Carol. Carol will have protected access. - carol := ht.NewNodeWithCoins("Carol", args) - ht.ConnectNodes(alice, carol) - - chanPoint2 := ht.OpenChannel( - alice, carol, lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - - ht.CloseChannel(alice, chanPoint2) - - // Make a pending channel with Dave. - dave := ht.NewNodeWithCoins("Dave", args) - ht.ConnectNodes(alice, dave) - - ht.OpenChannelAssertStream( - dave, alice, lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - - // Disconnect Bob, Carol, and Dave. - ht.DisconnectNodes(alice, bob) - ht.AssertNotConnected(alice, bob) + // Create a new node with two slots available. + alice := ht.NewNode("Alice", args) + bob := ht.NewNode("Bob", []string{"--dev.unsafeconnect"}) + carol := ht.NewNode("Carol", nil) - ht.DisconnectNodes(alice, carol) + // Bob now connects to Alice - from Alice's PoV, this is her inbound + // connection from Bob. We expect Bob to connect to Alice successfully. + _, err := ht.ConnectNodesNoAssert(bob, alice) + require.NoError(ht, err) + ht.AssertConnected(alice, bob) + + // Assert Alice's slot has been filled up by connecting Carol to Alice. + _, err = ht.ConnectNodesNoAssert(carol, alice) + require.NoError(ht, err) ht.AssertNotConnected(alice, carol) - ht.DisconnectNodes(alice, dave) - ht.AssertNotConnected(alice, dave) + // Bob connects to Alice again - from Alice's PoV, she already has an + // inbound connection from Bob so this connection attempt should be + // noop. We expect Alice and Bob to stay connected. + // + // TODO(yy): There's no way to assert the connection stays the same atm, + // need to update the RPC to return the socket port. As for now we need + // to visit the logs to check the connection is the same or not. + _, err = ht.ConnectNodesNoAssert(bob, alice) + require.NoError(ht, err) + ht.AssertConnected(alice, bob) +} + +// testPeerConnUnlimitedOutbound checks that for outbound connections, they are +// not restricted by the `num-restricted-slots` config. +func testPeerConnUnlimitedOutbound(ht *lntest.HarnessTest) { + args := []string{"--num-restricted-slots=1"} - // Connect 5 times to Alice. All of these connections should be - // successful. - for i := 0; i < 5; i++ { - peer := ht.NewNode("Peer"+strconv.Itoa(i), args) - ht.ConnectNodes(peer, alice) - ht.AssertConnected(peer, alice) - } + // Create a new node with one slot available. + alice := ht.NewNode("Alice", args) - // Connect an additional time to Alice. This should fail. - failedPeer := ht.NewNode("FailedPeer", args) - req := &lnrpc.ConnectPeerRequest{ - Addr: &lnrpc.LightningAddress{ - Pubkey: alice.RPC.GetInfo().IdentityPubkey, - Host: alice.Cfg.P2PAddr(), - }, - } - _ = failedPeer.RPC.ConnectPeer(req) - ht.AssertNotConnected(failedPeer, alice) + // Create three nodes. Alice will have an inbound connection with Bob + // and outbound connections with Carol and Dave. + bob := ht.NewNode("Bob", args) + carol := ht.NewNode("Carol", args) + dave := ht.NewNode("Dave", args) - // Connect nodes and assert access status. - ht.ConnectNodes(alice, bob) + // Bob now connects to Alice - from Alice's PoV, this is her inbound + // connection from Bob. We expect Bob to connect to Alice successfully. + _, err := ht.ConnectNodesNoAssert(bob, alice) + require.NoError(ht, err) ht.AssertConnected(alice, bob) - ht.ConnectNodes(alice, carol) + // Assert Alice's slot has been filled up by connecting Carol to Alice. + _, err = ht.ConnectNodesNoAssert(carol, alice) + require.NoError(ht, err) + ht.AssertNotConnected(alice, carol) + + // Now let Alice make an outbound connection to Carol and assert it's + // succeeded. + _, err = ht.ConnectNodesNoAssert(alice, carol) + require.NoError(ht, err) ht.AssertConnected(alice, carol) - ht.ConnectNodes(alice, dave) + // Alice can also make an outbound connection to Dave and assert it's + // succeeded since outbound connection is not restricted. + _, err = ht.ConnectNodesNoAssert(alice, dave) + require.NoError(ht, err) ht.AssertConnected(alice, dave) +} + +// testPeerConnUpgradeAccessPerm checks that when a peer has, or used to have a +// channel with Alice, it won't use her restricted slots. +func testPeerConnUpgradeAccessPerm(ht *lntest.HarnessTest) { + args := []string{"--num-restricted-slots=1"} + + // Create a new node with one slot available. + alice := ht.NewNodeWithCoins("Alice", args) + bob := ht.NewNode("Bob", nil) + carol := ht.NewNodeWithCoins("Carol", nil) + dave := ht.NewNode("Dave", nil) + eve := ht.NewNode("Eve", nil) + + // Connect Bob to Alice, which will use Alice's available slot. + ht.ConnectNodes(bob, alice) + + // Assert Alice's slot has been filled up by connecting Carol to Alice. + _, err := ht.ConnectNodesNoAssert(carol, alice) + require.NoError(ht, err) + ht.AssertNotConnected(alice, carol) + + // Open a channel from Alice to Bob and let it stay pending. + p := lntest.OpenChannelParams{ + Amt: chanAmt, + } + pendingUpdate := ht.OpenChannelAssertPending(alice, bob, p) + cpAB := lntest.ChanPointFromPendingUpdate(pendingUpdate) + + // Connect Carol to Alice - since Bob now has a pending channel with + // Alice, there's a slot available for Carol to connect. + ht.ConnectNodes(carol, alice) + + // Open a channel from Carol to Alice and let it stay pending. + pendingUpdate = ht.OpenChannelAssertPending(carol, alice, p) + cpCA := lntest.ChanPointFromPendingUpdate(pendingUpdate) + + // Mine the funding txns. + ht.MineBlocksAndAssertNumTxes(1, 2) + + // Dave should be able to connect to Alice. + ht.ConnectNodes(dave, alice) + + // Open a channel from Alice to Dave, which will free up Alice's slot. + cpAD := ht.OpenChannel(alice, dave, p) + + // Close the channels. + ht.CloseChannel(bob, cpAB) + ht.CloseChannel(carol, cpCA) + ht.CloseChannel(dave, cpAD) + // Alice should have one slot available, connect Eve to Alice now. + ht.ConnectNodes(eve, alice) +} + +// testPeerConnNodeRestart checks that when a peer has or used to have a channel +// with Alice, when Alice restarts, she should still have the available slot for +// more inbound connections. +func testPeerConnNodeRestart(ht *lntest.HarnessTest) { + args := []string{"--num-restricted-slots=1"} + + // Create a new node with one slot available. + alice := ht.NewNodeWithCoins("Alice", args) + bob := ht.NewNode("Bob", []string{"--dev.unsafeconnect"}) + carol := ht.NewNode("Carol", nil) + + // Connect Bob to Alice, which will use Alice's available slot. + ht.ConnectNodes(bob, alice) + + // Assert Alice's slot has been filled up by connecting Carol to Alice. + _, err := ht.ConnectNodesNoAssert(carol, alice) + require.NoError(ht, err) + ht.AssertNotConnected(alice, carol) + + // Restart Alice, which will reset her current slots, allowing Bob to + // connect to her again. + ht.RestartNode(alice) + ht.ConnectNodes(bob, alice) + + // Open a channel from Alice to Bob and let it stay pending. + p := lntest.OpenChannelParams{ + Amt: chanAmt, + } + pendingUpdate := ht.OpenChannelAssertPending(alice, bob, p) + cp := lntest.ChanPointFromPendingUpdate(pendingUpdate) + + // Restart Alice and let Bob connect to her - since Bob has a pending + // channel, it shouldn't take any slot. + ht.RestartNode(alice) + ht.ConnectNodes(bob, alice) + + // Connect Carol to Alice since Alice has a free slot. + ht.ConnectNodes(carol, alice) + + // Mine the funding tx. + ht.MineBlocksAndAssertNumTxes(1, 1) + + // Restart Alice and let Bob connect to her - since Bob has an open + // channel, it shouldn't take any slot. + ht.RestartNode(alice) + ht.ConnectNodes(bob, alice) + + // Connect Carol to Alice since Alice has a free slot. + ht.ConnectNodes(carol, alice) + + // Close the channel. + ht.CloseChannel(alice, cp) + + // Restart Alice and let Bob connect to her - since Bob has a closed + // channel, it shouldn't take any slot. + ht.RestartNode(alice) + ht.ConnectNodes(bob, alice) + + // Connect Carol to Alice since Alice has a free slot. + ht.ConnectNodes(carol, alice) +} + +// testPeerConnPeerReconnect checks that when a peer has or used to have a +// channel with Alice, it will not account for the restricted slot during +// reconnection. +func testPeerConnPeerReconnect(ht *lntest.HarnessTest) { + args := []string{"--num-restricted-slots=1"} + + // Create a new node with one slot available. + alice := ht.NewNodeWithCoins("Alice", args) + bob := ht.NewNode("Bob", []string{"--dev.unsafeconnect"}) + carol := ht.NewNode("Carol", nil) + + // Connect Bob to Alice, which will use Alice's available slot. + ht.ConnectNodes(bob, alice) + + // Let Bob connect to Alice again, which put Bob in Alice's + // `scheduledPeerConnection` map. + ht.ConnectNodes(bob, alice) + + // Assert Alice's slot has been filled up by connecting Carol to Alice. + _, err := ht.ConnectNodesNoAssert(carol, alice) + require.NoError(ht, err) + ht.AssertNotConnected(alice, carol) + + // Open a channel from Alice to Bob and let it stay pending. + p := lntest.OpenChannelParams{ + Amt: chanAmt, + } + pendingUpdate := ht.OpenChannelAssertPending(alice, bob, p) + cp := lntest.ChanPointFromPendingUpdate(pendingUpdate) + + // Bob now perform a reconnection - since Bob has a pending channel, it + // shouldn't take any slot. + ht.DisconnectNodes(bob, alice) + ht.AssertNotConnected(alice, bob) + ht.ConnectNodes(bob, alice) + + // Connect Carol to Alice since Alice has a free slot. + ht.ConnectNodes(carol, alice) + + // Once the above connection succeeded we let Carol disconnect to free + // the slot. + ht.DisconnectNodes(carol, alice) + + // Mine the funding tx. ht.MineBlocksAndAssertNumTxes(1, 1) + + // Bob now perform a reconnection - since Bob has a pending channel, it + // shouldn't take any slot. + ht.DisconnectNodes(bob, alice) + ht.AssertNotConnected(alice, bob) + ht.ConnectNodes(bob, alice) + + // Connect Carol to Alice since Alice has a free slot. + ht.ConnectNodes(carol, alice) + + // Once the above connection succeeded we let Carol disconnect to free + // the slot. + ht.DisconnectNodes(carol, alice) + + // Close the channel. + ht.CloseChannel(alice, cp) + + // Bob now perform a reconnection - since Bob has a pending channel, it + // shouldn't take any slot. + ht.DisconnectNodes(bob, alice) + ht.AssertNotConnected(alice, bob) + ht.ConnectNodes(bob, alice) + + // Connect Carol to Alice since Alice has a free slot. + ht.ConnectNodes(carol, alice) } diff --git a/itest/lnd_channel_force_close_test.go b/itest/lnd_channel_force_close_test.go index c04036a7673..b05b838ff3c 100644 --- a/itest/lnd_channel_force_close_test.go +++ b/itest/lnd_channel_force_close_test.go @@ -2,11 +2,11 @@ package itest import ( "bytes" + "errors" "fmt" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/go-errors/errors" "github.com/lightningnetwork/lnd" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" diff --git a/itest/lnd_multi-hop-payments_test.go b/itest/lnd_multi-hop-payments_test.go index 9c55d7845a0..718fdca8775 100644 --- a/itest/lnd_multi-hop-payments_test.go +++ b/itest/lnd_multi-hop-payments_test.go @@ -213,6 +213,31 @@ func testMultiHopPayments(ht *lntest.HarnessTest) { require.Equal(ht, aliceAlias, event.PeerAliasOut) } + // Verify HTLC IDs are not nil and unique across all forwarding events. + seenIDs := make(map[uint64]bool) + for _, event := range fwdingHistory.ForwardingEvents { + // We check that the incoming and outgoing htlc indices are not + // set to nil. The indices are required for any forwarding event + // recorded after v0.20. + require.NotNil(ht, event.IncomingHtlcId) + require.NotNil(ht, event.OutgoingHtlcId) + + require.False(ht, seenIDs[*event.IncomingHtlcId]) + require.False(ht, seenIDs[*event.OutgoingHtlcId]) + seenIDs[*event.IncomingHtlcId] = true + seenIDs[*event.OutgoingHtlcId] = true + } + + // The HTLC IDs should be exactly 0, 1, 2, 3, 4. + expectedIDs := map[uint64]bool{ + 0: true, + 1: true, + 2: true, + 3: true, + 4: true, + } + require.Equal(ht, expectedIDs, seenIDs) + // We expect Carol to have successful forwards and settles for // her sends. ht.AssertHtlcEvents( diff --git a/itest/lnd_revocation_test.go b/itest/lnd_revocation_test.go index c2508b885bb..b975438f1be 100644 --- a/itest/lnd_revocation_test.go +++ b/itest/lnd_revocation_test.go @@ -2,13 +2,13 @@ package itest import ( "bytes" + "errors" "fmt" "testing" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lntest" diff --git a/itest/lnd_watchtower_test.go b/itest/lnd_watchtower_test.go index 1b63f9063e7..77781ced071 100644 --- a/itest/lnd_watchtower_test.go +++ b/itest/lnd_watchtower_test.go @@ -1,12 +1,12 @@ package itest import ( + "errors" "fmt" "testing" "time" "github.com/btcsuite/btcd/btcutil" - "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" diff --git a/itest/lnd_zero_conf_test.go b/itest/lnd_zero_conf_test.go index 5b87846be5c..ac82ba0d503 100644 --- a/itest/lnd_zero_conf_test.go +++ b/itest/lnd_zero_conf_test.go @@ -1,12 +1,12 @@ package itest import ( + "errors" "fmt" "testing" "time" "github.com/btcsuite/btcd/btcutil" - "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/aliasmgr" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/lnrpc" diff --git a/lncfg/db.go b/lncfg/db.go index b45ee6dab29..3485d8c4ff6 100644 --- a/lncfg/db.go +++ b/lncfg/db.go @@ -86,15 +86,17 @@ type DB struct { Sqlite *sqldb.SqliteConfig `group:"sqlite" namespace:"sqlite" description:"Sqlite settings."` - UseNativeSQL bool `long:"use-native-sql" description:"Use native SQL for tables that already support it."` + UseNativeSQL bool `long:"use-native-sql" description:"If set to true, native SQL will be used instead of KV emulation for tables that support it already (currently this is the invoices DB)."` - SkipNativeSQLMigration bool `long:"skip-native-sql-migration" description:"Do not run the KV to native SQL migration. It should only be used if errors are encountered normally."` + SkipNativeSQLMigration bool `long:"skip-native-sql-migration" description:"If set to true, the KV to native SQL migration will be skipped. Note that this option is intended for users who experience non-resolvable migration errors. Enabling after there is a non-resolvable migration error that resulted in an incomplete migration will cause that partial migration to be abandoned and ignored and an empty database will be used instead. Since invoices are currently the only native SQL database used, our channels will still work but the invoice history will be forgotten. This option has no effect if native SQL is not in use (db.use-native-sql=false)."` NoGraphCache bool `long:"no-graph-cache" description:"Don't use the in-memory graph cache for path finding. Much slower but uses less RAM. Can only be used with a bolt database backend."` PruneRevocation bool `long:"prune-revocation" description:"Run the optional migration that prunes the revocation logs to save disk space."` NoRevLogAmtData bool `long:"no-rev-log-amt-data" description:"If set, the to-local and to-remote output amounts of revoked commitment transactions will not be stored in the revocation log. Note that once this data is lost, a watchtower client will not be able to back up the revoked state."` + + NoGcDecayedLog bool `long:"no-gc-decayed-log" description:"Do not run the optional migration that garbage collects the decayed log to save disk space."` } // DefaultDB creates and returns a new default DB config. diff --git a/lncfg/dev.go b/lncfg/dev.go index 6b247102400..f048d69b7a9 100644 --- a/lncfg/dev.go +++ b/lncfg/dev.go @@ -52,3 +52,9 @@ func (d *DevConfig) GetZombieSweeperInterval() time.Duration { func (d *DevConfig) GetMaxWaitNumBlocksFundingConf() uint32 { return DefaultMaxWaitNumBlocksFundingConf } + +// GetUnsafeConnect returns the config value `UnsafeConnect`, which is always +// false for production build. +func (d *DevConfig) GetUnsafeConnect() bool { + return false +} diff --git a/lncfg/dev_integration.go b/lncfg/dev_integration.go index c6467af2bd7..8ac85f5d9e9 100644 --- a/lncfg/dev_integration.go +++ b/lncfg/dev_integration.go @@ -26,6 +26,7 @@ type DevConfig struct { ZombieSweeperInterval time.Duration `long:"zombiesweeperinterval" description:"The time interval at which channel opening flows are evaluated for zombie status."` UnsafeDisconnect bool `long:"unsafedisconnect" description:"Allows the rpcserver to intentionally disconnect from peers with open channels."` MaxWaitNumBlocksFundingConf uint32 `long:"maxwaitnumblocksfundingconf" description:"Maximum blocks to wait for funding confirmation before discarding non-initiated channels."` + UnsafeConnect bool `long:"unsafeconnect" description:"Allow the rpcserver to connect to a peer even if there's already a connection."` } // ChannelReadyWait returns the config value `ProcessChannelReadyWait`. @@ -51,7 +52,7 @@ func (d *DevConfig) GetZombieSweeperInterval() time.Duration { return d.ZombieSweeperInterval } -// ChannelReadyWait returns the config value `UnsafeDisconnect`. +// GetUnsafeDisconnect returns the config value `UnsafeDisconnect`. func (d *DevConfig) GetUnsafeDisconnect() bool { return d.UnsafeDisconnect } @@ -65,3 +66,8 @@ func (d *DevConfig) GetMaxWaitNumBlocksFundingConf() uint32 { return d.MaxWaitNumBlocksFundingConf } + +// GetUnsafeConnect returns the config value `UnsafeConnect`. +func (d *DevConfig) GetUnsafeConnect() bool { + return d.UnsafeConnect +} diff --git a/lnrpc/lightning.pb.go b/lnrpc/lightning.pb.go index db2395440a7..7edcb7ff3a0 100644 --- a/lnrpc/lightning.pb.go +++ b/lnrpc/lightning.pb.go @@ -15663,6 +15663,12 @@ type ForwardingEvent struct { PeerAliasIn string `protobuf:"bytes,12,opt,name=peer_alias_in,json=peerAliasIn,proto3" json:"peer_alias_in,omitempty"` // The peer alias of the outgoing channel. PeerAliasOut string `protobuf:"bytes,13,opt,name=peer_alias_out,json=peerAliasOut,proto3" json:"peer_alias_out,omitempty"` + // The ID of the incoming HTLC in the payment circuit. This field is + // optional and is unset for forwarding events happened before v0.20. + IncomingHtlcId *uint64 `protobuf:"varint,14,opt,name=incoming_htlc_id,json=incomingHtlcId,proto3,oneof" json:"incoming_htlc_id,omitempty"` + // The ID of the outgoing HTLC in the payment circuit. This field is + // optional and may be unset for legacy forwarding events. + OutgoingHtlcId *uint64 `protobuf:"varint,15,opt,name=outgoing_htlc_id,json=outgoingHtlcId,proto3,oneof" json:"outgoing_htlc_id,omitempty"` } func (x *ForwardingEvent) Reset() { @@ -15782,6 +15788,20 @@ func (x *ForwardingEvent) GetPeerAliasOut() string { return "" } +func (x *ForwardingEvent) GetIncomingHtlcId() uint64 { + if x != nil && x.IncomingHtlcId != nil { + return *x.IncomingHtlcId + } + return 0 +} + +func (x *ForwardingEvent) GetOutgoingHtlcId() uint64 { + if x != nil && x.OutgoingHtlcId != nil { + return *x.OutgoingHtlcId + } + return 0 +} + type ForwardingHistoryResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -21010,7 +21030,7 @@ var file_lightning_proto_rawDesc = []byte{ 0x28, 0x0d, 0x52, 0x0c, 0x6e, 0x75, 0x6d, 0x4d, 0x61, 0x78, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x70, 0x65, 0x65, - 0x72, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x22, 0x85, 0x03, 0x0a, + 0x72, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x22, 0x8d, 0x04, 0x0a, 0x0f, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x20, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x18, 0x01, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, @@ -21035,785 +21055,794 @@ var file_lightning_proto_rawDesc = []byte{ 0x09, 0x52, 0x0b, 0x70, 0x65, 0x65, 0x72, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x49, 0x6e, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x6f, 0x75, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x65, 0x65, 0x72, 0x41, 0x6c, 0x69, 0x61, - 0x73, 0x4f, 0x75, 0x74, 0x22, 0x8c, 0x01, 0x0a, 0x19, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, - 0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x43, 0x0a, 0x11, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, - 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x10, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, - 0x67, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, - 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0d, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x49, 0x6e, - 0x64, 0x65, 0x78, 0x22, 0x50, 0x0a, 0x1a, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 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, 0x22, 0x64, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 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, 0x1f, 0x0a, 0x0b, 0x63, 0x68, - 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x22, 0x73, 0x0a, 0x0f, 0x4d, - 0x75, 0x6c, 0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x34, - 0x0a, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, - 0x69, 0x6e, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x63, 0x68, - 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x0f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, - 0x22, 0x19, 0x0a, 0x17, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x45, 0x78, - 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x9f, 0x01, 0x0a, 0x12, - 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, - 0x6f, 0x74, 0x12, 0x45, 0x0a, 0x13, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x5f, 0x63, 0x68, 0x61, - 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, - 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x52, 0x11, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x43, 0x68, - 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x42, 0x0a, 0x11, 0x6d, 0x75, 0x6c, + 0x73, 0x4f, 0x75, 0x74, 0x12, 0x2d, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, + 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x04, 0x48, 0x00, + 0x52, 0x0e, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x64, + 0x88, 0x01, 0x01, 0x12, 0x2d, 0x0a, 0x10, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, + 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x04, 0x48, 0x01, 0x52, + 0x0e, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x64, 0x88, + 0x01, 0x01, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, + 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x64, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x6f, 0x75, 0x74, 0x67, + 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x64, 0x22, 0x8c, 0x01, 0x0a, + 0x19, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, + 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x11, 0x66, 0x6f, + 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, + 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x10, 0x66, + 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, + 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x5f, 0x69, + 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, + 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x50, 0x0a, 0x1a, 0x45, + 0x78, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, + 0x75, 0x70, 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, 0x22, 0x64, 0x0a, + 0x0d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 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, 0x1f, 0x0a, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, + 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, + 0x6b, 0x75, 0x70, 0x22, 0x73, 0x0a, 0x0f, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, + 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x34, 0x0a, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, + 0x52, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11, + 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, + 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x43, 0x68, + 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x22, 0x19, 0x0a, 0x17, 0x43, 0x68, 0x61, 0x6e, + 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x22, 0x9f, 0x01, 0x0a, 0x12, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, + 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x45, 0x0a, 0x13, 0x73, 0x69, + 0x6e, 0x67, 0x6c, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x52, 0x11, + 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, + 0x73, 0x12, 0x42, 0x0a, 0x11, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, + 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, + 0x63, 0x6b, 0x75, 0x70, 0x52, 0x0f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, 0x42, + 0x61, 0x63, 0x6b, 0x75, 0x70, 0x22, 0x49, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x37, 0x0a, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x5f, + 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, + 0x6b, 0x75, 0x70, 0x52, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, + 0x22, 0x8e, 0x01, 0x0a, 0x18, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x61, 0x6e, + 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3a, 0x0a, + 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x48, 0x00, 0x52, 0x0b, 0x63, 0x68, + 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x2c, 0x0a, 0x11, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x6c, - 0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x0f, 0x6d, 0x75, - 0x6c, 0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x22, 0x49, 0x0a, - 0x0e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, - 0x37, 0x0a, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x0b, 0x63, 0x68, 0x61, - 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x22, 0x8e, 0x01, 0x0a, 0x18, 0x52, 0x65, 0x73, - 0x74, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3a, 0x0a, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x62, 0x61, - 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, - 0x70, 0x73, 0x48, 0x00, 0x52, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, - 0x73, 0x12, 0x2c, 0x0a, 0x11, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, - 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0f, - 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x42, - 0x08, 0x0a, 0x06, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x22, 0x3a, 0x0a, 0x15, 0x52, 0x65, 0x73, - 0x74, 0x6f, 0x72, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x75, 0x6d, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, - 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x52, 0x65, 0x73, - 0x74, 0x6f, 0x72, 0x65, 0x64, 0x22, 0x1b, 0x0a, 0x19, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x22, 0x3b, 0x0a, 0x18, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x43, 0x68, 0x61, 0x6e, - 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, - 0x0a, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x22, - 0x44, 0x0a, 0x12, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x16, 0x0a, - 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xb0, 0x01, 0x0a, 0x13, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, - 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3b, 0x0a, - 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72, - 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x70, - 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x72, 0x6f, - 0x6f, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x12, 0x3c, 0x0a, 0x1a, 0x61, 0x6c, - 0x6c, 0x6f, 0x77, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x65, 0x72, - 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, - 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x50, 0x65, 0x72, - 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x32, 0x0a, 0x14, 0x42, 0x61, 0x6b, 0x65, - 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x22, 0x18, 0x0a, 0x16, - 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x3b, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, - 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x20, 0x0a, 0x0c, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x04, 0x52, 0x0a, 0x72, 0x6f, 0x6f, 0x74, 0x4b, 0x65, 0x79, - 0x49, 0x64, 0x73, 0x22, 0x39, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, - 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, - 0x0a, 0x0b, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x22, 0x34, - 0x0a, 0x18, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, - 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x64, 0x22, 0x55, 0x0a, 0x16, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, - 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x3b, - 0x0a, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, - 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b, - 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x18, 0x0a, 0x16, 0x4c, - 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xe4, 0x01, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x64, 0x0a, 0x12, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x70, 0x65, 0x72, 0x6d, - 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4d, 0x65, - 0x74, 0x68, 0x6f, 0x64, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x52, 0x11, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x50, 0x65, 0x72, 0x6d, - 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x63, 0x0a, 0x16, 0x4d, 0x65, 0x74, 0x68, 0x6f, - 0x64, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x43, 0x68, 0x61, + 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x42, 0x08, 0x0a, 0x06, 0x62, 0x61, 0x63, 0x6b, 0x75, + 0x70, 0x22, 0x3a, 0x0a, 0x15, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x42, 0x61, 0x63, 0x6b, + 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x75, + 0x6d, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x22, 0x1b, 0x0a, + 0x19, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x75, + 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3b, 0x0a, 0x18, 0x56, 0x65, + 0x72, 0x69, 0x66, 0x79, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x68, 0x61, + 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x22, 0x44, 0x0a, 0x12, 0x4d, 0x61, 0x63, 0x61, 0x72, + 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, + 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xb0, 0x01, + 0x0a, 0x13, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3b, 0x0a, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x4b, 0x65, 0x79, + 0x49, 0x64, 0x12, 0x3c, 0x0a, 0x1a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x65, 0x78, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x45, 0x78, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, + 0x22, 0x32, 0x0a, 0x14, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x61, 0x63, 0x61, + 0x72, 0x6f, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x61, 0x63, 0x61, + 0x72, 0x6f, 0x6f, 0x6e, 0x22, 0x18, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x61, + 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x3b, + 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x0c, 0x72, 0x6f, 0x6f, + 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x04, 0x52, + 0x0a, 0x72, 0x6f, 0x6f, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x73, 0x22, 0x39, 0x0a, 0x17, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x6b, + 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x72, 0x6f, 0x6f, + 0x74, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x22, 0x34, 0x0a, 0x18, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x22, 0x55, 0x0a, 0x16, + 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x3b, 0x0a, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x73, 0x22, 0x18, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xe4, 0x01, + 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, 0x0a, 0x12, 0x6d, 0x65, 0x74, + 0x68, 0x6f, 0x64, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x50, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x11, 0x6d, 0x65, + 0x74, 0x68, 0x6f, 0x64, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x1a, + 0x63, 0x0a, 0x16, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x33, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x3a, 0x02, 0x38, 0x01, 0x22, 0xcc, 0x08, 0x0a, 0x07, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, + 0x12, 0x2e, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 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, 0x04, 0x63, 0x6f, 0x64, 0x65, + 0x12, 0x3b, 0x0a, 0x0e, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x75, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0d, + 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, + 0x09, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x08, 0x68, 0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x6e, + 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x68, 0x61, 0x5f, 0x32, 0x35, 0x36, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x0b, 0x6f, 0x6e, 0x69, 0x6f, 0x6e, 0x53, 0x68, 0x61, 0x32, 0x35, 0x36, 0x12, 0x1f, + 0x0a, 0x0b, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x63, 0x6c, 0x74, 0x76, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, + 0x14, 0x0a, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, + 0x66, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x30, 0x0a, 0x14, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, + 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x12, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x53, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, + 0x8b, 0x06, 0x0a, 0x0b, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, + 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x53, 0x45, 0x52, 0x56, 0x45, 0x44, 0x10, 0x00, 0x12, 0x28, 0x0a, + 0x24, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x4f, 0x52, 0x5f, 0x55, 0x4e, + 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x44, 0x45, + 0x54, 0x41, 0x49, 0x4c, 0x53, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x49, 0x4e, 0x43, 0x4f, 0x52, + 0x52, 0x45, 0x43, 0x54, 0x5f, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x4d, 0x4f, + 0x55, 0x4e, 0x54, 0x10, 0x02, 0x12, 0x1f, 0x0a, 0x1b, 0x46, 0x49, 0x4e, 0x41, 0x4c, 0x5f, 0x49, + 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x43, 0x4c, 0x54, 0x56, 0x5f, 0x45, 0x58, + 0x50, 0x49, 0x52, 0x59, 0x10, 0x03, 0x12, 0x1f, 0x0a, 0x1b, 0x46, 0x49, 0x4e, 0x41, 0x4c, 0x5f, + 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, + 0x4d, 0x4f, 0x55, 0x4e, 0x54, 0x10, 0x04, 0x12, 0x19, 0x0a, 0x15, 0x46, 0x49, 0x4e, 0x41, 0x4c, + 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, 0x5f, 0x54, 0x4f, 0x4f, 0x5f, 0x53, 0x4f, 0x4f, 0x4e, + 0x10, 0x05, 0x12, 0x11, 0x0a, 0x0d, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x52, 0x45, + 0x41, 0x4c, 0x4d, 0x10, 0x06, 0x12, 0x13, 0x0a, 0x0f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, 0x5f, + 0x54, 0x4f, 0x4f, 0x5f, 0x53, 0x4f, 0x4f, 0x4e, 0x10, 0x07, 0x12, 0x19, 0x0a, 0x15, 0x49, 0x4e, + 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x56, 0x45, 0x52, 0x53, + 0x49, 0x4f, 0x4e, 0x10, 0x08, 0x12, 0x16, 0x0a, 0x12, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, + 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x48, 0x4d, 0x41, 0x43, 0x10, 0x09, 0x12, 0x15, 0x0a, + 0x11, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x4b, + 0x45, 0x59, 0x10, 0x0a, 0x12, 0x18, 0x0a, 0x14, 0x41, 0x4d, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x42, + 0x45, 0x4c, 0x4f, 0x57, 0x5f, 0x4d, 0x49, 0x4e, 0x49, 0x4d, 0x55, 0x4d, 0x10, 0x0b, 0x12, 0x14, + 0x0a, 0x10, 0x46, 0x45, 0x45, 0x5f, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, + 0x4e, 0x54, 0x10, 0x0c, 0x12, 0x19, 0x0a, 0x15, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, + 0x54, 0x5f, 0x43, 0x4c, 0x54, 0x56, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, 0x10, 0x0d, 0x12, + 0x14, 0x0a, 0x10, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x5f, 0x44, 0x49, 0x53, 0x41, 0x42, + 0x4c, 0x45, 0x44, 0x10, 0x0e, 0x12, 0x1d, 0x0a, 0x19, 0x54, 0x45, 0x4d, 0x50, 0x4f, 0x52, 0x41, + 0x52, 0x59, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, + 0x52, 0x45, 0x10, 0x0f, 0x12, 0x21, 0x0a, 0x1d, 0x52, 0x45, 0x51, 0x55, 0x49, 0x52, 0x45, 0x44, + 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x46, 0x45, 0x41, 0x54, 0x55, 0x52, 0x45, 0x5f, 0x4d, 0x49, + 0x53, 0x53, 0x49, 0x4e, 0x47, 0x10, 0x10, 0x12, 0x24, 0x0a, 0x20, 0x52, 0x45, 0x51, 0x55, 0x49, + 0x52, 0x45, 0x44, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x5f, 0x46, 0x45, 0x41, 0x54, + 0x55, 0x52, 0x45, 0x5f, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4e, 0x47, 0x10, 0x11, 0x12, 0x15, 0x0a, + 0x11, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x4e, 0x45, 0x58, 0x54, 0x5f, 0x50, 0x45, + 0x45, 0x52, 0x10, 0x12, 0x12, 0x1a, 0x0a, 0x16, 0x54, 0x45, 0x4d, 0x50, 0x4f, 0x52, 0x41, 0x52, + 0x59, 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x13, + 0x12, 0x1a, 0x0a, 0x16, 0x50, 0x45, 0x52, 0x4d, 0x41, 0x4e, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x4f, + 0x44, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x14, 0x12, 0x1d, 0x0a, 0x19, + 0x50, 0x45, 0x52, 0x4d, 0x41, 0x4e, 0x45, 0x4e, 0x54, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, + 0x4c, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x15, 0x12, 0x12, 0x0a, 0x0e, 0x45, + 0x58, 0x50, 0x49, 0x52, 0x59, 0x5f, 0x54, 0x4f, 0x4f, 0x5f, 0x46, 0x41, 0x52, 0x10, 0x16, 0x12, + 0x0f, 0x0a, 0x0b, 0x4d, 0x50, 0x50, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x17, + 0x12, 0x19, 0x0a, 0x15, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, + 0x4e, 0x5f, 0x50, 0x41, 0x59, 0x4c, 0x4f, 0x41, 0x44, 0x10, 0x18, 0x12, 0x1a, 0x0a, 0x16, 0x49, + 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x42, 0x4c, 0x49, + 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x19, 0x12, 0x15, 0x0a, 0x10, 0x49, 0x4e, 0x54, 0x45, 0x52, + 0x4e, 0x41, 0x4c, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0xe5, 0x07, 0x12, 0x14, + 0x0a, 0x0f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, + 0x45, 0x10, 0xe6, 0x07, 0x12, 0x17, 0x0a, 0x12, 0x55, 0x4e, 0x52, 0x45, 0x41, 0x44, 0x41, 0x42, + 0x4c, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0xe7, 0x07, 0x4a, 0x04, 0x08, + 0x02, 0x10, 0x03, 0x22, 0xb3, 0x03, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x68, 0x61, 0x73, + 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x48, 0x61, + 0x73, 0x68, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, + 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x23, 0x0a, + 0x0d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x46, 0x6c, 0x61, + 0x67, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x66, 0x6c, + 0x61, 0x67, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x5f, + 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x0d, 0x74, 0x69, 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, + 0x2a, 0x0a, 0x11, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x5f, + 0x6d, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x68, 0x74, 0x6c, 0x63, + 0x4d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x62, + 0x61, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x62, + 0x61, 0x73, 0x65, 0x46, 0x65, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, + 0x74, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, + 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, + 0x6d, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x68, 0x74, + 0x6c, 0x63, 0x4d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x2a, 0x0a, + 0x11, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x5f, 0x64, 0x61, + 0x74, 0x61, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x65, 0x78, 0x74, 0x72, 0x61, 0x4f, + 0x70, 0x61, 0x71, 0x75, 0x65, 0x44, 0x61, 0x74, 0x61, 0x22, 0x5d, 0x0a, 0x0a, 0x4d, 0x61, 0x63, + 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x1c, 0x0a, + 0x09, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x09, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x03, 0x6f, + 0x70, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x4f, 0x70, 0x52, 0x03, 0x6f, 0x70, 0x73, 0x22, 0x36, 0x0a, 0x02, 0x4f, 0x70, 0x12, 0x16, + 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x22, 0x8e, 0x01, 0x0a, 0x13, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4d, 0x61, 0x63, 0x50, 0x65, 0x72, + 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x61, 0x63, 0x61, + 0x72, 0x6f, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6d, 0x61, 0x63, 0x61, + 0x72, 0x6f, 0x6f, 0x6e, 0x12, 0x3b, 0x0a, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x66, 0x75, 0x6c, 0x6c, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x66, 0x75, 0x6c, 0x6c, 0x4d, 0x65, 0x74, 0x68, 0x6f, + 0x64, 0x22, 0x2c, 0x0a, 0x14, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4d, 0x61, 0x63, 0x50, 0x65, 0x72, + 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x22, + 0xa4, 0x04, 0x0a, 0x14, 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x61, 0x77, 0x5f, 0x6d, + 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x72, + 0x61, 0x77, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x12, 0x36, 0x0a, 0x17, 0x63, 0x75, + 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x63, 0x61, 0x76, 0x65, 0x61, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x64, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x63, 0x75, 0x73, + 0x74, 0x6f, 0x6d, 0x43, 0x61, 0x76, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x34, 0x0a, 0x0b, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x61, 0x75, 0x74, + 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x41, 0x75, 0x74, 0x68, 0x48, 0x00, 0x52, 0x0a, 0x73, 0x74, + 0x72, 0x65, 0x61, 0x6d, 0x41, 0x75, 0x74, 0x68, 0x12, 0x2d, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x52, 0x50, 0x43, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x48, 0x00, 0x52, 0x07, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x52, 0x50, 0x43, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x48, 0x00, 0x52, 0x08, + 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0c, 0x72, 0x65, 0x67, 0x5f, + 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, + 0x52, 0x0b, 0x72, 0x65, 0x67, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x15, 0x0a, + 0x06, 0x6d, 0x73, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x6d, + 0x73, 0x67, 0x49, 0x64, 0x12, 0x55, 0x0a, 0x0e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x5f, 0x70, 0x61, 0x69, 0x72, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, + 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x50, 0x61, 0x69, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x6d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x50, 0x61, 0x69, 0x72, 0x73, 0x1a, 0x57, 0x0a, 0x12, 0x4d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x50, 0x61, 0x69, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x6b, 0x65, 0x79, 0x12, 0x33, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72, - 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, - 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xcc, 0x08, 0x0a, - 0x07, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x12, 0x2e, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, - 0x18, 0x01, 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, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x3b, 0x0a, 0x0e, 0x63, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, - 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x68, 0x74, 0x6c, 0x63, 0x4d, 0x73, - 0x61, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x6e, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x68, 0x61, 0x5f, - 0x32, 0x35, 0x36, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6f, 0x6e, 0x69, 0x6f, 0x6e, - 0x53, 0x68, 0x61, 0x32, 0x35, 0x36, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x65, - 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x63, 0x6c, 0x74, - 0x76, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x30, 0x0a, - 0x14, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, - 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x66, 0x61, 0x69, - 0x6c, 0x75, 0x72, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, - 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x8b, 0x06, 0x0a, 0x0b, 0x46, 0x61, 0x69, 0x6c, - 0x75, 0x72, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x53, 0x45, 0x52, - 0x56, 0x45, 0x44, 0x10, 0x00, 0x12, 0x28, 0x0a, 0x24, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, - 0x43, 0x54, 0x5f, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x50, 0x41, - 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x53, 0x10, 0x01, 0x12, - 0x1c, 0x0a, 0x18, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x50, 0x41, 0x59, - 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x4d, 0x4f, 0x55, 0x4e, 0x54, 0x10, 0x02, 0x12, 0x1f, 0x0a, - 0x1b, 0x46, 0x49, 0x4e, 0x41, 0x4c, 0x5f, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, - 0x5f, 0x43, 0x4c, 0x54, 0x56, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, 0x10, 0x03, 0x12, 0x1f, - 0x0a, 0x1b, 0x46, 0x49, 0x4e, 0x41, 0x4c, 0x5f, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, - 0x54, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x4d, 0x4f, 0x55, 0x4e, 0x54, 0x10, 0x04, 0x12, - 0x19, 0x0a, 0x15, 0x46, 0x49, 0x4e, 0x41, 0x4c, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, 0x5f, - 0x54, 0x4f, 0x4f, 0x5f, 0x53, 0x4f, 0x4f, 0x4e, 0x10, 0x05, 0x12, 0x11, 0x0a, 0x0d, 0x49, 0x4e, - 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x52, 0x45, 0x41, 0x4c, 0x4d, 0x10, 0x06, 0x12, 0x13, 0x0a, - 0x0f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, 0x5f, 0x54, 0x4f, 0x4f, 0x5f, 0x53, 0x4f, 0x4f, 0x4e, - 0x10, 0x07, 0x12, 0x19, 0x0a, 0x15, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4f, 0x4e, - 0x49, 0x4f, 0x4e, 0x5f, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x08, 0x12, 0x16, 0x0a, - 0x12, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x48, - 0x4d, 0x41, 0x43, 0x10, 0x09, 0x12, 0x15, 0x0a, 0x11, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, - 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x0a, 0x12, 0x18, 0x0a, 0x14, - 0x41, 0x4d, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x42, 0x45, 0x4c, 0x4f, 0x57, 0x5f, 0x4d, 0x49, 0x4e, - 0x49, 0x4d, 0x55, 0x4d, 0x10, 0x0b, 0x12, 0x14, 0x0a, 0x10, 0x46, 0x45, 0x45, 0x5f, 0x49, 0x4e, - 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x10, 0x0c, 0x12, 0x19, 0x0a, 0x15, - 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x43, 0x4c, 0x54, 0x56, 0x5f, 0x45, - 0x58, 0x50, 0x49, 0x52, 0x59, 0x10, 0x0d, 0x12, 0x14, 0x0a, 0x10, 0x43, 0x48, 0x41, 0x4e, 0x4e, - 0x45, 0x4c, 0x5f, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x44, 0x10, 0x0e, 0x12, 0x1d, 0x0a, - 0x19, 0x54, 0x45, 0x4d, 0x50, 0x4f, 0x52, 0x41, 0x52, 0x59, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, - 0x45, 0x4c, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x0f, 0x12, 0x21, 0x0a, 0x1d, - 0x52, 0x45, 0x51, 0x55, 0x49, 0x52, 0x45, 0x44, 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x46, 0x45, - 0x41, 0x54, 0x55, 0x52, 0x45, 0x5f, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4e, 0x47, 0x10, 0x10, 0x12, - 0x24, 0x0a, 0x20, 0x52, 0x45, 0x51, 0x55, 0x49, 0x52, 0x45, 0x44, 0x5f, 0x43, 0x48, 0x41, 0x4e, - 0x4e, 0x45, 0x4c, 0x5f, 0x46, 0x45, 0x41, 0x54, 0x55, 0x52, 0x45, 0x5f, 0x4d, 0x49, 0x53, 0x53, - 0x49, 0x4e, 0x47, 0x10, 0x11, 0x12, 0x15, 0x0a, 0x11, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, - 0x5f, 0x4e, 0x45, 0x58, 0x54, 0x5f, 0x50, 0x45, 0x45, 0x52, 0x10, 0x12, 0x12, 0x1a, 0x0a, 0x16, - 0x54, 0x45, 0x4d, 0x50, 0x4f, 0x52, 0x41, 0x52, 0x59, 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x46, - 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x13, 0x12, 0x1a, 0x0a, 0x16, 0x50, 0x45, 0x52, 0x4d, - 0x41, 0x4e, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, - 0x52, 0x45, 0x10, 0x14, 0x12, 0x1d, 0x0a, 0x19, 0x50, 0x45, 0x52, 0x4d, 0x41, 0x4e, 0x45, 0x4e, - 0x54, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, - 0x45, 0x10, 0x15, 0x12, 0x12, 0x0a, 0x0e, 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, 0x5f, 0x54, 0x4f, - 0x4f, 0x5f, 0x46, 0x41, 0x52, 0x10, 0x16, 0x12, 0x0f, 0x0a, 0x0b, 0x4d, 0x50, 0x50, 0x5f, 0x54, - 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x17, 0x12, 0x19, 0x0a, 0x15, 0x49, 0x4e, 0x56, 0x41, - 0x4c, 0x49, 0x44, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x50, 0x41, 0x59, 0x4c, 0x4f, 0x41, - 0x44, 0x10, 0x18, 0x12, 0x1a, 0x0a, 0x16, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4f, - 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x42, 0x4c, 0x49, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x19, 0x12, - 0x15, 0x0a, 0x10, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x5f, 0x46, 0x41, 0x49, 0x4c, - 0x55, 0x52, 0x45, 0x10, 0xe5, 0x07, 0x12, 0x14, 0x0a, 0x0f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, - 0x4e, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0xe6, 0x07, 0x12, 0x17, 0x0a, 0x12, - 0x55, 0x4e, 0x52, 0x45, 0x41, 0x44, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, - 0x52, 0x45, 0x10, 0xe7, 0x07, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x22, 0xb3, 0x03, 0x0a, 0x0d, - 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1c, 0x0a, - 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, - 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x09, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, - 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, - 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x0d, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, - 0x26, 0x0a, 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x64, 0x65, 0x6c, - 0x74, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x74, 0x69, 0x6d, 0x65, 0x4c, 0x6f, - 0x63, 0x6b, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x2a, 0x0a, 0x11, 0x68, 0x74, 0x6c, 0x63, 0x5f, - 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x0f, 0x68, 0x74, 0x6c, 0x63, 0x4d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x4d, - 0x73, 0x61, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x18, - 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x62, 0x61, 0x73, 0x65, 0x46, 0x65, 0x65, 0x12, 0x19, - 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x07, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x68, 0x74, 0x6c, - 0x63, 0x5f, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0b, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x68, 0x74, 0x6c, 0x63, 0x4d, 0x61, 0x78, 0x69, 0x6d, 0x75, - 0x6d, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, 0x6f, - 0x70, 0x61, 0x71, 0x75, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x0f, 0x65, 0x78, 0x74, 0x72, 0x61, 0x4f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x44, 0x61, 0x74, - 0x61, 0x22, 0x5d, 0x0a, 0x0a, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x64, 0x12, - 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, - 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, - 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, - 0x65, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x03, 0x6f, 0x70, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x09, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x70, 0x52, 0x03, 0x6f, 0x70, 0x73, - 0x22, 0x36, 0x0a, 0x02, 0x4f, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x18, - 0x0a, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x8e, 0x01, 0x0a, 0x13, 0x43, 0x68, 0x65, - 0x63, 0x6b, 0x4d, 0x61, 0x63, 0x50, 0x65, 0x72, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x08, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x12, 0x3b, 0x0a, 0x0b, - 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, - 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x70, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x66, 0x75, 0x6c, - 0x6c, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x66, - 0x75, 0x6c, 0x6c, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x22, 0x2c, 0x0a, 0x14, 0x43, 0x68, 0x65, - 0x63, 0x6b, 0x4d, 0x61, 0x63, 0x50, 0x65, 0x72, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x22, 0xa4, 0x04, 0x0a, 0x14, 0x52, 0x50, 0x43, 0x4d, - 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, - 0x21, 0x0a, 0x0c, 0x72, 0x61, 0x77, 0x5f, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x72, 0x61, 0x77, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, - 0x6f, 0x6e, 0x12, 0x36, 0x0a, 0x17, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x63, 0x61, 0x76, - 0x65, 0x61, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x15, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x61, 0x76, 0x65, 0x61, - 0x74, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x34, 0x0a, 0x0b, 0x73, 0x74, - 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x41, 0x75, - 0x74, 0x68, 0x48, 0x00, 0x52, 0x0a, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x41, 0x75, 0x74, 0x68, - 0x12, 0x2d, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x50, 0x43, 0x4d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x48, 0x00, 0x52, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x2f, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x50, 0x43, 0x4d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x48, 0x00, 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x23, 0x0a, 0x0c, 0x72, 0x65, 0x67, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x0b, 0x72, 0x65, 0x67, 0x43, 0x6f, 0x6d, - 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x6d, 0x73, 0x67, 0x5f, 0x69, 0x64, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x6d, 0x73, 0x67, 0x49, 0x64, 0x12, 0x55, 0x0a, 0x0e, - 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x70, 0x61, 0x69, 0x72, 0x73, 0x18, 0x09, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x50, 0x43, - 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x50, 0x61, 0x69, 0x72, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x50, 0x61, - 0x69, 0x72, 0x73, 0x1a, 0x57, 0x0a, 0x12, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x50, - 0x61, 0x69, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2b, 0x0a, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x10, 0x0a, 0x0e, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x22, 0x28, - 0x0a, 0x0e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, - 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0x34, 0x0a, 0x0a, 0x53, 0x74, 0x72, 0x65, - 0x61, 0x6d, 0x41, 0x75, 0x74, 0x68, 0x12, 0x26, 0x0a, 0x0f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, - 0x5f, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0d, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x46, 0x75, 0x6c, 0x6c, 0x55, 0x72, 0x69, 0x22, 0xab, - 0x01, 0x0a, 0x0a, 0x52, 0x50, 0x43, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x26, 0x0a, - 0x0f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x75, 0x72, 0x69, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x46, 0x75, - 0x6c, 0x6c, 0x55, 0x72, 0x69, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, - 0x72, 0x70, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x74, 0x72, 0x65, 0x61, - 0x6d, 0x52, 0x70, 0x63, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x79, 0x70, 0x65, 0x4e, 0x61, 0x6d, - 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, - 0x64, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x22, 0xc0, 0x01, 0x0a, - 0x15, 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x0a, 0x72, 0x65, 0x66, 0x5f, 0x6d, 0x73, - 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x72, 0x65, 0x66, 0x4d, - 0x73, 0x67, 0x49, 0x64, 0x12, 0x3b, 0x0a, 0x08, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, - 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, - 0x72, 0x12, 0x36, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x74, 0x65, - 0x72, 0x63, 0x65, 0x70, 0x74, 0x46, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x48, 0x00, 0x52, - 0x08, 0x66, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x42, 0x14, 0x0a, 0x12, 0x6d, 0x69, 0x64, - 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, - 0xa6, 0x01, 0x0a, 0x16, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, - 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x27, 0x0a, 0x0f, 0x6d, 0x69, - 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x4e, - 0x61, 0x6d, 0x65, 0x12, 0x3d, 0x0a, 0x1b, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x6d, 0x61, - 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x5f, 0x63, 0x61, 0x76, 0x65, 0x61, 0x74, 0x5f, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x18, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, - 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x43, 0x61, 0x76, 0x65, 0x61, 0x74, 0x4e, 0x61, - 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x5f, - 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x72, 0x65, 0x61, 0x64, - 0x4f, 0x6e, 0x6c, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x22, 0x8b, 0x01, 0x0a, 0x11, 0x49, 0x6e, 0x74, - 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x46, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x14, - 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, - 0x72, 0x72, 0x6f, 0x72, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x5f, - 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, - 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x35, 0x0a, 0x16, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x73, - 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x15, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x69, - 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x2a, 0xcb, 0x02, 0x0a, 0x10, 0x4f, 0x75, 0x74, 0x70, 0x75, - 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x53, - 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, - 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x53, 0x43, 0x52, 0x49, - 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x48, - 0x41, 0x53, 0x48, 0x10, 0x01, 0x12, 0x26, 0x0a, 0x22, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, - 0x54, 0x59, 0x50, 0x45, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x56, 0x30, 0x5f, - 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x02, 0x12, 0x26, 0x0a, + 0x6b, 0x65, 0x79, 0x12, 0x2b, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x3a, 0x02, 0x38, 0x01, 0x42, 0x10, 0x0a, 0x0e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, + 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x22, 0x28, 0x0a, 0x0e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, + 0x22, 0x34, 0x0a, 0x0a, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x41, 0x75, 0x74, 0x68, 0x12, 0x26, + 0x0a, 0x0f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x75, 0x72, + 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x46, + 0x75, 0x6c, 0x6c, 0x55, 0x72, 0x69, 0x22, 0xab, 0x01, 0x0a, 0x0a, 0x52, 0x50, 0x43, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, + 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, + 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x46, 0x75, 0x6c, 0x6c, 0x55, 0x72, 0x69, 0x12, 0x1d, 0x0a, + 0x0a, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x72, 0x70, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x09, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x70, 0x63, 0x12, 0x1b, 0x0a, 0x09, + 0x74, 0x79, 0x70, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x74, 0x79, 0x70, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x65, 0x72, + 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, + 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x45, + 0x72, 0x72, 0x6f, 0x72, 0x22, 0xc0, 0x01, 0x0a, 0x15, 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, + 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, + 0x0a, 0x0a, 0x72, 0x65, 0x66, 0x5f, 0x6d, 0x73, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x08, 0x72, 0x65, 0x66, 0x4d, 0x73, 0x67, 0x49, 0x64, 0x12, 0x3b, 0x0a, 0x08, + 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, + 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, + 0x08, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x36, 0x0a, 0x08, 0x66, 0x65, 0x65, + 0x64, 0x62, 0x61, 0x63, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x46, 0x65, 0x65, + 0x64, 0x62, 0x61, 0x63, 0x6b, 0x48, 0x00, 0x52, 0x08, 0x66, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, + 0x6b, 0x42, 0x14, 0x0a, 0x12, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x5f, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xa6, 0x01, 0x0a, 0x16, 0x4d, 0x69, 0x64, 0x64, + 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x27, 0x0a, 0x0f, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6d, 0x69, 0x64, + 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x3d, 0x0a, 0x1b, 0x63, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x5f, 0x63, + 0x61, 0x76, 0x65, 0x61, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x18, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, + 0x43, 0x61, 0x76, 0x65, 0x61, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x65, + 0x61, 0x64, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0c, 0x72, 0x65, 0x61, 0x64, 0x4f, 0x6e, 0x6c, 0x79, 0x4d, 0x6f, 0x64, 0x65, + 0x22, 0x8b, 0x01, 0x0a, 0x11, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x46, 0x65, + 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x29, 0x0a, 0x10, + 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x16, 0x72, 0x65, 0x70, 0x6c, 0x61, + 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x15, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x2a, 0xcb, + 0x02, 0x0a, 0x10, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x00, + 0x12, 0x1b, 0x0a, 0x17, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, + 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x01, 0x12, 0x26, 0x0a, 0x22, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x57, 0x49, 0x54, - 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x56, 0x30, 0x5f, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x48, - 0x41, 0x53, 0x48, 0x10, 0x03, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, - 0x54, 0x59, 0x50, 0x45, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x10, 0x04, 0x12, 0x18, 0x0a, - 0x14, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x55, 0x4c, - 0x54, 0x49, 0x53, 0x49, 0x47, 0x10, 0x05, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x43, 0x52, 0x49, 0x50, - 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4e, 0x55, 0x4c, 0x4c, 0x44, 0x41, 0x54, 0x41, 0x10, - 0x06, 0x12, 0x1c, 0x0a, 0x18, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, - 0x5f, 0x4e, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x4e, 0x44, 0x41, 0x52, 0x44, 0x10, 0x07, 0x12, - 0x1f, 0x0a, 0x1b, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x57, - 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x08, - 0x12, 0x22, 0x0a, 0x1e, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, - 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x56, 0x31, 0x5f, 0x54, 0x41, 0x50, 0x52, 0x4f, - 0x4f, 0x54, 0x10, 0x09, 0x2a, 0x62, 0x0a, 0x15, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x1e, 0x0a, - 0x1a, 0x53, 0x54, 0x52, 0x41, 0x54, 0x45, 0x47, 0x59, 0x5f, 0x55, 0x53, 0x45, 0x5f, 0x47, 0x4c, - 0x4f, 0x42, 0x41, 0x4c, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x10, 0x00, 0x12, 0x14, 0x0a, - 0x10, 0x53, 0x54, 0x52, 0x41, 0x54, 0x45, 0x47, 0x59, 0x5f, 0x4c, 0x41, 0x52, 0x47, 0x45, 0x53, - 0x54, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x54, 0x52, 0x41, 0x54, 0x45, 0x47, 0x59, 0x5f, - 0x52, 0x41, 0x4e, 0x44, 0x4f, 0x4d, 0x10, 0x02, 0x2a, 0xac, 0x01, 0x0a, 0x0b, 0x41, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x13, 0x57, 0x49, 0x54, 0x4e, + 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x56, 0x30, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, + 0x41, 0x53, 0x48, 0x10, 0x02, 0x12, 0x26, 0x0a, 0x22, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, + 0x54, 0x59, 0x50, 0x45, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x56, 0x30, 0x5f, + 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x03, 0x12, 0x16, 0x0a, + 0x12, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x50, 0x55, 0x42, + 0x4b, 0x45, 0x59, 0x10, 0x04, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, + 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x55, 0x4c, 0x54, 0x49, 0x53, 0x49, 0x47, 0x10, 0x05, 0x12, + 0x18, 0x0a, 0x14, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4e, + 0x55, 0x4c, 0x4c, 0x44, 0x41, 0x54, 0x41, 0x10, 0x06, 0x12, 0x1c, 0x0a, 0x18, 0x53, 0x43, 0x52, + 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4e, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, + 0x4e, 0x44, 0x41, 0x52, 0x44, 0x10, 0x07, 0x12, 0x1f, 0x0a, 0x1b, 0x53, 0x43, 0x52, 0x49, 0x50, + 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x55, + 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x08, 0x12, 0x22, 0x0a, 0x1e, 0x53, 0x43, 0x52, 0x49, + 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, + 0x56, 0x31, 0x5f, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x10, 0x09, 0x2a, 0x62, 0x0a, 0x15, + 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, + 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x1e, 0x0a, 0x1a, 0x53, 0x54, 0x52, 0x41, 0x54, 0x45, 0x47, + 0x59, 0x5f, 0x55, 0x53, 0x45, 0x5f, 0x47, 0x4c, 0x4f, 0x42, 0x41, 0x4c, 0x5f, 0x43, 0x4f, 0x4e, + 0x46, 0x49, 0x47, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x54, 0x52, 0x41, 0x54, 0x45, 0x47, + 0x59, 0x5f, 0x4c, 0x41, 0x52, 0x47, 0x45, 0x53, 0x54, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x53, + 0x54, 0x52, 0x41, 0x54, 0x45, 0x47, 0x59, 0x5f, 0x52, 0x41, 0x4e, 0x44, 0x4f, 0x4d, 0x10, 0x02, + 0x2a, 0xac, 0x01, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x17, 0x0a, 0x13, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, + 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x4e, 0x45, 0x53, + 0x54, 0x45, 0x44, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, + 0x01, 0x12, 0x1e, 0x0a, 0x1a, 0x55, 0x4e, 0x55, 0x53, 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, - 0x00, 0x12, 0x16, 0x0a, 0x12, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, 0x50, 0x55, 0x42, 0x4b, - 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x01, 0x12, 0x1e, 0x0a, 0x1a, 0x55, 0x4e, 0x55, - 0x53, 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, - 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x02, 0x12, 0x1d, 0x0a, 0x19, 0x55, 0x4e, 0x55, - 0x53, 0x45, 0x44, 0x5f, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, - 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x54, 0x41, 0x50, 0x52, - 0x4f, 0x4f, 0x54, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x10, 0x04, 0x12, 0x19, 0x0a, 0x15, - 0x55, 0x4e, 0x55, 0x53, 0x45, 0x44, 0x5f, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x50, - 0x55, 0x42, 0x4b, 0x45, 0x59, 0x10, 0x05, 0x2a, 0xa8, 0x01, 0x0a, 0x0e, 0x43, 0x6f, 0x6d, 0x6d, - 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x55, 0x4e, - 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, - 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x4c, 0x45, 0x47, 0x41, 0x43, - 0x59, 0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, 0x41, 0x54, 0x49, 0x43, 0x5f, 0x52, 0x45, - 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x4e, - 0x43, 0x48, 0x4f, 0x52, 0x53, 0x10, 0x03, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x43, 0x52, 0x49, 0x50, - 0x54, 0x5f, 0x45, 0x4e, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x44, 0x5f, 0x4c, 0x45, 0x41, 0x53, 0x45, - 0x10, 0x04, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x49, 0x4d, 0x50, 0x4c, 0x45, 0x5f, 0x54, 0x41, 0x50, - 0x52, 0x4f, 0x4f, 0x54, 0x10, 0x05, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x49, 0x4d, 0x50, 0x4c, 0x45, - 0x5f, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x4f, 0x56, 0x45, 0x52, 0x4c, 0x41, 0x59, - 0x10, 0x06, 0x2a, 0x61, 0x0a, 0x09, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x12, - 0x15, 0x0a, 0x11, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x4b, - 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, - 0x54, 0x4f, 0x52, 0x5f, 0x4c, 0x4f, 0x43, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x49, - 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x10, - 0x02, 0x12, 0x12, 0x0a, 0x0e, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x42, - 0x4f, 0x54, 0x48, 0x10, 0x03, 0x2a, 0x60, 0x0a, 0x0e, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, - 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x59, 0x50, 0x45, 0x5f, - 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x4e, 0x43, - 0x48, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x49, 0x4e, 0x43, 0x4f, 0x4d, 0x49, 0x4e, - 0x47, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x4f, 0x55, 0x54, 0x47, - 0x4f, 0x49, 0x4e, 0x47, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x43, - 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x10, 0x04, 0x2a, 0x71, 0x0a, 0x11, 0x52, 0x65, 0x73, 0x6f, 0x6c, - 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x63, 0x6f, 0x6d, 0x65, 0x12, 0x13, 0x0a, 0x0f, - 0x4f, 0x55, 0x54, 0x43, 0x4f, 0x4d, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, - 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x4c, 0x41, 0x49, 0x4d, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0d, - 0x0a, 0x09, 0x55, 0x4e, 0x43, 0x4c, 0x41, 0x49, 0x4d, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0d, 0x0a, - 0x09, 0x41, 0x42, 0x41, 0x4e, 0x44, 0x4f, 0x4e, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0f, 0x0a, 0x0b, - 0x46, 0x49, 0x52, 0x53, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x47, 0x45, 0x10, 0x04, 0x12, 0x0b, 0x0a, - 0x07, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x05, 0x2a, 0x39, 0x0a, 0x0e, 0x4e, 0x6f, - 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, - 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x42, 0x45, 0x54, - 0x57, 0x45, 0x45, 0x4e, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x43, 0x45, 0x4e, 0x54, 0x52, 0x41, 0x4c, - 0x49, 0x54, 0x59, 0x10, 0x01, 0x2a, 0x3b, 0x0a, 0x10, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, - 0x48, 0x54, 0x4c, 0x43, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x41, 0x43, 0x43, - 0x45, 0x50, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x45, 0x54, 0x54, 0x4c, - 0x45, 0x44, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x45, 0x44, - 0x10, 0x02, 0x2a, 0xf6, 0x01, 0x0a, 0x14, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x46, 0x61, - 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x13, 0x46, - 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, - 0x4e, 0x45, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, - 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x01, - 0x12, 0x1b, 0x0a, 0x17, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, - 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x5f, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x10, 0x02, 0x12, 0x18, 0x0a, - 0x14, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, - 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x2c, 0x0a, 0x28, 0x46, 0x41, 0x49, 0x4c, 0x55, - 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 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, 0x04, 0x12, 0x27, 0x0a, 0x23, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, - 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, - 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x42, 0x41, 0x4c, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x05, 0x12, 0x1b, - 0x0a, 0x17, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, - 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x45, 0x44, 0x10, 0x06, 0x2a, 0x89, 0x05, 0x0a, 0x0a, - 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x42, 0x69, 0x74, 0x12, 0x18, 0x0a, 0x14, 0x44, 0x41, - 0x54, 0x41, 0x4c, 0x4f, 0x53, 0x53, 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x45, 0x43, 0x54, 0x5f, 0x52, - 0x45, 0x51, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x44, 0x41, 0x54, 0x41, 0x4c, 0x4f, 0x53, 0x53, - 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x45, 0x43, 0x54, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x01, 0x12, 0x17, - 0x0a, 0x13, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x4c, 0x5f, 0x52, 0x4f, 0x55, 0x49, 0x4e, 0x47, - 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x10, 0x03, 0x12, 0x1f, 0x0a, 0x1b, 0x55, 0x50, 0x46, 0x52, 0x4f, - 0x4e, 0x54, 0x5f, 0x53, 0x48, 0x55, 0x54, 0x44, 0x4f, 0x57, 0x4e, 0x5f, 0x53, 0x43, 0x52, 0x49, - 0x50, 0x54, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x04, 0x12, 0x1f, 0x0a, 0x1b, 0x55, 0x50, 0x46, 0x52, - 0x4f, 0x4e, 0x54, 0x5f, 0x53, 0x48, 0x55, 0x54, 0x44, 0x4f, 0x57, 0x4e, 0x5f, 0x53, 0x43, 0x52, - 0x49, 0x50, 0x54, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x05, 0x12, 0x16, 0x0a, 0x12, 0x47, 0x4f, 0x53, - 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x49, 0x45, 0x53, 0x5f, 0x52, 0x45, 0x51, 0x10, - 0x06, 0x12, 0x16, 0x0a, 0x12, 0x47, 0x4f, 0x53, 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, - 0x49, 0x45, 0x53, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x07, 0x12, 0x11, 0x0a, 0x0d, 0x54, 0x4c, 0x56, - 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x08, 0x12, 0x11, 0x0a, 0x0d, - 0x54, 0x4c, 0x56, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x09, 0x12, - 0x1a, 0x0a, 0x16, 0x45, 0x58, 0x54, 0x5f, 0x47, 0x4f, 0x53, 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, - 0x45, 0x52, 0x49, 0x45, 0x53, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x0a, 0x12, 0x1a, 0x0a, 0x16, 0x45, - 0x58, 0x54, 0x5f, 0x47, 0x4f, 0x53, 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x49, 0x45, - 0x53, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x0b, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x54, 0x41, 0x54, 0x49, - 0x43, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x52, 0x45, 0x51, - 0x10, 0x0c, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x54, 0x41, 0x54, 0x49, 0x43, 0x5f, 0x52, 0x45, 0x4d, - 0x4f, 0x54, 0x45, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x0d, 0x12, 0x14, 0x0a, - 0x10, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x44, 0x44, 0x52, 0x5f, 0x52, 0x45, - 0x51, 0x10, 0x0e, 0x12, 0x14, 0x0a, 0x10, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x41, - 0x44, 0x44, 0x52, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x0f, 0x12, 0x0b, 0x0a, 0x07, 0x4d, 0x50, 0x50, - 0x5f, 0x52, 0x45, 0x51, 0x10, 0x10, 0x12, 0x0b, 0x0a, 0x07, 0x4d, 0x50, 0x50, 0x5f, 0x4f, 0x50, - 0x54, 0x10, 0x11, 0x12, 0x16, 0x0a, 0x12, 0x57, 0x55, 0x4d, 0x42, 0x4f, 0x5f, 0x43, 0x48, 0x41, - 0x4e, 0x4e, 0x45, 0x4c, 0x53, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x12, 0x12, 0x16, 0x0a, 0x12, 0x57, - 0x55, 0x4d, 0x42, 0x4f, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x53, 0x5f, 0x4f, 0x50, - 0x54, 0x10, 0x13, 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, 0x52, - 0x45, 0x51, 0x10, 0x14, 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, - 0x4f, 0x50, 0x54, 0x10, 0x15, 0x12, 0x1d, 0x0a, 0x19, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, - 0x5f, 0x5a, 0x45, 0x52, 0x4f, 0x5f, 0x46, 0x45, 0x45, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x52, - 0x45, 0x51, 0x10, 0x16, 0x12, 0x1d, 0x0a, 0x19, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, - 0x5a, 0x45, 0x52, 0x4f, 0x5f, 0x46, 0x45, 0x45, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x50, - 0x54, 0x10, 0x17, 0x12, 0x1b, 0x0a, 0x17, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x5f, 0x42, 0x4c, 0x49, - 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x49, 0x52, 0x45, 0x44, 0x10, 0x18, - 0x12, 0x1b, 0x0a, 0x17, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x5f, 0x42, 0x4c, 0x49, 0x4e, 0x44, 0x49, - 0x4e, 0x47, 0x5f, 0x4f, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x41, 0x4c, 0x10, 0x19, 0x12, 0x0b, 0x0a, - 0x07, 0x41, 0x4d, 0x50, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x1e, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x4d, - 0x50, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x1f, 0x2a, 0xac, 0x01, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x12, 0x1a, 0x0a, 0x16, 0x55, 0x50, 0x44, - 0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, - 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, - 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, - 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, - 0x55, 0x52, 0x45, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x46, 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x02, 0x12, - 0x1f, 0x0a, 0x1b, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, - 0x45, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x5f, 0x45, 0x52, 0x52, 0x10, 0x03, - 0x12, 0x24, 0x0a, 0x20, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, - 0x52, 0x45, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x50, 0x41, 0x52, 0x41, 0x4d, - 0x45, 0x54, 0x45, 0x52, 0x10, 0x04, 0x32, 0xc3, 0x27, 0x0a, 0x09, 0x4c, 0x69, 0x67, 0x68, 0x74, - 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x4a, 0x0a, 0x0d, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, - 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x61, - 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x4d, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, - 0x63, 0x65, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x4b, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, - 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x44, 0x0a, 0x0b, - 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x12, 0x19, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, - 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x09, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, - 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x69, 0x6e, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, - 0x74, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, - 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x15, 0x53, 0x75, 0x62, 0x73, - 0x63, 0x72, 0x69, 0x62, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, - 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x30, 0x01, 0x12, 0x3b, 0x0a, 0x08, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, - 0x6e, 0x79, 0x12, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4d, - 0x61, 0x6e, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0a, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x12, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0b, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, - 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x0d, - 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0b, 0x43, 0x6f, 0x6e, 0x6e, - 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, - 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, - 0x0a, 0x0e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, - 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, - 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, - 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, - 0x09, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x17, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x50, 0x65, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, - 0x13, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x50, 0x65, 0x65, 0x72, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x65, - 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x1a, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x38, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, - 0x6f, 0x12, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, - 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x47, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x44, 0x65, 0x62, 0x75, 0x67, 0x49, 0x6e, 0x66, 0x6f, - 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x62, 0x75, - 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x62, 0x75, 0x67, 0x49, 0x6e, 0x66, - 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x47, 0x65, 0x74, - 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1d, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, - 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, - 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x50, - 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1d, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, - 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1a, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x16, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, - 0x69, 0x62, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, - 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x30, 0x01, 0x12, 0x4d, - 0x0a, 0x0e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, - 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, - 0x0f, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x53, 0x79, 0x6e, 0x63, - 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, - 0x12, 0x43, 0x0a, 0x0b, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, - 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x30, 0x01, 0x12, 0x53, 0x0a, 0x10, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, - 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x10, 0x46, 0x75, - 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x74, 0x65, 0x70, 0x12, 0x1b, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x72, - 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x67, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, - 0x53, 0x74, 0x65, 0x70, 0x52, 0x65, 0x73, 0x70, 0x12, 0x50, 0x0a, 0x0f, 0x43, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, 0x46, 0x0a, 0x0c, 0x43, 0x6c, - 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, - 0x6c, 0x6f, 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x30, 0x01, 0x12, 0x4d, 0x0a, 0x0e, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x62, 0x61, - 0x6e, 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x62, 0x61, 0x6e, 0x64, - 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x3f, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, - 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, - 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x28, 0x01, - 0x30, 0x01, 0x12, 0x3f, 0x0a, 0x0f, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, - 0x74, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, - 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, - 0x88, 0x02, 0x01, 0x12, 0x46, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, - 0x74, 0x65, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, - 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x28, 0x01, 0x30, 0x01, 0x12, 0x46, 0x0a, 0x0f, 0x53, - 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x19, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, - 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, - 0x88, 0x02, 0x01, 0x12, 0x37, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, - 0x65, 0x12, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, - 0x65, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, - 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x0c, - 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x19, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, + 0x02, 0x12, 0x1d, 0x0a, 0x19, 0x55, 0x4e, 0x55, 0x53, 0x45, 0x44, 0x5f, 0x4e, 0x45, 0x53, 0x54, + 0x45, 0x44, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x03, + 0x12, 0x12, 0x0a, 0x0e, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x50, 0x55, 0x42, 0x4b, + 0x45, 0x59, 0x10, 0x04, 0x12, 0x19, 0x0a, 0x15, 0x55, 0x4e, 0x55, 0x53, 0x45, 0x44, 0x5f, 0x54, + 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x10, 0x05, 0x2a, + 0xa8, 0x01, 0x0a, 0x0e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x43, 0x4f, + 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x00, 0x12, + 0x0a, 0x0a, 0x06, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, 0x53, + 0x54, 0x41, 0x54, 0x49, 0x43, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x4b, 0x45, 0x59, + 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x10, 0x03, 0x12, + 0x19, 0x0a, 0x15, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x45, 0x4e, 0x46, 0x4f, 0x52, 0x43, + 0x45, 0x44, 0x5f, 0x4c, 0x45, 0x41, 0x53, 0x45, 0x10, 0x04, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x49, + 0x4d, 0x50, 0x4c, 0x45, 0x5f, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x10, 0x05, 0x12, 0x1a, + 0x0a, 0x16, 0x53, 0x49, 0x4d, 0x50, 0x4c, 0x45, 0x5f, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, + 0x5f, 0x4f, 0x56, 0x45, 0x52, 0x4c, 0x41, 0x59, 0x10, 0x06, 0x2a, 0x61, 0x0a, 0x09, 0x49, 0x6e, + 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x15, 0x0a, 0x11, 0x49, 0x4e, 0x49, 0x54, 0x49, + 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x13, + 0x0a, 0x0f, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x4c, 0x4f, 0x43, 0x41, + 0x4c, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x4f, 0x52, + 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x10, 0x02, 0x12, 0x12, 0x0a, 0x0e, 0x49, 0x4e, 0x49, + 0x54, 0x49, 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x42, 0x4f, 0x54, 0x48, 0x10, 0x03, 0x2a, 0x60, 0x0a, + 0x0e, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x10, 0x0a, 0x0c, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, + 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x11, 0x0a, + 0x0d, 0x49, 0x4e, 0x43, 0x4f, 0x4d, 0x49, 0x4e, 0x47, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x10, 0x02, + 0x12, 0x11, 0x0a, 0x0d, 0x4f, 0x55, 0x54, 0x47, 0x4f, 0x49, 0x4e, 0x47, 0x5f, 0x48, 0x54, 0x4c, + 0x43, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x10, 0x04, 0x2a, + 0x71, 0x0a, 0x11, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, + 0x63, 0x6f, 0x6d, 0x65, 0x12, 0x13, 0x0a, 0x0f, 0x4f, 0x55, 0x54, 0x43, 0x4f, 0x4d, 0x45, 0x5f, + 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x4c, 0x41, + 0x49, 0x4d, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e, 0x43, 0x4c, 0x41, 0x49, + 0x4d, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x41, 0x42, 0x41, 0x4e, 0x44, 0x4f, 0x4e, + 0x45, 0x44, 0x10, 0x03, 0x12, 0x0f, 0x0a, 0x0b, 0x46, 0x49, 0x52, 0x53, 0x54, 0x5f, 0x53, 0x54, + 0x41, 0x47, 0x45, 0x10, 0x04, 0x12, 0x0b, 0x0a, 0x07, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, + 0x10, 0x05, 0x2a, 0x39, 0x0a, 0x0e, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, + 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x42, 0x45, 0x54, 0x57, 0x45, 0x45, 0x4e, 0x4e, 0x45, 0x53, 0x53, + 0x5f, 0x43, 0x45, 0x4e, 0x54, 0x52, 0x41, 0x4c, 0x49, 0x54, 0x59, 0x10, 0x01, 0x2a, 0x3b, 0x0a, + 0x10, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x48, 0x54, 0x4c, 0x43, 0x53, 0x74, 0x61, 0x74, + 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, + 0x0b, 0x0a, 0x07, 0x53, 0x45, 0x54, 0x54, 0x4c, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, + 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x2a, 0xf6, 0x01, 0x0a, 0x14, 0x50, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, + 0x73, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x13, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, + 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, + 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x54, + 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x12, 0x1b, 0x0a, 0x17, 0x46, 0x41, 0x49, 0x4c, + 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x5f, 0x52, 0x4f, + 0x55, 0x54, 0x45, 0x10, 0x02, 0x12, 0x18, 0x0a, 0x14, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, + 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, + 0x2c, 0x0a, 0x28, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, + 0x4e, 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, 0x04, 0x12, 0x27, 0x0a, + 0x23, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, + 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x42, 0x41, 0x4c, + 0x41, 0x4e, 0x43, 0x45, 0x10, 0x05, 0x12, 0x1b, 0x0a, 0x17, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, + 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x45, + 0x44, 0x10, 0x06, 0x2a, 0x89, 0x05, 0x0a, 0x0a, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x42, + 0x69, 0x74, 0x12, 0x18, 0x0a, 0x14, 0x44, 0x41, 0x54, 0x41, 0x4c, 0x4f, 0x53, 0x53, 0x5f, 0x50, + 0x52, 0x4f, 0x54, 0x45, 0x43, 0x54, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, + 0x44, 0x41, 0x54, 0x41, 0x4c, 0x4f, 0x53, 0x53, 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x45, 0x43, 0x54, + 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, + 0x4c, 0x5f, 0x52, 0x4f, 0x55, 0x49, 0x4e, 0x47, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x10, 0x03, 0x12, + 0x1f, 0x0a, 0x1b, 0x55, 0x50, 0x46, 0x52, 0x4f, 0x4e, 0x54, 0x5f, 0x53, 0x48, 0x55, 0x54, 0x44, + 0x4f, 0x57, 0x4e, 0x5f, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x04, + 0x12, 0x1f, 0x0a, 0x1b, 0x55, 0x50, 0x46, 0x52, 0x4f, 0x4e, 0x54, 0x5f, 0x53, 0x48, 0x55, 0x54, + 0x44, 0x4f, 0x57, 0x4e, 0x5f, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x4f, 0x50, 0x54, 0x10, + 0x05, 0x12, 0x16, 0x0a, 0x12, 0x47, 0x4f, 0x53, 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, + 0x49, 0x45, 0x53, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x06, 0x12, 0x16, 0x0a, 0x12, 0x47, 0x4f, 0x53, + 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x49, 0x45, 0x53, 0x5f, 0x4f, 0x50, 0x54, 0x10, + 0x07, 0x12, 0x11, 0x0a, 0x0d, 0x54, 0x4c, 0x56, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x52, + 0x45, 0x51, 0x10, 0x08, 0x12, 0x11, 0x0a, 0x0d, 0x54, 0x4c, 0x56, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, + 0x4e, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x09, 0x12, 0x1a, 0x0a, 0x16, 0x45, 0x58, 0x54, 0x5f, 0x47, + 0x4f, 0x53, 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x49, 0x45, 0x53, 0x5f, 0x52, 0x45, + 0x51, 0x10, 0x0a, 0x12, 0x1a, 0x0a, 0x16, 0x45, 0x58, 0x54, 0x5f, 0x47, 0x4f, 0x53, 0x53, 0x49, + 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x49, 0x45, 0x53, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x0b, 0x12, + 0x19, 0x0a, 0x15, 0x53, 0x54, 0x41, 0x54, 0x49, 0x43, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, + 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x0c, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x54, + 0x41, 0x54, 0x49, 0x43, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x4b, 0x45, 0x59, 0x5f, + 0x4f, 0x50, 0x54, 0x10, 0x0d, 0x12, 0x14, 0x0a, 0x10, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, + 0x5f, 0x41, 0x44, 0x44, 0x52, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x0e, 0x12, 0x14, 0x0a, 0x10, 0x50, + 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x44, 0x44, 0x52, 0x5f, 0x4f, 0x50, 0x54, 0x10, + 0x0f, 0x12, 0x0b, 0x0a, 0x07, 0x4d, 0x50, 0x50, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x10, 0x12, 0x0b, + 0x0a, 0x07, 0x4d, 0x50, 0x50, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x11, 0x12, 0x16, 0x0a, 0x12, 0x57, + 0x55, 0x4d, 0x42, 0x4f, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x53, 0x5f, 0x52, 0x45, + 0x51, 0x10, 0x12, 0x12, 0x16, 0x0a, 0x12, 0x57, 0x55, 0x4d, 0x42, 0x4f, 0x5f, 0x43, 0x48, 0x41, + 0x4e, 0x4e, 0x45, 0x4c, 0x53, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x13, 0x12, 0x0f, 0x0a, 0x0b, 0x41, + 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x14, 0x12, 0x0f, 0x0a, 0x0b, + 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x15, 0x12, 0x1d, 0x0a, + 0x19, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, 0x5a, 0x45, 0x52, 0x4f, 0x5f, 0x46, 0x45, + 0x45, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x16, 0x12, 0x1d, 0x0a, 0x19, + 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, 0x5a, 0x45, 0x52, 0x4f, 0x5f, 0x46, 0x45, 0x45, + 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x17, 0x12, 0x1b, 0x0a, 0x17, 0x52, + 0x4f, 0x55, 0x54, 0x45, 0x5f, 0x42, 0x4c, 0x49, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x52, 0x45, + 0x51, 0x55, 0x49, 0x52, 0x45, 0x44, 0x10, 0x18, 0x12, 0x1b, 0x0a, 0x17, 0x52, 0x4f, 0x55, 0x54, + 0x45, 0x5f, 0x42, 0x4c, 0x49, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x4f, 0x50, 0x54, 0x49, 0x4f, + 0x4e, 0x41, 0x4c, 0x10, 0x19, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x4d, 0x50, 0x5f, 0x52, 0x45, 0x51, + 0x10, 0x1e, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x4d, 0x50, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x1f, 0x2a, + 0xac, 0x01, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, + 0x65, 0x12, 0x1a, 0x0a, 0x16, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, + 0x55, 0x52, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x1a, 0x0a, + 0x16, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, + 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x55, 0x50, 0x44, + 0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, + 0x46, 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x02, 0x12, 0x1f, 0x0a, 0x1b, 0x55, 0x50, 0x44, 0x41, 0x54, + 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, + 0x41, 0x4c, 0x5f, 0x45, 0x52, 0x52, 0x10, 0x03, 0x12, 0x24, 0x0a, 0x20, 0x55, 0x50, 0x44, 0x41, + 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, + 0x49, 0x44, 0x5f, 0x50, 0x41, 0x52, 0x41, 0x4d, 0x45, 0x54, 0x45, 0x52, 0x10, 0x04, 0x32, 0xc3, + 0x27, 0x0a, 0x09, 0x4c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x4a, 0x0a, 0x0d, + 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1b, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, + 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x54, 0x72, + 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x74, + 0x61, 0x69, 0x6c, 0x73, 0x12, 0x44, 0x0a, 0x0b, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, + 0x46, 0x65, 0x65, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, + 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, + 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x09, 0x53, 0x65, + 0x6e, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x69, + 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0b, 0x4c, 0x69, + 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x4c, 0x0a, 0x15, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x30, 0x01, 0x12, 0x3b, + 0x0a, 0x08, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x79, 0x12, 0x16, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4d, + 0x61, 0x6e, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0a, 0x4e, + 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x77, 0x41, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, + 0x0a, 0x0b, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x19, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x0d, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, + 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, + 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x44, 0x0a, 0x0b, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x12, + 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, + 0x65, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, + 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, + 0x72, 0x73, 0x12, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, + 0x65, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x13, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, + 0x62, 0x65, 0x50, 0x65, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1c, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, + 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x10, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x38, + 0x0a, 0x07, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x44, + 0x65, 0x62, 0x75, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x62, 0x75, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, + 0x44, 0x65, 0x62, 0x75, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, + 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, + 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x52, + 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, + 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, + 0x0a, 0x16, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x30, 0x01, 0x12, 0x4d, 0x0a, 0x0e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0f, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x43, 0x0a, 0x0b, 0x4f, 0x70, 0x65, 0x6e, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x30, 0x01, 0x12, 0x53, 0x0a, + 0x10, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x12, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, + 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, + 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x10, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, + 0x74, 0x65, 0x53, 0x74, 0x65, 0x70, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, + 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x4d, 0x73, 0x67, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x74, 0x65, 0x70, 0x52, 0x65, 0x73, 0x70, + 0x12, 0x50, 0x0a, 0x0f, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, + 0x74, 0x6f, 0x72, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, + 0x30, 0x01, 0x12, 0x46, 0x0a, 0x0c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x30, 0x01, 0x12, 0x4d, 0x0a, 0x0e, 0x41, 0x62, + 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x1c, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x0b, 0x53, 0x65, 0x6e, + 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x28, 0x01, 0x30, 0x01, 0x12, 0x3f, 0x0a, 0x0f, 0x53, 0x65, + 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x12, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x12, 0x46, 0x0a, 0x0b, 0x53, + 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, + 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x28, + 0x01, 0x30, 0x01, 0x12, 0x46, 0x0a, 0x0f, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, + 0x74, 0x65, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, + 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x12, 0x37, 0x0a, 0x0a, 0x41, + 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, + 0x69, 0x63, 0x65, 0x73, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, + 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x0d, 0x4c, + 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x12, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, + 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, + 0x12, 0x41, 0x0a, 0x11, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x49, 0x6e, 0x76, + 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, + 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, + 0x65, 0x30, 0x01, 0x12, 0x32, 0x0a, 0x0c, 0x44, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x50, 0x61, 0x79, + 0x52, 0x65, 0x71, 0x12, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x52, + 0x65, 0x71, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x1a, 0x0d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, 0x47, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x50, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x4a, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x11, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x73, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x0d, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, + 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x47, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64, + 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, + 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x39, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x64, 0x67, 0x65, 0x12, 0x36, 0x0a, 0x0b, 0x47, 0x65, + 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, + 0x66, 0x6f, 0x12, 0x44, 0x0a, 0x0b, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, + 0x73, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, + 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4e, + 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, + 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x35, 0x0a, 0x0a, 0x53, 0x74, 0x6f, + 0x70, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x57, 0x0a, 0x15, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x47, 0x72, 0x61, 0x70, 0x68, 0x54, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x53, + 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x47, 0x72, 0x61, 0x70, 0x68, 0x54, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, + 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x30, 0x01, 0x12, 0x41, 0x0a, 0x0a, 0x44, 0x65, 0x62, + 0x75, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, + 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x09, + 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x65, 0x52, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x13, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x6c, + 0x69, 0x63, 0x79, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69, + 0x63, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x11, + 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, + 0x79, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, + 0x64, 0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, + 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x13, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x21, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, + 0x63, 0x6b, 0x75, 0x70, 0x12, 0x54, 0x0a, 0x17, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x6c, + 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, + 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, + 0x75, 0x70, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, + 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x4e, 0x0a, 0x10, 0x56, 0x65, + 0x72, 0x69, 0x66, 0x79, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x19, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, + 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, + 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x15, 0x52, 0x65, + 0x73, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, + 0x75, 0x70, 0x73, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x74, + 0x6f, 0x72, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, + 0x74, 0x6f, 0x72, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x58, 0x0a, 0x17, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x20, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, + 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, + 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, + 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x30, 0x01, 0x12, 0x47, 0x0a, 0x0c, + 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x12, 0x1a, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, + 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x10, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x1e, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, + 0x6f, 0x6e, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, + 0x6f, 0x6e, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, + 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, + 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, + 0x0a, 0x18, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4d, 0x61, 0x63, 0x50, 0x65, 0x72, 0x6d, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x68, 0x65, 0x63, 0x6b, 0x4d, 0x61, 0x63, 0x50, 0x65, 0x72, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x15, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, + 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x12, 0x1c, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, + 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, 0x56, 0x0a, 0x11, 0x53, + 0x65, 0x6e, 0x64, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x75, 0x73, + 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x75, + 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x17, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, + 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x12, 0x25, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, + 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, + 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x30, 0x01, 0x12, 0x44, 0x0a, + 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x12, 0x19, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x0d, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x49, 0x6e, 0x76, - 0x6f, 0x69, 0x63, 0x65, 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x41, 0x0a, 0x11, 0x53, 0x75, 0x62, 0x73, - 0x63, 0x72, 0x69, 0x62, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x1a, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x75, 0x62, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x30, 0x01, 0x12, 0x32, 0x0a, 0x0c, 0x44, - 0x65, 0x63, 0x6f, 0x64, 0x65, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, 0x13, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, - 0x1a, 0x0d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, - 0x47, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, - 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, - 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, - 0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, - 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6d, - 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x0d, - 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x1a, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, - 0x70, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x47, - 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, - 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, - 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x43, 0x68, - 0x61, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, - 0x68, 0x61, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x64, - 0x67, 0x65, 0x12, 0x36, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, - 0x6f, 0x12, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, - 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x44, 0x0a, 0x0b, 0x51, 0x75, - 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, - 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x3f, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x6e, - 0x66, 0x6f, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, - 0x72, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x66, - 0x6f, 0x12, 0x35, 0x0a, 0x0a, 0x53, 0x74, 0x6f, 0x70, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x12, - 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x6f, 0x70, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x15, 0x53, 0x75, 0x62, 0x73, - 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, 0x70, - 0x68, 0x12, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x72, 0x61, 0x70, 0x68, 0x54, - 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x72, 0x61, 0x70, - 0x68, 0x54, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x30, - 0x01, 0x12, 0x41, 0x0a, 0x0a, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, - 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x65, 0x76, - 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x09, 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, - 0x74, 0x12, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x11, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, - 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, - 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, - 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x13, - 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, - 0x6b, 0x75, 0x70, 0x12, 0x21, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x78, 0x70, 0x6f, - 0x72, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x54, 0x0a, 0x17, - 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x6c, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, - 0x6f, 0x74, 0x12, 0x4e, 0x0a, 0x10, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x43, 0x68, 0x61, 0x6e, - 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, - 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, - 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, - 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x56, 0x0a, 0x15, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x42, - 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x42, 0x61, 0x63, 0x6b, - 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x17, 0x53, 0x75, - 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, - 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, - 0x6f, 0x74, 0x30, 0x01, 0x12, 0x47, 0x0a, 0x0c, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, 0x63, 0x61, - 0x72, 0x6f, 0x6f, 0x6e, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x6b, - 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, 0x63, - 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, - 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, - 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, - 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x61, - 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x53, 0x0a, 0x10, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, - 0x6e, 0x49, 0x44, 0x12, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, - 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x18, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4d, - 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, - 0x4d, 0x61, 0x63, 0x50, 0x65, 0x72, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4d, 0x61, 0x63, 0x50, - 0x65, 0x72, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x15, 0x52, - 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, - 0x77, 0x61, 0x72, 0x65, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x50, 0x43, - 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x50, 0x43, 0x4d, 0x69, - 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, - 0x01, 0x30, 0x01, 0x12, 0x56, 0x0a, 0x11, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x75, 0x73, 0x74, 0x6f, - 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x17, 0x53, - 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x12, 0x25, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, - 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x30, 0x01, 0x12, 0x44, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x69, - 0x61, 0x73, 0x65, 0x73, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, - 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x69, 0x61, - 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5f, 0x0a, 0x14, 0x4c, - 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x48, 0x74, 0x6c, 0x63, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, - 0x75, 0x70, 0x48, 0x74, 0x6c, 0x63, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x48, 0x74, 0x6c, 0x63, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, - 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, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x5f, 0x0a, 0x14, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x48, 0x74, 0x6c, + 0x63, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x48, 0x74, 0x6c, 0x63, 0x52, 0x65, + 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x23, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x48, 0x74, + 0x6c, 0x63, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, 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, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -24967,6 +24996,7 @@ func file_lightning_proto_init() { (*PolicyUpdateRequest_Global)(nil), (*PolicyUpdateRequest_ChanPoint)(nil), } + file_lightning_proto_msgTypes[167].OneofWrappers = []interface{}{} file_lightning_proto_msgTypes[175].OneofWrappers = []interface{}{ (*RestoreChanBackupRequest_ChanBackups)(nil), (*RestoreChanBackupRequest_MultiChanBackup)(nil), diff --git a/lnrpc/lightning.proto b/lnrpc/lightning.proto index 5ee8e671c37..acd1db6302b 100644 --- a/lnrpc/lightning.proto +++ b/lnrpc/lightning.proto @@ -4710,6 +4710,14 @@ message ForwardingEvent { // The peer alias of the outgoing channel. string peer_alias_out = 13; + // The ID of the incoming HTLC in the payment circuit. This field is + // optional and is unset for forwarding events happened before v0.20. + optional uint64 incoming_htlc_id = 14; + + // The ID of the outgoing HTLC in the payment circuit. This field is + // optional and may be unset for legacy forwarding events. + optional uint64 outgoing_htlc_id = 15; + // TODO(roasbeef): add settlement latency? // * use FPE on the chan id? // * also list failures? diff --git a/lnrpc/lightning.swagger.json b/lnrpc/lightning.swagger.json index 518c3ee47d3..c9f698cf126 100644 --- a/lnrpc/lightning.swagger.json +++ b/lnrpc/lightning.swagger.json @@ -5067,6 +5067,16 @@ "peer_alias_out": { "type": "string", "description": "The peer alias of the outgoing channel." + }, + "incoming_htlc_id": { + "type": "string", + "format": "uint64", + "description": "The ID of the incoming HTLC in the payment circuit. This field is\noptional and is unset for forwarding events happened before v0.20." + }, + "outgoing_htlc_id": { + "type": "string", + "format": "uint64", + "description": "The ID of the outgoing HTLC in the payment circuit. This field is\noptional and may be unset for legacy forwarding events." } } }, diff --git a/lnrpc/routerrpc/router_server.go b/lnrpc/routerrpc/router_server.go index 58fa23622f3..2e65aed70aa 100644 --- a/lnrpc/routerrpc/router_server.go +++ b/lnrpc/routerrpc/router_server.go @@ -509,6 +509,10 @@ func (s *Server) probeDestination(dest []byte, amtSat int64) (*RouteFeeResponse, // node. If the route hints don't indicate an LSP, they are passed as arguments // to the SendPayment_V2 method, which enable it to send probe payments to the // payment request destination. +// +// NOTE: Be aware that because of the special heuristic that is applied to +// identify LSPs, the probe payment might use a different node id as the +// final destination (the assumed LSP node id). func (s *Server) probePaymentRequest(ctx context.Context, paymentRequest string, timeout uint32) (*RouteFeeResponse, error) { @@ -558,6 +562,9 @@ func (s *Server) probePaymentRequest(ctx context.Context, paymentRequest string, // payment won't be blocked along the route to the destination. We send // a probe payment with unmodified route hints. if !isLSP(hints, s.cfg.RouterBackend.FetchChannelEndpoints) { + log.Infof("No LSP detected, probing destination %x", + probeRequest.Dest) + probeRequest.RouteHints = invoicesrpc.CreateRPCRouteHints(hints) return s.sendProbePayment(ctx, probeRequest) } @@ -571,9 +578,14 @@ func (s *Server) probePaymentRequest(ctx context.Context, paymentRequest string, return nil, err } + // Set the destination to the LSP node ID. + lspDest := lspHint.NodeID.SerializeCompressed() + probeRequest.Dest = lspDest + + log.Infof("LSP detected, probing LSP with destination: %x", lspDest) + // The adjusted route hints serve the payment probe to find the last // public hop to the LSP on the route. - probeRequest.Dest = lspHint.NodeID.SerializeCompressed() if len(lspAdjustedRouteHints) > 0 { probeRequest.RouteHints = invoicesrpc.CreateRPCRouteHints( lspAdjustedRouteHints, @@ -609,7 +621,8 @@ func (s *Server) probePaymentRequest(ctx context.Context, paymentRequest string, // Dispatch the payment probe with adjusted fee amount. resp, err := s.sendProbePayment(ctx, probeRequest) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to send probe payment to "+ + "LSP with destination %x: %w", lspDest, err) } // If the payment probe failed we only return the failure reason and @@ -667,11 +680,11 @@ func isLSP(routeHints [][]zpay32.HopHint, return false } - idMatchesRefNode := bytes.Equal( + matchesDestNode := bytes.Equal( lastHop.NodeID.SerializeCompressed(), destHopHint.NodeID.SerializeCompressed(), ) - if !idMatchesRefNode { + if !matchesDestNode { return false } } diff --git a/lntest/harness.go b/lntest/harness.go index 4b12c8cf631..dc9ab1c9954 100644 --- a/lntest/harness.go +++ b/lntest/harness.go @@ -3,6 +3,7 @@ package lntest import ( "context" "fmt" + "runtime/debug" "strings" "testing" "time" @@ -13,7 +14,6 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/kvdb/etcd" @@ -293,10 +293,22 @@ func (h *HarnessTest) Stop() { // represented as fatal. func (h *HarnessTest) RunTestCase(testCase *TestCase) { defer func() { - if err := recover(); err != nil { - description := errors.Wrap(err, 2).ErrorStack() - h.Fatalf("Failed: (%v) panic with: \n%v", - testCase.Name, description) + if r := recover(); r != nil { + // Wrap the recovered panic in an error. + var err error + switch v := r.(type) { + case error: + err = v + default: + err = fmt.Errorf("%v", v) + } + + // Capture and print the stack trace. + stack := debug.Stack() + + // Fail the test with panic info and stack. + h.Fatalf("Failed: (%v) panic with: %v\n%s", + testCase.Name, err, stack) } }() diff --git a/lntest/harness_assertion.go b/lntest/harness_assertion.go index e8082bdba70..2540ef6b0b4 100644 --- a/lntest/harness_assertion.go +++ b/lntest/harness_assertion.go @@ -119,7 +119,7 @@ func (h *HarnessTest) ConnectNodes(a, b *node.HarnessNode) { }, } a.RPC.ConnectPeer(req) - h.AssertPeerConnected(a, b) + h.AssertConnected(a, b) } // ConnectNodesPerm creates a persistent connection between the two nodes and @@ -240,6 +240,24 @@ func (h *HarnessTest) EnsureConnected(a, b *node.HarnessNode) { h.AssertPeerConnected(b, a) } +// ConnectNodesNoAssert creates a connection from node A to node B. +func (h *HarnessTest) ConnectNodesNoAssert(a, b *node.HarnessNode) ( + *lnrpc.ConnectPeerResponse, error) { + + bobInfo := b.RPC.GetInfo() + + req := &lnrpc.ConnectPeerRequest{ + Addr: &lnrpc.LightningAddress{ + Pubkey: bobInfo.IdentityPubkey, + Host: b.Cfg.P2PAddr(), + }, + } + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + return a.RPC.LN.ConnectPeer(ctxt, req) +} + // AssertNumEdges checks that an expected number of edges can be found in the // node specified. func (h *HarnessTest) AssertNumEdges(hn *node.HarnessNode, @@ -1669,6 +1687,11 @@ func (h *HarnessTest) AssertPeerNotConnected(a, b *node.HarnessNode) { // AssertNotConnected asserts that two peers are not connected. func (h *HarnessTest) AssertNotConnected(a, b *node.HarnessNode) { + // Sleep one second before the assertion to make sure that when there's + // a RPC call to connect, that RPC call is finished before the + // assertion. + time.Sleep(1 * time.Second) + h.AssertPeerNotConnected(a, b) h.AssertPeerNotConnected(b, a) } diff --git a/lnwallet/chainfee/rates.go b/lnwallet/chainfee/rates.go index 20f4f1e1d28..735cbd20c5b 100644 --- a/lnwallet/chainfee/rates.go +++ b/lnwallet/chainfee/rates.go @@ -71,6 +71,14 @@ func (s SatPerKWeight) FeeForWeight(wu lntypes.WeightUnit) btcutil.Amount { return btcutil.Amount(s) * btcutil.Amount(wu) / 1000 } +// FeeForWeightRoundUp calculates the fee resulting from this fee rate and the +// given weight in weight units (wu), rounding up to the nearest satoshi. +func (s SatPerKWeight) FeeForWeightRoundUp( + wu lntypes.WeightUnit) btcutil.Amount { + + return (btcutil.Amount(s)*btcutil.Amount(wu) + 999) / 1000 +} + // FeeForVByte calculates the fee resulting from this fee rate and the given // size in vbytes (vb). func (s SatPerKWeight) FeeForVByte(vb lntypes.VByte) btcutil.Amount { diff --git a/lnwallet/chainfee/rates_test.go b/lnwallet/chainfee/rates_test.go index 11aa35adf6d..d3e4740f38f 100644 --- a/lnwallet/chainfee/rates_test.go +++ b/lnwallet/chainfee/rates_test.go @@ -3,6 +3,7 @@ package chainfee import ( "testing" + "github.com/lightningnetwork/lnd/lntypes" "github.com/stretchr/testify/require" ) @@ -20,3 +21,13 @@ func TestSatPerVByteConversion(t *testing.T) { // 1 sat/vb should be equal to 250 sat/kw. require.Equal(t, SatPerKWeight(250), rate.FeePerKWeight()) } + +// TestFeeForWeightRoundUp checks that the FeeForWeightRoundUp method correctly +// rounds up the fee for a given weight. +func TestFeeForWeightRoundUp(t *testing.T) { + feeRate := SatPerVByte(1).FeePerKWeight() + txWeight := lntypes.WeightUnit(674) // 674 weight units is 168.5 vb. + + require.EqualValues(t, 168, feeRate.FeeForWeight(txWeight)) + require.EqualValues(t, 169, feeRate.FeeForWeightRoundUp(txWeight)) +} diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 545c4ccb8d0..ee45bf943d7 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -2340,18 +2340,21 @@ func createHtlcRetribution(chanState *channeldb.OpenChannel, // We'll generate the original second level witness script now, as // we'll need it if we're revoking an HTLC output on the remote // commitment transaction, and *they* go to the second level. + //nolint:ll secondLevelAuxLeaf := fn.FlatMapOption( func(l CommitAuxLeaves) fn.Option[input.AuxTapLeaf] { - return fn.MapOption(func(val uint16) input.AuxTapLeaf { - idx := input.HtlcIndex(val) + return fn.MapOption( + func(val tlv.BigSizeT[uint64]) input.AuxTapLeaf { + idx := val.Int() - if htlc.Incoming.Val { - leaves := l.IncomingHtlcLeaves[idx] - return leaves.SecondLevelLeaf - } + if htlc.Incoming.Val { + leaves := l.IncomingHtlcLeaves[idx] + return leaves.SecondLevelLeaf + } - return l.OutgoingHtlcLeaves[idx].SecondLevelLeaf - })(htlc.HtlcIndex.ValOpt()) + return l.OutgoingHtlcLeaves[idx].SecondLevelLeaf + }, + )(htlc.HtlcIndex.ValOpt()) }, )(auxLeaves) secondLevelScript, err := SecondLevelHtlcScript( @@ -2365,21 +2368,24 @@ func createHtlcRetribution(chanState *channeldb.OpenChannel, // If this is an incoming HTLC, then this means that they were the // sender of the HTLC (relative to us). So we'll re-generate the sender - // HTLC script. Otherwise, is this was an outgoing HTLC that we sent, + // HTLC script. Otherwise, if this was an outgoing HTLC that we sent, // then from the PoV of the remote commitment state, they're the // receiver of this HTLC. + //nolint:ll htlcLeaf := fn.FlatMapOption( func(l CommitAuxLeaves) fn.Option[input.AuxTapLeaf] { - return fn.MapOption(func(val uint16) input.AuxTapLeaf { - idx := input.HtlcIndex(val) + return fn.MapOption( + func(val tlv.BigSizeT[uint64]) input.AuxTapLeaf { + idx := val.Int() - if htlc.Incoming.Val { - leaves := l.IncomingHtlcLeaves[idx] - return leaves.AuxTapLeaf - } + if htlc.Incoming.Val { + leaves := l.IncomingHtlcLeaves[idx] + return leaves.AuxTapLeaf + } - return l.OutgoingHtlcLeaves[idx].AuxTapLeaf - })(htlc.HtlcIndex.ValOpt()) + return l.OutgoingHtlcLeaves[idx].AuxTapLeaf + }, + )(htlc.HtlcIndex.ValOpt()) }, )(auxLeaves) scriptInfo, err := genHtlcScript( diff --git a/lnwire/lnwire.go b/lnwire/lnwire.go index fa43d25bf21..7d686658297 100644 --- a/lnwire/lnwire.go +++ b/lnwire/lnwire.go @@ -3,6 +3,7 @@ package lnwire import ( "bytes" "encoding/binary" + "errors" "fmt" "image/color" "io" @@ -13,7 +14,6 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/tor" ) diff --git a/lnwire/onion_error.go b/lnwire/onion_error.go index 7b65a85f4e9..2cbf548fa23 100644 --- a/lnwire/onion_error.go +++ b/lnwire/onion_error.go @@ -9,7 +9,6 @@ import ( "io" "github.com/davecgh/go-spew/spew" - "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/tlv" ) @@ -1513,7 +1512,7 @@ func makeEmptyOnionError(code FailCode) (FailureMessage, error) { return &FailInvalidBlinding{}, nil default: - return nil, errors.Errorf("unknown error code: %v", code) + return nil, fmt.Errorf("unknown error code: %v", code) } } diff --git a/netann/channel_update.go b/netann/channel_update.go index efc5cf61e49..eab04b7a2f9 100644 --- a/netann/channel_update.go +++ b/netann/channel_update.go @@ -13,7 +13,6 @@ import ( "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" - "github.com/pkg/errors" ) const ( @@ -271,13 +270,13 @@ func validateChannelUpdate1Fields(capacity btcutil.Amount, // The maxHTLC flag is mandatory. if !msg.MessageFlags.HasMaxHtlc() { - return errors.Errorf("max htlc flag not set for channel "+ + return fmt.Errorf("max htlc flag not set for channel "+ "update %v", spew.Sdump(msg)) } maxHtlc := msg.HtlcMaximumMsat if maxHtlc == 0 || maxHtlc < msg.HtlcMinimumMsat { - return errors.Errorf("invalid max htlc for channel "+ + return fmt.Errorf("invalid max htlc for channel "+ "update %v", spew.Sdump(msg)) } @@ -286,7 +285,7 @@ func validateChannelUpdate1Fields(capacity btcutil.Amount, // capacity. capacityMsat := lnwire.NewMSatFromSatoshis(capacity) if capacityMsat != 0 && maxHtlc > capacityMsat { - return errors.Errorf("max_htlc (%v) for channel update "+ + return fmt.Errorf("max_htlc (%v) for channel update "+ "greater than capacity (%v)", maxHtlc, capacityMsat) } diff --git a/netann/node_announcement.go b/netann/node_announcement.go index 3b5114c7d89..71250217057 100644 --- a/netann/node_announcement.go +++ b/netann/node_announcement.go @@ -2,13 +2,13 @@ package netann import ( "bytes" + "fmt" "image/color" "net" "time" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" @@ -110,7 +110,7 @@ func ValidateNodeAnn(a *lnwire.NodeAnnouncement) error { return err } - return errors.Errorf("signature on NodeAnnouncement(%x) is "+ + return fmt.Errorf("signature on NodeAnnouncement(%x) is "+ "invalid: %x", nodeKey.SerializeCompressed(), msgBuf.Bytes()) } diff --git a/peer/brontide.go b/peer/brontide.go index b599e593391..45a26e7de72 100644 --- a/peer/brontide.go +++ b/peer/brontide.go @@ -1601,6 +1601,10 @@ func (p *Brontide) WaitForDisconnect(ready chan struct{}) { // Disconnect terminates the connection with the remote peer. Additionally, a // signal is sent to the server and htlcSwitch indicating the resources // allocated to the peer can now be cleaned up. +// +// NOTE: Be aware that this method will block if the peer is still starting up. +// Therefore consider starting it in a goroutine if you cannot guarantee that +// the peer has finished starting up before calling this method. func (p *Brontide) Disconnect(reason error) { if !atomic.CompareAndSwapInt32(&p.disconnect, 0, 1) { return @@ -1613,7 +1617,8 @@ func (p *Brontide) Disconnect(reason error) { // started, otherwise we will skip reading it as this chan won't be // closed, hence blocks forever. if atomic.LoadInt32(&p.started) == 1 { - p.log.Debugf("Started, waiting on startReady signal") + p.log.Debugf("Peer hasn't finished starting up yet, waiting " + + "on startReady signal before closing connection") select { case <-p.startReady: @@ -1995,32 +2000,22 @@ func newDiscMsgStream(p *Brontide) *msgStream { // so that a parent context can be passed in here. ctx := context.TODO() + // Processing here means we send it to the gossiper which then + // decides whether this message is processed immediately or + // waits for dependent messages to be processed. It can also + // happen that the message is not processed at all if it is + // premature and the LRU cache fills up and the message is + // deleted. p.log.Debugf("Processing remote msg %T", msg) - errChan := p.cfg.AuthGossiper.ProcessRemoteAnnouncement( - ctx, msg, p, - ) - - // Start a goroutine to process the error channel for logging - // purposes. - // - // TODO(ziggie): Maybe use the error to potentially punish the - // peer depending on the error ? - go func() { - select { - case <-p.cg.Done(): - return - - case err := <-errChan: - if err != nil { - p.log.Warnf("Error processing remote "+ - "msg %T: %v", msg, - err) - } - } - - p.log.Debugf("Processed remote msg %T", msg) - }() + // TODO(ziggie): ProcessRemoteAnnouncement returns an error + // channel, but we cannot rely on it being written to. + // Because some messages might never be processed (e.g. + // premature channel updates). We should change the design here + // and use the actor model pattern as soon as it is available. + // So for now we should NOT use the error channel. + // See https://github.com/lightningnetwork/lnd/pull/9820. + p.cfg.AuthGossiper.ProcessRemoteAnnouncement(ctx, msg, p) } return newMsgStream( diff --git a/protofsm/daemon_events.go b/protofsm/daemon_events.go index bca7283d397..3b4ca9b4d3b 100644 --- a/protofsm/daemon_events.go +++ b/protofsm/daemon_events.go @@ -72,6 +72,10 @@ func (b *BroadcastTxn) daemonSealed() {} // custom state machine event. type SpendMapper[Event any] func(*chainntnfs.SpendDetail) Event +// ConfMapper is a function that's used to map a confirmation notification to a +// custom state machine event. +type ConfMapper[Event any] func(*chainntnfs.TxConfirmation) Event + // RegisterSpend is used to request that a certain event is sent into the state // machine once the specified outpoint has been spent. type RegisterSpend[Event any] struct { @@ -112,10 +116,14 @@ type RegisterConf[Event any] struct { // transaction needs to dispatch an event. NumConfs fn.Option[uint32] - // PostConfEvent is an event that's sent back to the requester once the - // transaction specified above has confirmed in the chain with - // sufficient depth. - PostConfEvent fn.Option[Event] + // FullBlock is a boolean that indicates whether we want the full block + // in the returned response. This is useful if callers want to create an + // SPV proof for the transaction post conf. + FullBlock bool + + // PostConfMapper is a special conf mapper, that if present, will be + // used to map the protofsm confirmation event to a custom event. + PostConfMapper fn.Option[ConfMapper[Event]] } // daemonSealed indicates that this struct is a DaemonEvent instance. diff --git a/protofsm/state_machine.go b/protofsm/state_machine.go index 5081480de14..aabf69baa74 100644 --- a/protofsm/state_machine.go +++ b/protofsm/state_machine.go @@ -510,10 +510,15 @@ func (s *StateMachine[Event, Env]) executeDaemonEvent(ctx context.Context, s.log.DebugS(ctx, "Registering conf", "txid", daemonEvent.Txid) + var opts []chainntnfs.NotifierOption + if daemonEvent.FullBlock { + opts = append(opts, chainntnfs.WithIncludeBlock()) + } + numConfs := daemonEvent.NumConfs.UnwrapOr(1) confEvent, err := s.cfg.Daemon.RegisterConfirmationsNtfn( &daemonEvent.Txid, daemonEvent.PkScript, - numConfs, daemonEvent.HeightHint, + numConfs, daemonEvent.HeightHint, opts..., ) if err != nil { return fmt.Errorf("unable to register conf: %w", err) @@ -522,16 +527,19 @@ func (s *StateMachine[Event, Env]) executeDaemonEvent(ctx context.Context, launched := s.gm.Go(ctx, func(ctx context.Context) { for { select { - case <-confEvent.Confirmed: - // If there's a post-conf event, then + //nolint:ll + case conf, ok := <-confEvent.Confirmed: + if !ok { + return + } + + // If there's a post-conf mapper, then // we'll send that into the current // state now. - // - // TODO(roasbeef): refactor to - // dispatchAfterRecv w/ above - postConf := daemonEvent.PostConfEvent - postConf.WhenSome(func(e Event) { - s.SendEvent(ctx, e) + postConfMapper := daemonEvent.PostConfMapper + postConfMapper.WhenSome(func(f ConfMapper[Event]) { + customEvent := f(conf) + s.SendEvent(ctx, customEvent) }) return diff --git a/protofsm/state_machine_test.go b/protofsm/state_machine_test.go index dc356236542..844256fb4e8 100644 --- a/protofsm/state_machine_test.go +++ b/protofsm/state_machine_test.go @@ -40,6 +40,35 @@ type daemonEvents struct { func (s *daemonEvents) dummy() { } +type confDetailsEvent struct { + blockHash chainhash.Hash + blockHeight uint32 +} + +func (c *confDetailsEvent) dummy() { +} + +type registerConf struct { + fullBlock bool +} + +func (r *registerConf) dummy() { +} + +type spendDetailsEvent struct { + spenderTxHash chainhash.Hash + spendingHeight int32 +} + +func (s *spendDetailsEvent) dummy() { +} + +type registerSpend struct { +} + +func (r *registerSpend) dummy() { +} + type dummyEnv struct { mock.Mock } @@ -74,7 +103,7 @@ var ( func (d *dummyStateStart) ProcessEvent(event dummyEvents, env *dummyEnv, ) (*StateTransition[dummyEvents, *dummyEnv], error) { - switch event.(type) { + switch newEvent := event.(type) { case *goToFin: return &StateTransition[dummyEvents, *dummyEnv]{ NextState: &dummyStateFin{}, @@ -127,6 +156,101 @@ func (d *dummyStateStart) ProcessEvent(event dummyEvents, env *dummyEnv, }, }), }, nil + + // This state will emit a RegisterConf event which uses a mapper to + // transition to the final state upon confirmation. + case *registerConf: + confMapper := func( + conf *chainntnfs.TxConfirmation) dummyEvents { + + // Map the conf details into our custom event. + return &confDetailsEvent{ + blockHash: *conf.BlockHash, + blockHeight: conf.BlockHeight, + } + } + + regConfEvent := &RegisterConf[dummyEvents]{ + Txid: chainhash.Hash{1}, + PkScript: []byte{0x01}, + HeightHint: 100, + FullBlock: newEvent.fullBlock, + PostConfMapper: fn.Some[ConfMapper[dummyEvents]]( + confMapper, + ), + } + + return &StateTransition[dummyEvents, *dummyEnv]{ + // Stay in the start state until the conf event is + // received and mapped. + NextState: &dummyStateStart{ + canSend: d.canSend, + }, + NewEvents: fn.Some(EmittedEvent[dummyEvents]{ + ExternalEvents: DaemonEventSet{ + regConfEvent, + }, + }), + }, nil + + // This event contains details from the confirmation and signals us to + // transition to the final state. + case *confDetailsEvent: + // We received the mapped confirmation details, transition to + // the confirmed state. + return &StateTransition[dummyEvents, *dummyEnv]{ + NextState: &dummyStateConfirmed{ + blockHash: newEvent.blockHash, + blockHeight: newEvent.blockHeight, + }, + }, nil + + // This state will emit a RegisterSpend event which uses a mapper to + // transition to the spent state upon spend detection. + case *registerSpend: + spendMapper := func( + spend *chainntnfs.SpendDetail) dummyEvents { + + // Map the spend details into our custom event. + return &spendDetailsEvent{ + spenderTxHash: *spend.SpenderTxHash, + spendingHeight: spend.SpendingHeight, + } + } + + regSpendEvent := &RegisterSpend[dummyEvents]{ + OutPoint: wire.OutPoint{Hash: chainhash.Hash{3}}, + PkScript: []byte{0x03}, + HeightHint: 300, + PostSpendEvent: fn.Some[SpendMapper[dummyEvents]]( + spendMapper, + ), + } + + return &StateTransition[dummyEvents, *dummyEnv]{ + // Stay in the start state until the spend event is + // received and mapped. + NextState: &dummyStateStart{ + canSend: d.canSend, + }, + NewEvents: fn.Some(EmittedEvent[dummyEvents]{ + ExternalEvents: DaemonEventSet{ + regSpendEvent, + }, + }), + }, nil + + // This event contains details from the spend notification and signals + // us to transition to the spent state. + case *spendDetailsEvent: + // We received the mapped spend details, transition to the + // spent state. + return &StateTransition[dummyEvents, *dummyEnv]{ + NextState: &dummyStateSpent{ + spenderTxHash: newEvent.spenderTxHash, + spendingHeight: newEvent.spendingHeight, + }, + }, nil } return nil, fmt.Errorf("unknown event: %T", event) @@ -155,12 +279,64 @@ func (d *dummyStateFin) IsTerminal() bool { return true } -func assertState[Event any, Env Environment](t *testing.T, - m *StateMachine[Event, Env], expectedState State[Event, Env]) { +type dummyStateConfirmed struct { + blockHash chainhash.Hash + blockHeight uint32 +} + +func (d *dummyStateConfirmed) String() string { + return "dummyStateConfirmed" +} + +func (d *dummyStateConfirmed) ProcessEvent(event dummyEvents, env *dummyEnv, +) (*StateTransition[dummyEvents, *dummyEnv], error) { + + // This is a terminal state, no further transitions. + return &StateTransition[dummyEvents, *dummyEnv]{ + NextState: d, + }, nil +} + +func (d *dummyStateConfirmed) IsTerminal() bool { + return true +} + +type dummyStateSpent struct { + spenderTxHash chainhash.Hash + spendingHeight int32 +} + +func (d *dummyStateSpent) String() string { + return "dummyStateSpent" +} + +func (d *dummyStateSpent) ProcessEvent(event dummyEvents, env *dummyEnv, +) (*StateTransition[dummyEvents, *dummyEnv], error) { + + // This is a terminal state, no further transitions. + return &StateTransition[dummyEvents, *dummyEnv]{ + NextState: d, + }, nil +} + +func (d *dummyStateSpent) IsTerminal() bool { + return true +} + +// assertState asserts that the state machine is currently in the expected +// state type and returns the state cast to that type. +func assertState[Event any, Env Environment, S State[Event, Env]](t *testing.T, + m *StateMachine[Event, Env], expectedState S) S { state, err := m.CurrentState() require.NoError(t, err) require.IsType(t, expectedState, state) + + // Perform the type assertion to return the concrete type. + concreteState, ok := state.(S) + require.True(t, ok, "state type assertion failed") + + return concreteState } func assertStateTransitions[Event any, Env Environment]( @@ -209,7 +385,8 @@ func (d *dummyAdapters) RegisterConfirmationsNtfn(txid *chainhash.Hash, opts ...chainntnfs.NotifierOption, ) (*chainntnfs.ConfirmationEvent, error) { - args := d.Called(txid, pkScript, numConfs) + // Pass opts as the last argument to the mock call checker. + args := d.Called(txid, pkScript, numConfs, heightHint, opts) err := args.Error(0) @@ -415,6 +592,208 @@ func TestStateMachineDaemonEvents(t *testing.T) { env.AssertExpectations(t) } +// testStateMachineConfMapperImpl is a helper function that encapsulates the +// core logic for testing the confirmation mapping functionality of the state +// machine. It takes a boolean flag `fullBlock` to determine whether to test the +// scenario where full block details are requested in the confirmation +// notification. +func testStateMachineConfMapperImpl(t *testing.T, fullBlock bool) { + ctx := context.Background() + + // Create the state machine. + env := &dummyEnv{} + startingState := &dummyStateStart{} + adapters := newDaemonAdapters() + + cfg := StateMachineCfg[dummyEvents, *dummyEnv]{ + Daemon: adapters, + InitialState: startingState, + Env: env, + } + stateMachine := NewStateMachine(cfg) + + stateSub := stateMachine.RegisterStateEvents() + defer stateMachine.RemoveStateSub(stateSub) + + stateMachine.Start(ctx) + defer stateMachine.Stop() + + // Define the expected arguments for the mock call. + expectedTxid := &chainhash.Hash{1} + expectedPkScript := []byte{0x01} + expectedNumConfs := uint32(1) + expectedHeightHint := uint32(100) + + // Set up the mock expectation based on the FullBlock flag. We use + // mock.MatchedBy to assert the options passed. + if fullBlock { + // Expect WithIncludeBlock() option when FullBlock is true. + adapters.On( + "RegisterConfirmationsNtfn", + expectedTxid, expectedPkScript, + expectedNumConfs, expectedHeightHint, + mock.MatchedBy( + func(opts []chainntnfs.NotifierOption) bool { + // Check if exactly one option is passed + // and it's the correct type. Unless we + // use reflect, we can introspect into + // the private fields. + return len(opts) == 1 + }, + ), + ).Return(nil) + } else { + // Expect no options when FullBlock is false. + adapters.On( + "RegisterConfirmationsNtfn", + expectedTxid, expectedPkScript, + expectedNumConfs, expectedHeightHint, + mock.MatchedBy(func(opts []chainntnfs.NotifierOption) bool { //nolint:ll + return len(opts) == 0 + }), + ).Return(nil) + } + + // Create the registerConf event with the specified FullBlock value. + regConfEvent := ®isterConf{ + fullBlock: fullBlock, + } + + // Send the event that triggers RegisterConf emission. + stateMachine.SendEvent(ctx, regConfEvent) + + // We should transition back to the starting state initially. + expectedStates := []State[dummyEvents, *dummyEnv]{ + &dummyStateStart{}, &dummyStateStart{}, + } + assertStateTransitions(t, stateSub, expectedStates) + + // Assert the registration call was made with the correct arguments + // (including options). + adapters.AssertExpectations(t) + + // Now, simulate the confirmation event coming back from the notifier. + simulatedConf := &chainntnfs.TxConfirmation{ + BlockHash: &chainhash.Hash{2}, + BlockHeight: 123, + } + adapters.confChan <- simulatedConf + + // This should trigger the mapper and send the confDetailsEvent, + // transitioning us to the confirmed state. + expectedStates = []State[dummyEvents, *dummyEnv]{&dummyStateConfirmed{}} + assertStateTransitions(t, stateSub, expectedStates) + + // Final state assertion. + finalState := assertState(t, &stateMachine, &dummyStateConfirmed{}) + + // Assert that the details from the confirmation event were correctly + // propagated to the final state. + require.Equal(t, + *simulatedConf.BlockHash, finalState.blockHash, + ) + require.Equal(t, + simulatedConf.BlockHeight, finalState.blockHeight, + ) + + adapters.AssertExpectations(t) + env.AssertExpectations(t) +} + +// TestStateMachineConfMapper tests the confirmation mapping functionality using +// subtests driven by the testStateMachineConfMapperImpl helper function. It +// covers scenarios both with and without requesting the full block details. +func TestStateMachineConfMapper(t *testing.T) { + t.Parallel() + + t.Run("full block false", func(t *testing.T) { + t.Parallel() + testStateMachineConfMapperImpl(t, false) + }) + + t.Run("full block true", func(t *testing.T) { + t.Parallel() + testStateMachineConfMapperImpl(t, true) + }) +} + +// TestStateMachineSpendMapper tests that the state machine is able to properly +// map the spend event into a custom event that can be used to trigger a state +// transition. +func TestStateMachineSpendMapper(t *testing.T) { + t.Parallel() + ctx := context.Background() + + // Create the state machine. + env := &dummyEnv{} + startingState := &dummyStateStart{} + adapters := newDaemonAdapters() + + cfg := StateMachineCfg[dummyEvents, *dummyEnv]{ + Daemon: adapters, + InitialState: startingState, + Env: env, + } + stateMachine := NewStateMachine(cfg) + + stateSub := stateMachine.RegisterStateEvents() + defer stateMachine.RemoveStateSub(stateSub) + + stateMachine.Start(ctx) + defer stateMachine.Stop() + + // Expect the RegisterSpendNtfn call when we send the event. + targetOutpoint := &wire.OutPoint{Hash: chainhash.Hash{3}} + targetPkScript := []byte{0x03} + targetHeightHint := uint32(300) + adapters.On( + "RegisterSpendNtfn", targetOutpoint, targetPkScript, + targetHeightHint, + ).Return(nil) + + // Send the event that triggers RegisterSpend emission. + stateMachine.SendEvent(ctx, ®isterSpend{}) + + // We should transition back to the starting state initially. + expectedStates := []State[dummyEvents, *dummyEnv]{ + &dummyStateStart{}, &dummyStateStart{}, + } + assertStateTransitions(t, stateSub, expectedStates) + + // Assert the registration call was made. + adapters.AssertExpectations(t) + + // Now, simulate the spend event coming back from the notifier. Populate + // it with some data to be mapped. + simulatedSpend := &chainntnfs.SpendDetail{ + SpentOutPoint: targetOutpoint, + SpenderTxHash: &chainhash.Hash{4}, + SpendingTx: &wire.MsgTx{}, + SpendingHeight: 456, + } + adapters.spendChan <- simulatedSpend + + // This should trigger the mapper and send the spendDetailsEvent, + // transitioning us to the spent state. + expectedStates = []State[dummyEvents, *dummyEnv]{&dummyStateSpent{}} + assertStateTransitions(t, stateSub, expectedStates) + + // Final state assertion. + finalState := assertState(t, &stateMachine, &dummyStateSpent{}) + + // Assert that the details from the spend event were correctly + // propagated to the final state. + require.Equal(t, + *simulatedSpend.SpenderTxHash, finalState.spenderTxHash, + ) + require.Equal(t, + simulatedSpend.SpendingHeight, finalState.spendingHeight, + ) + + adapters.AssertExpectations(t) + env.AssertExpectations(t) +} + type dummyMsgMapper struct { mock.Mock } diff --git a/routing/bandwidth_test.go b/routing/bandwidth_test.go index 7e0bca81c11..b7f6e3f13b2 100644 --- a/routing/bandwidth_test.go +++ b/routing/bandwidth_test.go @@ -1,14 +1,15 @@ package routing import ( + "errors" "testing" "github.com/btcsuite/btcd/btcutil" - "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/tlv" "github.com/stretchr/testify/require" ) @@ -152,7 +153,7 @@ func (*mockTrafficShaper) ShouldHandleTraffic(_ lnwire.ShortChannelID, // ShouldHandleTraffic method should be called first. func (*mockTrafficShaper) PaymentBandwidth(_, _, _ fn.Option[tlv.Blob], linkBandwidth, _ lnwire.MilliSatoshi, - _ lnwallet.AuxHtlcView) (lnwire.MilliSatoshi, error) { + _ lnwallet.AuxHtlcView, _ route.Vertex) (lnwire.MilliSatoshi, error) { return linkBandwidth, nil } @@ -161,8 +162,8 @@ func (*mockTrafficShaper) PaymentBandwidth(_, _, _ fn.Option[tlv.Blob], // data blob of an HTLC, may produce a different blob or modify the // amount of bitcoin this htlc should carry. func (*mockTrafficShaper) ProduceHtlcExtraData(totalAmount lnwire.MilliSatoshi, - _ lnwire.CustomRecords) (lnwire.MilliSatoshi, lnwire.CustomRecords, - error) { + _ lnwire.CustomRecords, _ route.Vertex) (lnwire.MilliSatoshi, + lnwire.CustomRecords, error) { return totalAmount, nil, nil } diff --git a/routing/mock_test.go b/routing/mock_test.go index a1268d0c3de..d3601636c66 100644 --- a/routing/mock_test.go +++ b/routing/mock_test.go @@ -1,12 +1,12 @@ package routing import ( + "errors" "fmt" "sync" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" - "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/graph/db/models" diff --git a/routing/payment_lifecycle.go b/routing/payment_lifecycle.go index a64c8c87e6a..7a6443ab66a 100644 --- a/routing/payment_lifecycle.go +++ b/routing/payment_lifecycle.go @@ -724,6 +724,13 @@ func (p *paymentLifecycle) amendFirstHopData(rt *route.Route) error { // value. rt.FirstHopWireCustomRecords = p.firstHopCustomRecords + if len(rt.Hops) == 0 { + return fmt.Errorf("cannot amend first hop data, route length " + + "is zero") + } + + firstHopPK := rt.Hops[0].PubKeyBytes + // extraDataRequest is a helper struct to pass the custom records and // amount back from the traffic shaper. type extraDataRequest struct { @@ -740,6 +747,7 @@ func (p *paymentLifecycle) amendFirstHopData(rt *route.Route) error { func(ts htlcswitch.AuxTrafficShaper) fn.Result[extraDataRequest] { newAmt, newRecords, err := ts.ProduceHtlcExtraData( rt.TotalAmount, p.firstHopCustomRecords, + firstHopPK, ) if err != nil { return fn.Err[extraDataRequest](err) diff --git a/routing/payment_lifecycle_test.go b/routing/payment_lifecycle_test.go index 9df02ec3284..0ee751196f9 100644 --- a/routing/payment_lifecycle_test.go +++ b/routing/payment_lifecycle_test.go @@ -2,12 +2,12 @@ package routing import ( "context" + "errors" "sync/atomic" "testing" "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/htlcswitch" @@ -368,7 +368,11 @@ func TestRequestRouteSucceed(t *testing.T) { // Create a mock payment session and a dummy route. paySession := &mockPaymentSession{} - dummyRoute := &route.Route{} + dummyRoute := &route.Route{ + Hops: []*route.Hop{ + testHop, + }, + } // Mount the mocked payment session. p.paySession = paySession diff --git a/routing/probability_bimodal.go b/routing/probability_bimodal.go index 35d1f1073a9..748a8d1c177 100644 --- a/routing/probability_bimodal.go +++ b/routing/probability_bimodal.go @@ -1,12 +1,12 @@ package routing import ( + "errors" "fmt" "math" "time" "github.com/btcsuite/btcd/btcutil" - "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" ) diff --git a/routing/router.go b/routing/router.go index 440373200cd..1b2a94d07cb 100644 --- a/routing/router.go +++ b/routing/router.go @@ -2,6 +2,7 @@ package routing import ( "context" + "errors" "fmt" "math" "math/big" @@ -13,7 +14,6 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/davecgh/go-spew/spew" - "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/amp" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/clock" diff --git a/routing/router_test.go b/routing/router_test.go index 62a68b21302..21cb84fd326 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -19,7 +19,6 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/davecgh/go-spew/spew" - "github.com/go-errors/errors" sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/clock" @@ -188,7 +187,7 @@ func createTestNode() (*models.LightningNode, error) { priv, err := btcec.NewPrivateKey() if err != nil { - return nil, errors.Errorf("unable create private key: %v", err) + return nil, fmt.Errorf("unable create private key: %w", err) } pub := priv.PubKey().SerializeCompressed() diff --git a/rpcserver.go b/rpcserver.go index b009b2d6b46..297a50da383 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -1869,11 +1869,13 @@ func (r *rpcServer) DisconnectPeer(ctx context.Context, // In order to avoid erroneously disconnecting from a peer that we have // an active channel with, if we have any channels active with this - // peer, then we'll disallow disconnecting from them. + // peer, then we'll disallow disconnecting from them in certain + // situations. if len(nodeChannels) != 0 { - // If we are not in a dev environment or the configed dev value - // `unsafedisconnect` is false, we return an error since there - // are active channels. + // If the configured dev value `unsafedisconnect` is false, we + // return an error since there are active channels. For + // production environments, we allow disconnecting from a peer + // even if there are channels active with them. if !r.cfg.Dev.GetUnsafeDisconnect() { return nil, fmt.Errorf("cannot disconnect from "+ "peer(%x), still has %d active channels", @@ -8061,6 +8063,16 @@ func (r *rpcServer) ForwardingHistory(ctx context.Context, AmtOutMsat: uint64(amtOutMsat), } + // If the incoming htlc id is present, add it to the response. + event.IncomingHtlcID.WhenSome(func(id uint64) { + resp.ForwardingEvents[i].IncomingHtlcId = &id + }) + + // If the outgoing htlc id is present, add it to the response. + event.OutgoingHtlcID.WhenSome(func(id uint64) { + resp.ForwardingEvents[i].OutgoingHtlcId = &id + }) + if req.PeerAliasLookup { aliasIn, err := getRemoteAlias(event.IncomingChanID) if err != nil { diff --git a/sample-lnd.conf b/sample-lnd.conf index 40d6bacd298..39a0bbc49ad 100644 --- a/sample-lnd.conf +++ b/sample-lnd.conf @@ -566,7 +566,8 @@ ; the headers of an HTTP request. ; http-header-timeout=5s -; The number of restricted slots the server will allocate for peers. +; The max number of incoming connections allowed in the server. Outbound +; connections are not restricted. ; num-restricted-slots=100 ; If true, a peer will *not* be disconnected if a pong is not received in time @@ -1475,6 +1476,11 @@ ; channels prior to lnd@v0.15.0. ; db.prune-revocation=false +; Specify whether the optional migration for garbage collecting the decayed +; sphinx logs should be applied. By default, the decayed log will be garbage +; collected. +; db.no-gc-decayed-log=false + ; If set to true, then the to-local and to-remote output amount data of revoked ; commitment transactions will not be stored in the revocation log. Note that ; this flag can only be set if --wtclient.active is not set. It is not @@ -1483,13 +1489,17 @@ ; db.no-rev-log-amt-data=false ; If set to true, native SQL will be used instead of KV emulation for tables -; that support it already. Note: this is an experimental feature, use at your -; own risk. +; that support it already (currently this is the invoices DB). ; db.use-native-sql=false ; If set to true, the KV to native SQL migration will be skipped. Note that ; this option is intended for users who experience non-resolvable migration -; errors. +; errors. Enabling after there is a non-resolvable migration error that resulted +; in an incomplete migration will cause that partial migration to be abandoned +; and ignored and an empty database will be used instead. Since invoices are +; currently the only native SQL database used, our channels will still work but +; the invoice history will be forgotten. This option has no effect if native SQL +; is not in use (db.use-native-sql=false). ; db.skip-native-sql-migration=false [etcd] diff --git a/server.go b/server.go index 81cffee3e3d..8730d2710ab 100644 --- a/server.go +++ b/server.go @@ -5,6 +5,7 @@ import ( "context" "crypto/rand" "encoding/hex" + "errors" "fmt" "math/big" prand "math/rand" @@ -23,7 +24,7 @@ import ( "github.com/btcsuite/btcd/connmgr" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/go-errors/errors" + "github.com/btcsuite/btclog/v2" sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/aliasmgr" "github.com/lightningnetwork/lnd/autopilot" @@ -56,6 +57,7 @@ import ( "github.com/lightningnetwork/lnd/lnpeer" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" + "github.com/lightningnetwork/lnd/lnutils" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwallet/chanfunding" @@ -193,6 +195,23 @@ const ( peerStatusProtected ) +// String returns a human-readable representation of the status code. +func (p peerAccessStatus) String() string { + switch p { + case peerStatusRestricted: + return "restricted" + + case peerStatusTemporary: + return "temporary" + + case peerStatusProtected: + return "protected" + + default: + return "unknown" + } +} + // peerSlotStatus determines whether a peer gets access to one of our free // slots or gets to bypass this safety mechanism. type peerSlotStatus struct { @@ -933,9 +952,35 @@ func newServer(_ context.Context, cfg *Config, listenAddrs []net.Addr, if err != nil { return nil, err } + + // TODO(elle): All previously persisted node announcement fields (ie, + // not just LastUpdate) should be consulted here to ensure that we + // aren't overwriting any fields that may have been set during the + // last run of lnd. + nodeLastUpdate := time.Now() + srcNode, err := dbs.GraphDB.SourceNode() + switch { + // If we have a source node persisted in the DB already, then we just + // need to make sure that the new LastUpdate time is at least one + // second after the last update time. + case err == nil: + if srcNode.LastUpdate.Second() >= nodeLastUpdate.Second() { + nodeLastUpdate = srcNode.LastUpdate.Add(time.Second) + } + + // If we don't have a source node persisted in the DB, then we'll + // create a new one with the current time as the LastUpdate. + case errors.Is(err, graphdb.ErrSourceNodeNotSet): + + // If the above cases are not matched, then we have an unhandled non + // nil error. + default: + return nil, fmt.Errorf("unable to fetch source node: %w", err) + } + selfNode := &models.LightningNode{ HaveNodeAnnouncement: true, - LastUpdate: time.Now(), + LastUpdate: nodeLastUpdate, Addresses: selfAddrs, Alias: nodeAlias.String(), Features: s.featureMgr.Get(feature.SetNodeAnn), @@ -1889,7 +1934,9 @@ func newServer(_ context.Context, cfg *Config, listenAddrs []net.Addr, // connection requests when we call NewListener. listeners[i], err = brontide.NewListener( nodeKeyECDH, listenAddr.String(), - s.peerAccessMan.checkIncomingConnBanScore, + // TODO(yy): remove this check and unify the inbound + // connection check inside `InboundPeerConnected`. + s.peerAccessMan.checkAcceptIncomingConn, ) if err != nil { return nil, err @@ -3992,22 +4039,6 @@ func (s *server) InboundPeerConnected(conn net.Conn) { s.mu.Lock() defer s.mu.Unlock() - // If the remote node's public key is banned, drop the connection. - access, err := s.peerAccessMan.assignPeerPerms(nodePub) - if err != nil { - // Clean up the persistent peer maps if we're dropping this - // connection. - s.bannedPersistentPeerConnection(pubStr) - - srvrLog.Debugf("Dropping connection for %x since we are out "+ - "of restricted-access connection slots: %v.", pubSer, - err) - - conn.Close() - - return - } - // If we already have an outbound connection to this peer, then ignore // this new connection. if p, ok := s.outboundPeers[pubStr]; ok { @@ -4042,9 +4073,14 @@ func (s *server) InboundPeerConnected(conn net.Conn) { // We were unable to locate an existing connection with the // target peer, proceed to connect. s.cancelConnReqs(pubStr, nil) - s.peerConnected(conn, nil, true, access) + s.peerConnected(conn, nil, true) case nil: + ctx := btclog.WithCtx( + context.TODO(), + lnutils.LogPubKey("peer", connectedPeer.IdentityKey()), + ) + // We already have a connection with the incoming peer. If the // connection we've already established should be kept and is // not of the same type of the new connection (inbound), then @@ -4054,27 +4090,27 @@ func (s *server) InboundPeerConnected(conn net.Conn) { if !connectedPeer.Inbound() && !shouldDropLocalConnection(localPub, nodePub) { - srvrLog.Warnf("Received inbound connection from "+ - "peer %v, but already have outbound "+ - "connection, dropping conn", connectedPeer) + srvrLog.WarnS(ctx, "Received inbound connection from "+ + "peer, but already have outbound "+ + "connection, dropping conn", + fmt.Errorf("already have outbound conn")) conn.Close() return } // Otherwise, if we should drop the connection, then we'll // disconnect our already connected peer. - srvrLog.Debugf("Disconnecting stale connection to %v", - connectedPeer) + srvrLog.DebugS(ctx, "Disconnecting stale connection") s.cancelConnReqs(pubStr, nil) // Remove the current peer from the server's internal state and // signal that the peer termination watcher does not need to // execute for this peer. - s.removePeer(connectedPeer) + s.removePeerUnsafe(ctx, connectedPeer) s.ignorePeerTermination[connectedPeer] = struct{}{} s.scheduledPeerConnection[pubStr] = func() { - s.peerConnected(conn, nil, true, access) + s.peerConnected(conn, nil, true) } } } @@ -4099,25 +4135,6 @@ func (s *server) OutboundPeerConnected(connReq *connmgr.ConnReq, conn net.Conn) s.mu.Lock() defer s.mu.Unlock() - access, err := s.peerAccessMan.assignPeerPerms(nodePub) - if err != nil { - // Clean up the persistent peer maps if we're dropping this - // connection. - s.bannedPersistentPeerConnection(pubStr) - - srvrLog.Debugf("Dropping connection for %x since we are out "+ - "of restricted-access connection slots: %v.", pubSer, - err) - - if connReq != nil { - s.connMgr.Remove(connReq.ID()) - } - - conn.Close() - - return - } - // If we already have an inbound connection to this peer, then ignore // this new connection. if p, ok := s.inboundPeers[pubStr]; ok { @@ -4152,7 +4169,7 @@ func (s *server) OutboundPeerConnected(connReq *connmgr.ConnReq, conn net.Conn) return } - srvrLog.Infof("Established connection to: %x@%v", pubStr, + srvrLog.Infof("Established outbound connection to: %x@%v", pubStr, conn.RemoteAddr()) if connReq != nil { @@ -4176,9 +4193,14 @@ func (s *server) OutboundPeerConnected(connReq *connmgr.ConnReq, conn net.Conn) case ErrPeerNotConnected: // We were unable to locate an existing connection with the // target peer, proceed to connect. - s.peerConnected(conn, connReq, false, access) + s.peerConnected(conn, connReq, false) case nil: + ctx := btclog.WithCtx( + context.TODO(), + lnutils.LogPubKey("peer", connectedPeer.IdentityKey()), + ) + // We already have a connection with the incoming peer. If the // connection we've already established should be kept and is // not of the same type of the new connection (outbound), then @@ -4188,9 +4210,10 @@ func (s *server) OutboundPeerConnected(connReq *connmgr.ConnReq, conn net.Conn) if connectedPeer.Inbound() && shouldDropLocalConnection(localPub, nodePub) { - srvrLog.Warnf("Established outbound connection to "+ - "peer %v, but already have inbound "+ - "connection, dropping conn", connectedPeer) + srvrLog.WarnS(ctx, "Established outbound connection "+ + "to peer, but already have inbound "+ + "connection, dropping conn", + fmt.Errorf("already have inbound conn")) if connReq != nil { s.connMgr.Remove(connReq.ID()) } @@ -4201,16 +4224,15 @@ func (s *server) OutboundPeerConnected(connReq *connmgr.ConnReq, conn net.Conn) // Otherwise, _their_ connection should be dropped. So we'll // disconnect the peer and send the now obsolete peer to the // server for garbage collection. - srvrLog.Debugf("Disconnecting stale connection to %v", - connectedPeer) + srvrLog.DebugS(ctx, "Disconnecting stale connection") // Remove the current peer from the server's internal state and // signal that the peer termination watcher does not need to // execute for this peer. - s.removePeer(connectedPeer) + s.removePeerUnsafe(ctx, connectedPeer) s.ignorePeerTermination[connectedPeer] = struct{}{} s.scheduledPeerConnection[pubStr] = func() { - s.peerConnected(conn, connReq, false, access) + s.peerConnected(conn, connReq, false) } } } @@ -4287,43 +4309,48 @@ func (s *server) SubscribeCustomMessages() (*subscribe.Client, error) { // notifyOpenChannelPeerEvent updates the access manager's maps and then calls // the channelNotifier's NotifyOpenChannelEvent. func (s *server) notifyOpenChannelPeerEvent(op wire.OutPoint, - remotePub *btcec.PublicKey) error { + remotePub *btcec.PublicKey) { // Call newOpenChan to update the access manager's maps for this peer. if err := s.peerAccessMan.newOpenChan(remotePub); err != nil { - return err + srvrLog.Errorf("Failed to update peer[%x] access status after "+ + "channel[%v] open", remotePub.SerializeCompressed(), op) } // Notify subscribers about this open channel event. s.channelNotifier.NotifyOpenChannelEvent(op) - - return nil } // notifyPendingOpenChannelPeerEvent updates the access manager's maps and then // calls the channelNotifier's NotifyPendingOpenChannelEvent. func (s *server) notifyPendingOpenChannelPeerEvent(op wire.OutPoint, - pendingChan *channeldb.OpenChannel, remotePub *btcec.PublicKey) error { + pendingChan *channeldb.OpenChannel, remotePub *btcec.PublicKey) { // Call newPendingOpenChan to update the access manager's maps for this // peer. if err := s.peerAccessMan.newPendingOpenChan(remotePub); err != nil { - return err + srvrLog.Errorf("Failed to update peer[%x] access status after "+ + "channel[%v] pending open", + remotePub.SerializeCompressed(), op) } // Notify subscribers about this event. s.channelNotifier.NotifyPendingOpenChannelEvent(op, pendingChan) - - return nil } // notifyFundingTimeoutPeerEvent updates the access manager's maps and then // calls the channelNotifier's NotifyFundingTimeout. func (s *server) notifyFundingTimeoutPeerEvent(op wire.OutPoint, - remotePub *btcec.PublicKey) error { + remotePub *btcec.PublicKey) { // Call newPendingCloseChan to potentially demote the peer. err := s.peerAccessMan.newPendingCloseChan(remotePub) + if err != nil { + srvrLog.Errorf("Failed to update peer[%x] access status after "+ + "channel[%v] pending close", + remotePub.SerializeCompressed(), op) + } + if errors.Is(err, ErrNoMoreRestrictedAccessSlots) { // If we encounter an error while attempting to disconnect the // peer, log the error. @@ -4334,8 +4361,6 @@ func (s *server) notifyFundingTimeoutPeerEvent(op wire.OutPoint, // Notify subscribers about this event. s.channelNotifier.NotifyFundingTimeout(op) - - return nil } // peerConnected is a function that handles initialization a newly connected @@ -4343,12 +4368,35 @@ func (s *server) notifyFundingTimeoutPeerEvent(op wire.OutPoint, // starting all the goroutines the peer needs to function properly. The inbound // boolean should be true if the peer initiated the connection to us. func (s *server) peerConnected(conn net.Conn, connReq *connmgr.ConnReq, - inbound bool, access peerAccessStatus) { + inbound bool) { brontideConn := conn.(*brontide.Conn) addr := conn.RemoteAddr() pubKey := brontideConn.RemotePub() + // Only restrict access for inbound connections, which means if the + // remote node's public key is banned or the restricted slots are used + // up, we will drop the connection. + // + // TODO(yy): Consider perform this check in + // `peerAccessMan.addPeerAccess`. + access, err := s.peerAccessMan.assignPeerPerms(pubKey) + if inbound && err != nil { + pubSer := pubKey.SerializeCompressed() + + // Clean up the persistent peer maps if we're dropping this + // connection. + s.bannedPersistentPeerConnection(string(pubSer)) + + srvrLog.Debugf("Dropping connection for %x since we are out "+ + "of restricted-access connection slots: %v.", pubSer, + err) + + conn.Close() + + return + } + srvrLog.Infof("Finalizing connection to %x@%s, inbound=%v", pubKey.SerializeCompressed(), addr, inbound) @@ -4486,7 +4534,7 @@ func (s *server) peerConnected(conn net.Conn, connReq *connmgr.ConnReq, p := peer.NewBrontide(pCfg) // Update the access manager with the access permission for this peer. - s.peerAccessMan.addPeerAccess(pubKey, access) + s.peerAccessMan.addPeerAccess(pubKey, access, inbound) // TODO(roasbeef): update IP address for link-node // * also mark last-seen, do it one single transaction? @@ -4629,14 +4677,18 @@ func (s *server) peerInitializer(p *peer.Brontide) { func (s *server) peerTerminationWatcher(p *peer.Brontide, ready chan struct{}) { defer s.wg.Done() + ctx := btclog.WithCtx( + context.TODO(), lnutils.LogPubKey("peer", p.IdentityKey()), + ) + p.WaitForDisconnect(ready) - srvrLog.Debugf("Peer %v has been disconnected", p) + srvrLog.DebugS(ctx, "Peer has been disconnected") // If the server is exiting then we can bail out early ourselves as all // the other sub-systems will already be shutting down. if s.Stopped() { - srvrLog.Debugf("Server quitting, exit early for peer %v", p) + srvrLog.DebugS(ctx, "Server quitting, exit early for peer") return } @@ -4671,7 +4723,7 @@ func (s *server) peerTerminationWatcher(p *peer.Brontide, ready chan struct{}) { // If there were any notification requests for when this peer // disconnected, we can trigger them now. - srvrLog.Debugf("Notifying that peer %v is offline", p) + srvrLog.DebugS(ctx, "Notifying that peer is offline") pubStr := string(pubKey.SerializeCompressed()) for _, offlineChan := range s.peerDisconnectedListeners[pubStr] { close(offlineChan) @@ -4701,7 +4753,7 @@ func (s *server) peerTerminationWatcher(p *peer.Brontide, ready chan struct{}) { // First, cleanup any remaining state the server has regarding the peer // in question. - s.removePeer(p) + s.removePeerUnsafe(ctx, p) // Next, check to see if this is a persistent peer or not. if _, ok := s.persistentPeers[pubStr]; !ok { @@ -4740,18 +4792,16 @@ func (s *server) peerTerminationWatcher(p *peer.Brontide, ready chan struct{}) { // the address used by our onion service to dial // to lnd), so we don't have enough information // to attempt a reconnect. - srvrLog.Debugf("Ignoring reconnection attempt "+ - "to inbound peer %v without "+ - "advertised address", p) + srvrLog.DebugS(ctx, "Ignoring reconnection attempt "+ + "to inbound peer without advertised address") return // We came across an error retrieving an advertised // address, log it, and fall back to the existing peer // address. default: - srvrLog.Errorf("Unable to retrieve advertised "+ - "address for node %x: %v", p.PubKey(), - err) + srvrLog.ErrorS(ctx, "Unable to retrieve advertised "+ + "address for peer", err) } // Make an easy lookup map so that we can check if an address @@ -4793,9 +4843,9 @@ func (s *server) peerTerminationWatcher(p *peer.Brontide, ready chan struct{}) { // call can stall for arbitrarily long if we shutdown while an // outbound connection attempt is being made. go func() { - srvrLog.Debugf("Scheduling connection re-establishment to "+ - "persistent peer %x in %s", - p.IdentityKey().SerializeCompressed(), backoff) + srvrLog.DebugS(ctx, "Scheduling connection "+ + "re-establishment to persistent peer", + "reconnecting_in", backoff) select { case <-time.After(backoff): @@ -4805,9 +4855,8 @@ func (s *server) peerTerminationWatcher(p *peer.Brontide, ready chan struct{}) { return } - srvrLog.Debugf("Attempting to re-establish persistent "+ - "connection to peer %x", - p.IdentityKey().SerializeCompressed()) + srvrLog.DebugS(ctx, "Attempting to re-establish persistent "+ + "connection") s.connectToPersistentPeer(pubStr) }() @@ -4910,29 +4959,24 @@ func (s *server) connectToPersistentPeer(pubKeyStr string) { }() } -// removePeer removes the passed peer from the server's state of all active -// peers. -func (s *server) removePeer(p *peer.Brontide) { +// removePeerUnsafe removes the passed peer from the server's state of all +// active peers. +// +// NOTE: Server mutex must be held when calling this function. +func (s *server) removePeerUnsafe(ctx context.Context, p *peer.Brontide) { if p == nil { return } - srvrLog.Debugf("removing peer %v", p) - - // As the peer is now finished, ensure that the TCP connection is - // closed and all of its related goroutines have exited. - p.Disconnect(fmt.Errorf("server: disconnecting peer %v", p)) + srvrLog.DebugS(ctx, "Removing peer") - // If this peer had an active persistent connection request, remove it. - if p.ConnReq() != nil { - s.connMgr.Remove(p.ConnReq().ID()) - } - - // Ignore deleting peers if we're shutting down. + // Exit early if we have already been instructed to shutdown, the peers + // will be disconnected in the server shutdown process. if s.Stopped() { return } + // Capture the peer's public key and string representation. pKey := p.PubKey() pubSer := pKey[:] pubStr := string(pubSer) @@ -4945,21 +4989,45 @@ func (s *server) removePeer(p *peer.Brontide) { delete(s.outboundPeers, pubStr) } - // Remove the peer's access permission from the access manager. - s.peerAccessMan.removePeerAccess(p.IdentityKey()) + // When removing the peer we make sure to disconnect it asynchronously + // to avoid blocking the main server goroutine because it is holding the + // server's mutex. Disconnecting the peer might block and wait until the + // peer has fully started up. This can happen if an inbound and outbound + // race condition occurs. + s.wg.Add(1) + go func() { + defer s.wg.Done() + + p.Disconnect(fmt.Errorf("server: disconnecting peer %v", p)) - // Copy the peer's error buffer across to the server if it has any items - // in it so that we can restore peer errors across connections. - if p.ErrorBuffer().Total() > 0 { - s.peerErrors[pubStr] = p.ErrorBuffer() - } + // If this peer had an active persistent connection request, + // remove it. + if p.ConnReq() != nil { + s.connMgr.Remove(p.ConnReq().ID()) + } - // Inform the peer notifier of a peer offline event so that it can be - // reported to clients listening for peer events. - var pubKey [33]byte - copy(pubKey[:], pubSer) + // Remove the peer's access permission from the access manager. + peerPubStr := string(p.IdentityKey().SerializeCompressed()) + s.peerAccessMan.removePeerAccess(ctx, peerPubStr) + + // Copy the peer's error buffer across to the server if it has + // any items in it so that we can restore peer errors across + // connections. We need to look up the error after the peer has + // been disconnected because we write the error in the + // `Disconnect` method. + s.mu.Lock() + if p.ErrorBuffer().Total() > 0 { + s.peerErrors[pubStr] = p.ErrorBuffer() + } + s.mu.Unlock() + + // Inform the peer notifier of a peer offline event so that it + // can be reported to clients listening for peer events. + var pubKey [33]byte + copy(pubKey[:], pubSer) - s.peerNotifier.NotifyPeerOffline(pubKey) + s.peerNotifier.NotifyPeerOffline(pubKey) + }() } // ConnectToPeer requests that the server connect to a Lightning Network peer @@ -4980,7 +5048,11 @@ func (s *server) ConnectToPeer(addr *lnwire.NetAddress, // Ensure we're not already connected to this peer. peer, err := s.findPeerByPubStr(targetPub) - if err == nil { + + // When there's no error it means we already have a connection with this + // peer. If this is a dev environment with the `--unsafeconnect` flag + // set, we will ignore the existing connection and continue. + if err == nil && !s.cfg.Dev.GetUnsafeConnect() { s.mu.Unlock() return &errPeerAlreadyConnected{peer: peer} } @@ -5095,8 +5167,11 @@ func (s *server) DisconnectPeer(pubKey *btcec.PublicKey) error { delete(s.persistentPeersBackoff, pubStr) // Remove the peer by calling Disconnect. Previously this was done with - // removePeer, which bypassed the peerTerminationWatcher. - peer.Disconnect(fmt.Errorf("server: DisconnectPeer called")) + // removePeerUnsafe, which bypassed the peerTerminationWatcher. + // + // NOTE: We call it in a goroutine to avoid blocking the main server + // goroutine because we might hold the server's mutex. + go peer.Disconnect(fmt.Errorf("server: DisconnectPeer called")) return nil } diff --git a/shachain/element_test.go b/shachain/element_test.go index df2b75593ad..3abdf7b9012 100644 --- a/shachain/element_test.go +++ b/shachain/element_test.go @@ -1,10 +1,10 @@ package shachain import ( + "errors" "reflect" "testing" - "github.com/go-errors/errors" "github.com/stretchr/testify/require" ) diff --git a/shachain/store.go b/shachain/store.go index 252abdfeb3b..8582e62cc2f 100644 --- a/shachain/store.go +++ b/shachain/store.go @@ -2,10 +2,11 @@ package shachain import ( "encoding/binary" + "errors" + "fmt" "io" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/go-errors/errors" ) // Store is an interface which serves as an abstraction over data structure @@ -117,7 +118,7 @@ func (store *RevocationStore) LookUp(v uint64) (*chainhash.Hash, error) { return &element.hash, nil } - return nil, errors.Errorf("unable to derive hash #%v", ind) + return nil, fmt.Errorf("unable to derive hash #%v", ind) } // AddNextEntry attempts to store the given hash within its internal storage in