Repository: lightningnetwork/lnd
Pull request #6232 (2022-02-03 14:35:04)

htlcswitch: add an always on mode to htlc interceptor
Adds a flag that makes htlc interception a requirement. The switch will queue htlcs as long as there is no interceptor connected. When the stream disconnects, htlcs waiting for resolution will not resume automatically.

Supersedes #6165 

Solves #6161 

joostjager: @carlaKC @champo I tried a few variations of the always on interceptor and ended up with what's in this PR. Tried to address the outstanding comments in https://github.com/lightningnetwork/lnd/pull/6165
champo: This is a great improvement! Moving the logic to interceptable switch makes a lot of sense.
alexbosworth: could it reject all htlcs when an interceptor is not connected rather than queue them?
joostjager: >could it reject all htlcs when an interceptor is not connected rather than queue them?

That is a dangerous setup. The interceptor may have signaled to settle an htlc. Then lnd goes down for some reason, replays the packet but finds no interceptor. It will then fail the htlc while the interceptor assumes it has been settled.
alexbosworth: > > could it reject all htlcs when an interceptor is not connected rather than queue them?
> 
> That is a dangerous setup. The interceptor may have signaled to settle an htlc. Then lnd goes down for some reason, replays the packet but finds no interceptor. It will then fail the htlc while the interceptor assumes it has been settled.

I mean new HTLCs, not ones that were already received, or is that what this does do?
joostjager: Got it. Yes, I think that could be safe. Not sure though if we have the (persisted) information somewhere that indicates whether the htlc is new or not.

We have the plan to do a height watcher in a follow-up pr that fails htlcs when they get close to the broadcast deadline. This has sort of the same effect when the interceptor doesn't connect anymore, only delayed.

Are concerns about force-closes the reason why you ask?
alexbosworth: > We have the plan to do a height watcher in a follow-up pr that fails htlcs when they get close to the broadcast deadline. This has sort of the same effect when the interceptor doesn't connect anymore, only delayed.

> Are concerns about force-closes the reason why you ask?

I think force closes are definitely a concern due to closing costs but also the case that excessively holding HTLCs start to represent a cost to the holding party. I think failing HTLCs is a simpler failure mode than holding other people's pending payments open
joostjager: The always-on mode is intended for interceptors that are high availability. Given that, dangling htlcs should be an edge case.
alexbosworth: Is the queue bounded by the HTLC slots? The other concern is that if the interceptor failed that HTLCs would queue endlessly?
joostjager: Added commit that fails fresh packets if the interceptor is required but not connected. Interested to hear the opinion of reviewers on this addition.
champo: I think failing HTLCs when the interceptor is disconnected is not a good idea from a reliability stand point given that only one interceptor can be registered at a given time. It means deploys or restarts of an interceptor are always visible downtime to users or if you use the interceptor to enforce some sort of traffic policy it means unwanted HTLCs might be handled automatically. Always holding HTLCs + height based failure IMO makes it safe to use the API and easier to reason about what's going on from the implementors point of view.

alexbosworth: A compromise could be: don't hold HTLCs longer than x blocks when interceptor is disconnected?
joostjager: If the interceptor is down, isn't it preferred to present the user with a failure rather than holding on to the htlc and locking capital along the route for an extended period of time (several blocks)?
alexbosworth: Yeah I'm saying hold capital for the least possible amount of time
champo: I guess that depends on whether you expect the interceptor to have high uptime or not. The question can also be: if the intercptor will come right back up, why present the user with a failure? That said, this can be easily customized with a small patch, I'll defer to the mantainers opinions.
joostjager: What would be a reasonable time for this? To just cover deployment windows, it doesn't need to be long. Definitely not until getting close to htlc expiry.
joostjager: I think a watcher that does the following two things should cover it all:

* Watches block height for all pending intercepts and auto-fails before the channel is force-closed
* Keeps a timer for _new_ pending intercepts and auto-fails if no interceptor connects in time.
lightninglabs-deploy: @yyforyongyu: review reminder

Patch:
diff --git a/config.go b/config.go
index 6a710ee79..6f7a66b98 100644
--- a/config.go
+++ b/config.go
@@ -354,6 +354,10 @@ type Config struct {
 
 	RejectHTLC bool `long:"rejecthtlc" description:"If true, lnd will not forward any HTLCs that are meant as onward payments. This option will still allow lnd to send HTLCs and receive HTLCs but lnd won't be used as a hop."`
 
+	// RequireInterceptor determines whether the HTLC interceptor is
+	// registered regardless of whether the RPC is called or not.
+	RequireInterceptor bool `long:"requireinterceptor" description:"Whether to always intercept HTLCs, even if no stream is attached"`
+
 	StaggerInitialReconnect bool `long:"stagger-initial-reconnect" description:"If true, will apply a randomized staggering between 0s and 30s when reconnecting to persistent peers on startup. The first 10 reconnections will be attempted instantly, regardless of the flag's value"`
 
 	MaxOutgoingCltvExpiry uint32 `long:"max-cltv-expiry" description:"The maximum number of blocks funds could be locked up for when forwarding payments."`
diff --git a/discovery/gossiper.go b/discovery/gossiper.go
index 18681843f..858ede922 100644
--- a/discovery/gossiper.go
+++ b/discovery/gossiper.go
@@ -1758,1209 +1758,1220 @@ func (d *AuthenticatedGossiper) processNetworkAnnouncement(
 		schedulerOp = append(schedulerOp, batch.LazyAdd())
 	}
 
+	var announcements []networkMsg
+
 	switch msg := nMsg.msg.(type) {
+
 	// A new node announcement has arrived which either presents new
 	// information about a node in one of the channels we know about, or a
 	// updating previously advertised information.
 	case *lnwire.NodeAnnouncement:
-		return d.handleNodeAnnouncement(nMsg, msg, schedulerOp)
+		timestamp := time.Unix(int64(msg.Timestamp), 0)
+
+		// We'll quickly ask the router if it already has a
+		// newer update for this node so we can skip validating
+		// signatures if not required.
+		if d.cfg.Router.IsStaleNode(msg.NodeID, timestamp) {
+			log.Debugf("Skipped processing stale node: %x",
+				msg.NodeID)
+			nMsg.err <- nil
+			return nil, true
+		}
+
+		if err := d.addNode(msg, schedulerOp...); err != nil {
+			log.Debugf("Adding node: %x got error: %v",
+				msg.NodeID, err)
+
+			if !routing.IsError(
+				err,
+				routing.ErrOutdated,
+				routing.ErrIgnored,
+				routing.ErrVBarrierShuttingDown,
+			) {
+
+				log.Error(err)
+			}
+
+			nMsg.err <- err
+			return nil, false
+		}
+
+		// In order to ensure we don't leak unadvertised nodes, we'll
+		// make a quick check to ensure this node intends to publicly
+		// advertise itself to the network.
+		isPublic, err := d.cfg.Router.IsPublicNode(msg.NodeID)
+		if err != nil {
+			log.Errorf("Unable to determine if node %x is "+
+				"advertised: %v", msg.NodeID, err)
+			nMsg.err <- err
+			return nil, false
+		}
+
+		// If it does, we'll add their announcement to our batch so that
+		// it can be broadcast to the rest of our peers.
+		if isPublic {
+			announcements = append(announcements, networkMsg{
+				peer:   nMsg.peer,
+				source: nMsg.source,
+				msg:    msg,
+			})
+		} else {
+			log.Tracef("Skipping broadcasting node announcement "+
+				"for %x due to being unadvertised", msg.NodeID)
+		}
+
+		nMsg.err <- nil
+		// TODO(roasbeef): get rid of the above
+		return announcements, true
 
 	// A new channel announcement has arrived, this indicates the
 	// *creation* of a new channel within the network. This only advertises
 	// the existence of a channel and not yet the routing policies in
 	// either direction of the channel.
 	case *lnwire.ChannelAnnouncement:
-		return d.handleChanAnnouncement(nMsg, msg, schedulerOp)
+		// We'll ignore any channel announcements that target any chain
+		// other than the set of chains we know of.
+		if !bytes.Equal(msg.ChainHash[:], d.cfg.ChainHash[:]) {
+			err := fmt.Errorf("ignoring ChannelAnnouncement from "+
+				"chain=%v, gossiper on chain=%v", msg.ChainHash,
+				d.cfg.ChainHash)
+			log.Errorf(err.Error())
 
-	// A new authenticated channel edge update has arrived. This indicates
-	// that the directional information for an already known channel has
-	// been updated.
-	case *lnwire.ChannelUpdate:
-		return d.handleChanUpdate(nMsg, msg, schedulerOp)
+			key := newRejectCacheKey(
+				msg.ShortChannelID.ToUint64(),
+				sourceToPub(nMsg.source),
+			)
+			_, _ = d.recentRejects.Put(key, &cachedReject{})
 
-	// A new signature announcement has been received. This indicates
-	// willingness of nodes involved in the funding of a channel to
-	// announce this new channel to the rest of the world.
-	case *lnwire.AnnounceSignatures:
-		return d.handleAnnSig(nMsg, msg)
+			nMsg.err <- err
+			return nil, false
+		}
 
-	default:
-		err := errors.New("wrong type of the announcement")
-		nMsg.err <- err
-		return nil, false
-	}
-}
+		// If the advertised inclusionary block is beyond our knowledge
+		// of the chain tip, then we'll ignore for it now.
+		d.Lock()
+		if nMsg.isRemote && d.isPremature(msg.ShortChannelID, 0, nMsg) {
+			log.Warnf("Announcement for chan_id=(%v), is "+
+				"premature: advertises height %v, only "+
+				"height %v is known",
+				msg.ShortChannelID.ToUint64(),
+				msg.ShortChannelID.BlockHeight,
+				d.bestHeight)
+			d.Unlock()
+			nMsg.err <- nil
+			return nil, false
+		}
+		d.Unlock()
 
-// processZombieUpdate determines whether the provided channel update should
-// resurrect a given zombie edge.
-func (d *AuthenticatedGossiper) processZombieUpdate(
-	chanInfo *channeldb.ChannelEdgeInfo, msg *lnwire.ChannelUpdate) error {
+		// At this point, we'll now ask the router if this is a
+		// zombie/known edge. If so we can skip all the processing
+		// below.
+		if d.cfg.Router.IsKnownEdge(msg.ShortChannelID) {
+			nMsg.err <- nil
+			return nil, true
+		}
 
-	// The least-significant bit in the flag on the channel update tells us
-	// which edge is being updated.
-	isNode1 := msg.ChannelFlags&lnwire.ChanUpdateDirection == 0
+		// If this is a remote channel announcement, then we'll validate
+		// all the signatures within the proof as it should be well
+		// formed.
+		var proof *channeldb.ChannelAuthProof
+		if nMsg.isRemote {
+			if err := routing.ValidateChannelAnn(msg); err != nil {
+				err := fmt.Errorf("unable to validate "+
+					"announcement: %v", err)
 
-	// Since we've deemed the update as not stale above, before marking it
-	// live, we'll make sure it has been signed by the correct party. If we
-	// have both pubkeys, either party can resurect the channel. If we've
-	// already marked this with the stricter, single-sided resurrection we
-	// will only have the pubkey of the node with the oldest timestamp.
-	var pubKey *btcec.PublicKey
-	switch {
-	case isNode1 && chanInfo.NodeKey1Bytes != emptyPubkey:
-		pubKey, _ = chanInfo.NodeKey1()
-	case !isNode1 && chanInfo.NodeKey2Bytes != emptyPubkey:
-		pubKey, _ = chanInfo.NodeKey2()
-	}
-	if pubKey == nil {
-		return fmt.Errorf("incorrect pubkey to resurrect zombie "+
-			"with chan_id=%v", msg.ShortChannelID)
-	}
+				key := newRejectCacheKey(
+					msg.ShortChannelID.ToUint64(),
+					sourceToPub(nMsg.source),
+				)
+				_, _ = d.recentRejects.Put(key, &cachedReject{})
 
-	err := routing.VerifyChannelUpdateSignature(msg, pubKey)
-	if err != nil {
-		return fmt.Errorf("unable to verify channel "+
-			"update signature: %v", err)
-	}
+				log.Error(err)
+				nMsg.err <- err
+				return nil, false
+			}
 
-	// With the signature valid, we'll proceed to mark the
-	// edge as live and wait for the channel announcement to
-	// come through again.
-	err = d.cfg.Router.MarkEdgeLive(msg.ShortChannelID)
-	if err != nil {
-		return fmt.Errorf("unable to remove edge with "+
-			"chan_id=%v from zombie index: %v",
-			msg.ShortChannelID, err)
-	}
+			// If the proof checks out, then we'll save the proof
+			// itself to the database so we can fetch it later when
+			// gossiping with other nodes.
+			proof = &channeldb.ChannelAuthProof{
+				NodeSig1Bytes:    msg.NodeSig1.ToSignatureBytes(),
+				NodeSig2Bytes:    msg.NodeSig2.ToSignatureBytes(),
+				BitcoinSig1Bytes: msg.BitcoinSig1.ToSignatureBytes(),
+				BitcoinSig2Bytes: msg.BitcoinSig2.ToSignatureBytes(),
+			}
+		}
 
-	log.Debugf("Removed edge with chan_id=%v from zombie "+
-		"index", msg.ShortChannelID)
+		// With the proof validate (if necessary), we can now store it
+		// within the database for our path finding and syncing needs.
+		var featureBuf bytes.Buffer
+		if err := msg.Features.Encode(&featureBuf); err != nil {
+			log.Errorf("unable to encode features: %v", err)
+			nMsg.err <- err
+			return nil, false
+		}
 
-	return nil
-}
+		edge := &channeldb.ChannelEdgeInfo{
+			ChannelID:        msg.ShortChannelID.ToUint64(),
+			ChainHash:        msg.ChainHash,
+			NodeKey1Bytes:    msg.NodeID1,
+			NodeKey2Bytes:    msg.NodeID2,
+			BitcoinKey1Bytes: msg.BitcoinKey1,
+			BitcoinKey2Bytes: msg.BitcoinKey2,
+			AuthProof:        proof,
+			Features:         featureBuf.Bytes(),
+			ExtraOpaqueData:  msg.ExtraOpaqueData,
+		}
 
-// fetchNodeAnn fetches the latest signed node announcement from our point of
-// view for the node with the given public key.
-func (d *AuthenticatedGossiper) fetchNodeAnn(
-	pubKey [33]byte) (*lnwire.NodeAnnouncement, error) {
+		// If there were any optional message fields provided, we'll
+		// include them in its serialized disk representation now.
+		if nMsg.optionalMsgFields != nil {
+			if nMsg.optionalMsgFields.capacity != nil {
+				edge.Capacity = *nMsg.optionalMsgFields.capacity
+			}
+			if nMsg.optionalMsgFields.channelPoint != nil {
+				edge.ChannelPoint = *nMsg.optionalMsgFields.channelPoint
+			}
+		}
 
-	node, err := d.cfg.Router.FetchLightningNode(pubKey)
-	if err != nil {
-		return nil, err
-	}
+		// We will add the edge to the channel router. If the nodes
+		// present in this channel are not present in the database, a
+		// partial node will be added to represent each node while we
+		// wait for a node announcement.
+		//
+		// Before we add the edge to the database, we obtain
+		// the mutex for this channel ID. We do this to ensure
+		// no other goroutine has read the database and is now
+		// making decisions based on this DB state, before it
+		// writes to the DB.
+		d.channelMtx.Lock(msg.ShortChannelID.ToUint64())
+		err := d.cfg.Router.AddEdge(edge, schedulerOp...)
+		if err != nil {
+			defer d.channelMtx.Unlock(msg.ShortChannelID.ToUint64())
+
+			// If the edge was rejected due to already being known,
+			// then it may be that case that this new message has a
+			// fresh channel proof, so we'll check.
+			if routing.IsError(err, routing.ErrIgnored) {
+				// Attempt to process the rejected message to
+				// see if we get any new announcements.
+				anns, rErr := d.processRejectedEdge(msg, proof)
+				if rErr != nil {
+
+					key := newRejectCacheKey(
+						msg.ShortChannelID.ToUint64(),
+						sourceToPub(nMsg.source),
+					)
+					_, _ = d.recentRejects.Put(key, &cachedReject{})
 
-	return node.NodeAnnouncement(true)
-}
+					nMsg.err <- rErr
+					return nil, false
+				}
 
-// isMsgStale determines whether a message retrieved from the backing
-// MessageStore is seen as stale by the current graph.
-func (d *AuthenticatedGossiper) isMsgStale(msg lnwire.Message) bool {
-	switch msg := msg.(type) {
-	case *lnwire.AnnounceSignatures:
-		chanInfo, _, _, err := d.cfg.Router.GetChannelByID(
-			msg.ShortChannelID,
-		)
+				// If while processing this rejected edge, we
+				// realized there's a set of announcements we
+				// could extract, then we'll return those
+				// directly.
+				if len(anns) != 0 {
+					nMsg.err <- nil
+					return anns, true
+				}
 
-		// If the channel cannot be found, it is most likely a leftover
-		// message for a channel that was closed, so we can consider it
-		// stale.
-		if err == channeldb.ErrEdgeNotFound {
-			return true
+				// Otherwise, this is just a regular rejected
+				// edge.
+				log.Debugf("Router rejected channel "+
+					"edge: %v", err)
+			} else {
+				log.Debugf("Router rejected channel "+
+					"edge: %v", err)
+
+				key := newRejectCacheKey(
+					msg.ShortChannelID.ToUint64(),
+					sourceToPub(nMsg.source),
+				)
+				_, _ = d.recentRejects.Put(key, &cachedReject{})
+			}
+
+			nMsg.err <- err
+			return nil, false
 		}
-		if err != nil {
-			log.Debugf("Unable to retrieve channel=%v from graph: "+
-				"%v", err)
-			return false
+
+		// If err is nil, release the lock immediately.
+		d.channelMtx.Unlock(msg.ShortChannelID.ToUint64())
+
+		// If we earlier received any ChannelUpdates for this channel,
+		// we can now process them, as the channel is added to the
+		// graph.
+		shortChanID := msg.ShortChannelID.ToUint64()
+		var channelUpdates []*networkMsg
+
+		earlyChanUpdates, err := d.prematureChannelUpdates.Get(shortChanID)
+		if err == nil {
+			// There was actually an entry in the map, so we'll
+			// accumulate it. We don't worry about deletion, since
+			// it'll eventually fall out anyway.
+			chanMsgs := earlyChanUpdates.(*cachedNetworkMsg)
+			channelUpdates = append(channelUpdates, chanMsgs.msgs...)
 		}
 
-		// If the proof exists in the graph, then we have successfully
-		// received the remote proof and assembled the full proof, so we
-		// can safely delete the local proof from the database.
-		return chanInfo.AuthProof != nil
+		// Launch a new goroutine to handle each ChannelUpdate, this to
+		// ensure we don't block here, as we can handle only one
+		// announcement at a time.
+		for _, cu := range channelUpdates {
+			d.wg.Add(1)
+			go func(nMsg *networkMsg) {
+				defer d.wg.Done()
 
-	case *lnwire.ChannelUpdate:
-		_, p1, p2, err := d.cfg.Router.GetChannelByID(msg.ShortChannelID)
+				switch msg := nMsg.msg.(type) {
 
-		// If the channel cannot be found, it is most likely a leftover
-		// message for a channel that was closed, so we can consider it
-		// stale.
-		if err == channeldb.ErrEdgeNotFound {
-			return true
+				// Reprocess the message, making sure we return
+				// an error to the original caller in case the
+				// gossiper shuts down.
+				case *lnwire.ChannelUpdate:
+					log.Debugf("Reprocessing"+
+						" ChannelUpdate for "+
+						"shortChanID=%v",
+						msg.ShortChannelID.ToUint64())
+
+					select {
+					case d.networkMsgs <- nMsg:
+					case <-d.quit:
+						nMsg.err <- ErrGossiperShuttingDown
+					}
+
+				// We don't expect any other message type than
+				// ChannelUpdate to be in this map.
+				default:
+					log.Errorf("Unsupported message type "+
+						"found among ChannelUpdates: "+
+						"%T", msg)
+				}
+			}(cu)
 		}
-		if err != nil {
-			log.Debugf("Unable to retrieve channel=%v from graph: "+
-				"%v", msg.ShortChannelID, err)
-			return false
+
+		// Channel announcement was successfully proceeded and know it
+		// might be broadcast to other connected nodes if it was
+		// announcement with proof (remote).
+		if proof != nil {
+			announcements = append(announcements, networkMsg{
+				peer:   nMsg.peer,
+				source: nMsg.source,
+				msg:    msg,
+			})
 		}
 
-		// Otherwise, we'll retrieve the correct policy that we
-		// currently have stored within our graph to check if this
-		// message is stale by comparing its timestamp.
-		var p *channeldb.ChannelEdgePolicy
-		if msg.ChannelFlags&lnwire.ChanUpdateDirection == 0 {
-			p = p1
-		} else {
-			p = p2
+		nMsg.err <- nil
+		return announcements, true
+
+	// A new authenticated channel edge update has arrived. This indicates
+	// that the directional information for an already known channel has
+	// been updated.
+	case *lnwire.ChannelUpdate:
+		// We'll ignore any channel announcements that target any chain
+		// other than the set of chains we know of.
+		if !bytes.Equal(msg.ChainHash[:], d.cfg.ChainHash[:]) {
+			err := fmt.Errorf("ignoring ChannelUpdate from "+
+				"chain=%v, gossiper on chain=%v", msg.ChainHash,
+				d.cfg.ChainHash)
+			log.Errorf(err.Error())
+
+			key := newRejectCacheKey(
+				msg.ShortChannelID.ToUint64(),
+				sourceToPub(nMsg.source),
+			)
+			_, _ = d.recentRejects.Put(key, &cachedReject{})
+
+			nMsg.err <- err
+			return nil, false
 		}
 
-		// If the policy is still unknown, then we can consider this
-		// policy fresh.
-		if p == nil {
-			return false
+		blockHeight := msg.ShortChannelID.BlockHeight
+		shortChanID := msg.ShortChannelID.ToUint64()
+
+		// If the advertised inclusionary block is beyond our knowledge
+		// of the chain tip, then we'll put the announcement in limbo
+		// to be fully verified once we advance forward in the chain.
+		d.Lock()
+		if nMsg.isRemote && d.isPremature(msg.ShortChannelID, 0, nMsg) {
+			log.Warnf("Update announcement for "+
+				"short_chan_id(%v), is premature: advertises "+
+				"height %v, only height %v is known",
+				shortChanID, blockHeight,
+				d.bestHeight)
+			d.Unlock()
+			nMsg.err <- nil
+			return nil, false
 		}
+		d.Unlock()
 
+		// Before we perform any of the expensive checks below, we'll
+		// check whether this update is stale or is for a zombie
+		// channel in order to quickly reject it.
 		timestamp := time.Unix(int64(msg.Timestamp), 0)
-		return p.LastUpdate.After(timestamp)
-
-	default:
-		// We'll make sure to not mark any unsupported messages as stale
-		// to ensure they are not removed.
-		return false
-	}
-}
+		if d.cfg.Router.IsStaleEdgePolicy(
+			msg.ShortChannelID, timestamp, msg.ChannelFlags,
+		) {
 
-// updateChannel creates a new fully signed update for the channel, and updates
-// the underlying graph with the new state.
-func (d *AuthenticatedGossiper) updateChannel(info *channeldb.ChannelEdgeInfo,
-	edge *channeldb.ChannelEdgePolicy) (*lnwire.ChannelAnnouncement,
-	*lnwire.ChannelUpdate, error) {
+			log.Debugf("Ignored stale edge policy: peer=%v, "+
+				"source=%x, msg=%s, is_remote=%v", nMsg.peer,
+				nMsg.source.SerializeCompressed(),
+				nMsg.msg.MsgType(), nMsg.isRemote)
 
-	// Parse the unsigned edge into a channel update.
-	chanUpdate := netann.UnsignedChannelUpdateFromEdge(info, edge)
+			nMsg.err <- nil
+			return nil, true
+		}
 
-	// We'll generate a new signature over a digest of the channel
-	// announcement itself and update the timestamp to ensure it propagate.
-	err := netann.SignChannelUpdate(
-		d.cfg.AnnSigner, d.selfKeyLoc, chanUpdate,
-		netann.ChanUpdSetTimestamp,
-	)
-	if err != nil {
-		return nil, nil, err
-	}
+		// Get the node pub key as far as we don't have it in channel
+		// update announcement message. We'll need this to properly
+		// verify message signature.
+		//
+		// We make sure to obtain the mutex for this channel ID
+		// before we access the database. This ensures the state
+		// we read from the database has not changed between this
+		// point and when we call UpdateEdge() later.
+		d.channelMtx.Lock(msg.ShortChannelID.ToUint64())
+		defer d.channelMtx.Unlock(msg.ShortChannelID.ToUint64())
+		chanInfo, edge1, edge2, err := d.cfg.Router.GetChannelByID(msg.ShortChannelID)
+		switch err {
+		// No error, break.
+		case nil:
+			break
+
+		case channeldb.ErrZombieEdge:
+			err = d.processZombieUpdate(chanInfo, msg)
+			if err != nil {
+				log.Debug(err)
+				nMsg.err <- err
+				return nil, false
+			}
 
-	// Next, we'll set the new signature in place, and update the reference
-	// in the backing slice.
-	edge.LastUpdate = time.Unix(int64(chanUpdate.Timestamp), 0)
-	edge.SigBytes = chanUpdate.Signature.ToSignatureBytes()
+			// We'll fallthrough to ensure we stash the update until
+			// we receive its corresponding ChannelAnnouncement.
+			// This is needed to ensure the edge exists in the graph
+			// before applying the update.
+			fallthrough
+		case channeldb.ErrGraphNotFound:
+			fallthrough
+		case channeldb.ErrGraphNoEdgesFound:
+			fallthrough
+		case channeldb.ErrEdgeNotFound:
+			// If the edge corresponding to this ChannelUpdate was
+			// not found in the graph, this might be a channel in
+			// the process of being opened, and we haven't processed
+			// our own ChannelAnnouncement yet, hence it is not
+			// found in the graph. This usually gets resolved after
+			// the channel proofs are exchanged and the channel is
+			// broadcasted to the rest of the network, but in case
+			// this is a private channel this won't ever happen.
+			// This can also happen in the case of a zombie channel
+			// with a fresh update for which we don't have a
+			// ChannelAnnouncement for since we reject them. Because
+			// of this, we temporarily add it to a map, and
+			// reprocess it after our own ChannelAnnouncement has
+			// been processed.
+			earlyMsgs, err := d.prematureChannelUpdates.Get(
+				shortChanID,
+			)
+			switch {
+			// Nothing in the cache yet, we can just directly
+			// insert this element.
+			case err == cache.ErrElementNotFound:
+				_, _ = d.prematureChannelUpdates.Put(
+					shortChanID, &cachedNetworkMsg{
+						msgs: []*networkMsg{nMsg},
+					})
+
+			// There's already something in the cache, so we'll
+			// combine the set of messages into a single value.
+			default:
+				msgs := earlyMsgs.(*cachedNetworkMsg).msgs
+				msgs = append(msgs, nMsg)
+				_, _ = d.prematureChannelUpdates.Put(
+					shortChanID, &cachedNetworkMsg{
+						msgs: msgs,
+					})
+			}
 
-	// To ensure that our signature is valid, we'll verify it ourself
-	// before committing it to the slice returned.
-	err = routing.ValidateChannelUpdateAnn(d.selfKey, info.Capacity, chanUpdate)
-	if err != nil {
-		return nil, nil, fmt.Errorf("generated invalid channel "+
-			"update sig: %v", err)
-	}
+			log.Debugf("Got ChannelUpdate for edge not found in "+
+				"graph(shortChanID=%v), saving for "+
+				"reprocessing later", shortChanID)
 
-	// Finally, we'll write the new edge policy to disk.
-	if err := d.cfg.Router.UpdateEdge(edge); err != nil {
-		return nil, nil, err
-	}
+			// 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.
+			return nil, false
 
-	// We'll also create the original channel announcement so the two can
-	// be broadcast along side each other (if necessary), but only if we
-	// have a full channel announcement for this channel.
-	var chanAnn *lnwire.ChannelAnnouncement
-	if info.AuthProof != nil {
-		chanID := lnwire.NewShortChanIDFromInt(info.ChannelID)
-		chanAnn = &lnwire.ChannelAnnouncement{
-			ShortChannelID:  chanID,
-			NodeID1:         info.NodeKey1Bytes,
-			NodeID2:         info.NodeKey2Bytes,
-			ChainHash:       info.ChainHash,
-			BitcoinKey1:     info.BitcoinKey1Bytes,
-			Features:        lnwire.NewRawFeatureVector(),
-			BitcoinKey2:     info.BitcoinKey2Bytes,
-			ExtraOpaqueData: edge.ExtraOpaqueData,
-		}
-		chanAnn.NodeSig1, err = lnwire.NewSigFromRawSignature(
-			info.AuthProof.NodeSig1Bytes,
-		)
-		if err != nil {
-			return nil, nil, err
+		default:
+			err := fmt.Errorf("unable to validate channel update "+
+				"short_chan_id=%v: %v", shortChanID, err)
+			log.Error(err)
+			nMsg.err <- err
+
+			key := newRejectCacheKey(
+				msg.ShortChannelID.ToUint64(),
+				sourceToPub(nMsg.source),
+			)
+			_, _ = d.recentRejects.Put(key, &cachedReject{})
+
+			return nil, false
 		}
-		chanAnn.NodeSig2, err = lnwire.NewSigFromRawSignature(
-			info.AuthProof.NodeSig2Bytes,
+
+		// The least-significant bit in the flag on the channel update
+		// announcement tells us "which" side of the channels directed
+		// edge is being updated.
+		var (
+			pubKey       *btcec.PublicKey
+			edgeToUpdate *channeldb.ChannelEdgePolicy
 		)
-		if err != nil {
-			return nil, nil, err
+		direction := msg.ChannelFlags & lnwire.ChanUpdateDirection
+		switch direction {
+		case 0:
+			pubKey, _ = chanInfo.NodeKey1()
+			edgeToUpdate = edge1
+		case 1:
+			pubKey, _ = chanInfo.NodeKey2()
+			edgeToUpdate = edge2
 		}
-		chanAnn.BitcoinSig1, err = lnwire.NewSigFromRawSignature(
-			info.AuthProof.BitcoinSig1Bytes,
-		)
-		if err != nil {
-			return nil, nil, err
+
+		// If we have a previous version of the edge being updated,
+		// we'll want to rate limit its updates to prevent spam
+		// throughout the network.
+		if nMsg.isRemote && edgeToUpdate != nil {
+			// If it's a keep-alive update, we'll only propagate one
+			// if it's been a day since the previous. This follows
+			// our own heuristic of sending keep-alive updates after
+			// the same duration (see retransmitStaleAnns).
+			timeSinceLastUpdate := timestamp.Sub(edgeToUpdate.LastUpdate)
+			if IsKeepAliveUpdate(msg, edgeToUpdate) {
+				if timeSinceLastUpdate < d.cfg.RebroadcastInterval {
+					log.Debugf("Ignoring keep alive update "+
+						"not within %v period for "+
+						"channel %v",
+						d.cfg.RebroadcastInterval,
+						shortChanID)
+					nMsg.err <- nil
+					return nil, false
+				}
+			} else {
+				// If it's not, we'll allow an update per minute
+				// with a maximum burst of 10. If we haven't
+				// seen an update for this channel before, we'll
+				// need to initialize a rate limiter for each
+				// direction.
+				d.Lock()
+				rateLimiters, ok := d.chanUpdateRateLimiter[shortChanID]
+				if !ok {
+					r := rate.Every(d.cfg.ChannelUpdateInterval)
+					b := d.cfg.MaxChannelUpdateBurst
+					rateLimiters = [2]*rate.Limiter{
+						rate.NewLimiter(r, b),
+						rate.NewLimiter(r, b),
+					}
+					d.chanUpdateRateLimiter[shortChanID] = rateLimiters
+				}
+				d.Unlock()
+
+				if !rateLimiters[direction].Allow() {
+					log.Debugf("Rate limiting update for "+
+						"channel %v from direction %x",
+						shortChanID,
+						pubKey.SerializeCompressed())
+					nMsg.err <- nil
+					return nil, false
+				}
+			}
 		}
-		chanAnn.BitcoinSig2, err = lnwire.NewSigFromRawSignature(
-			info.AuthProof.BitcoinSig2Bytes,
-		)
+
+		// Validate the channel announcement with the expected public key and
+		// channel capacity. In the case of an invalid channel update, we'll
+		// return an error to the caller and exit early.
+		err = routing.ValidateChannelUpdateAnn(pubKey, chanInfo.Capacity, msg)
 		if err != nil {
-			return nil, nil, err
-		}
-	}
+			rErr := fmt.Errorf("unable to validate channel "+
+				"update announcement for short_chan_id=%v: %v",
+				spew.Sdump(msg.ShortChannelID), err)
 
-	return chanAnn, chanUpdate, err
-}
+			log.Error(rErr)
+			nMsg.err <- rErr
+			return nil, false
+		}
 
-// SyncManager returns the gossiper's SyncManager instance.
-func (d *AuthenticatedGossiper) SyncManager() *SyncManager {
-	return d.syncMgr
-}
+		update := &channeldb.ChannelEdgePolicy{
+			SigBytes:                  msg.Signature.ToSignatureBytes(),
+			ChannelID:                 shortChanID,
+			LastUpdate:                timestamp,
+			MessageFlags:              msg.MessageFlags,
+			ChannelFlags:              msg.ChannelFlags,
+			TimeLockDelta:             msg.TimeLockDelta,
+			MinHTLC:                   msg.HtlcMinimumMsat,
+			MaxHTLC:                   msg.HtlcMaximumMsat,
+			FeeBaseMSat:               lnwire.MilliSatoshi(msg.BaseFee),
+			FeeProportionalMillionths: lnwire.MilliSatoshi(msg.FeeRate),
+			ExtraOpaqueData:           msg.ExtraOpaqueData,
+		}
 
-// IsKeepAliveUpdate determines whether this channel update is considered a
-// keep-alive update based on the previous channel update processed for the same
-// direction.
-func IsKeepAliveUpdate(update *lnwire.ChannelUpdate,
-	prev *channeldb.ChannelEdgePolicy) bool {
+		if err := d.cfg.Router.UpdateEdge(update, schedulerOp...); err != nil {
+			if routing.IsError(
+				err, routing.ErrOutdated,
+				routing.ErrIgnored,
+				routing.ErrVBarrierShuttingDown,
+			) {
 
-	// Both updates should be from the same direction.
-	if update.ChannelFlags&lnwire.ChanUpdateDirection !=
-		prev.ChannelFlags&lnwire.ChanUpdateDirection {
+				log.Debug(err)
+			} else {
+				key := newRejectCacheKey(
+					msg.ShortChannelID.ToUint64(),
+					sourceToPub(nMsg.source),
+				)
+				_, _ = d.recentRejects.Put(key, &cachedReject{})
 
-		return false
-	}
+				log.Error(err)
+			}
 
-	// The timestamp should always increase for a keep-alive update.
-	timestamp := time.Unix(int64(update.Timestamp), 0)
-	if !timestamp.After(prev.LastUpdate) {
-		return false
-	}
+			nMsg.err <- err
+			return nil, false
+		}
 
-	// None of the remaining fields should change for a keep-alive update.
-	if update.ChannelFlags.IsDisabled() != prev.ChannelFlags.IsDisabled() {
-		return false
-	}
-	if lnwire.MilliSatoshi(update.BaseFee) != prev.FeeBaseMSat {
-		return false
-	}
-	if lnwire.MilliSatoshi(update.FeeRate) != prev.FeeProportionalMillionths {
-		return false
-	}
-	if update.TimeLockDelta != prev.TimeLockDelta {
-		return false
-	}
-	if update.HtlcMinimumMsat != prev.MinHTLC {
-		return false
-	}
-	if update.MessageFlags.HasMaxHtlc() && !prev.MessageFlags.HasMaxHtlc() {
-		return false
-	}
-	if update.HtlcMaximumMsat != prev.MaxHTLC {
-		return false
-	}
-	if !bytes.Equal(update.ExtraOpaqueData, prev.ExtraOpaqueData) {
-		return false
-	}
-	return true
-}
+		// If this is a local ChannelUpdate without an AuthProof, it
+		// means it is an update to a channel that is not (yet)
+		// supposed to be announced to the greater network. However,
+		// our channel counter party will need to be given the update,
+		// so we'll try sending the update directly to the remote peer.
+		if !nMsg.isRemote && chanInfo.AuthProof == nil {
+			// Get our peer's public key.
+			remotePubKey := remotePubFromChanInfo(
+				chanInfo, msg.ChannelFlags,
+			)
 
-// latestHeight returns the gossiper's latest height known of the chain.
-func (d *AuthenticatedGossiper) latestHeight() uint32 {
-	d.Lock()
-	defer d.Unlock()
-	return d.bestHeight
-}
+			log.Debugf("The message %v has no AuthProof, sending "+
+				"the update to remote peer %x",
+				msg.MsgType(), remotePubKey)
 
-// handleNodeAnnouncement processes a new node announcement.
-func (d *AuthenticatedGossiper) handleNodeAnnouncement(nMsg *networkMsg,
-	nodeAnn *lnwire.NodeAnnouncement,
-	ops []batch.SchedulerOption) ([]networkMsg, bool) {
+			// Now, we'll attempt to send the channel update message
+			// reliably to the remote peer in the background, so
+			// that we don't block if the peer happens to be offline
+			// at the moment.
+			err := d.reliableSender.sendMessage(msg, remotePubKey)
+			if err != nil {
+				err := fmt.Errorf("unable to reliably send %v "+
+					"for channel=%v to peer=%x: %v",
+					msg.MsgType(), msg.ShortChannelID,
+					remotePubKey, err)
+				nMsg.err <- err
+				return nil, false
+			}
+		}
 
-	timestamp := time.Unix(int64(nodeAnn.Timestamp), 0)
+		// Channel update announcement was successfully processed and
+		// now it can be broadcast to the rest of the network. However,
+		// we'll only broadcast the channel update announcement if it
+		// has an attached authentication proof.
+		if chanInfo.AuthProof != nil {
+			announcements = append(announcements, networkMsg{
+				peer:   nMsg.peer,
+				source: nMsg.source,
+				msg:    msg,
+			})
+		}
 
-	// We'll quickly ask the router if it already has a newer update for
-	// this node so we can skip validating signatures if not required.
-	if d.cfg.Router.IsStaleNode(nodeAnn.NodeID, timestamp) {
-		log.Debugf("Skipped processing stale node: %x", nodeAnn.NodeID)
 		nMsg.err <- nil
-		return nil, true
-	}
-
-	if err := d.addNode(nodeAnn, ops...); err != nil {
-		log.Debugf("Adding node: %x got error: %v", nodeAnn.NodeID,
-			err)
+		return announcements, true
 
-		if !routing.IsError(
-			err,
-			routing.ErrOutdated,
-			routing.ErrIgnored,
-			routing.ErrVBarrierShuttingDown,
-		) {
+	// A new signature announcement has been received. This indicates
+	// willingness of nodes involved in the funding of a channel to
+	// announce this new channel to the rest of the world.
+	case *lnwire.AnnounceSignatures:
+		needBlockHeight := msg.ShortChannelID.BlockHeight +
+			d.cfg.ProofMatureDelta
+		shortChanID := msg.ShortChannelID.ToUint64()
 
-			log.Error(err)
+		prefix := "local"
+		if nMsg.isRemote {
+			prefix = "remote"
 		}
 
-		nMsg.err <- err
-		return nil, false
-	}
-
-	// In order to ensure we don't leak unadvertised nodes, we'll make a
-	// quick check to ensure this node intends to publicly advertise itself
-	// to the network.
-	isPublic, err := d.cfg.Router.IsPublicNode(nodeAnn.NodeID)
-	if err != nil {
-		log.Errorf("Unable to determine if node %x is advertised: %v",
-			nodeAnn.NodeID, err)
-		nMsg.err <- err
-		return nil, false
-	}
-
-	var announcements []networkMsg
-
-	// If it does, we'll add their announcement to our batch so that it can
-	// be broadcast to the rest of our peers.
-	if isPublic {
-		announcements = append(announcements, networkMsg{
-			peer:   nMsg.peer,
-			source: nMsg.source,
-			msg:    nodeAnn,
-		})
-	} else {
-		log.Tracef("Skipping broadcasting node announcement for %x "+
-			"due to being unadvertised", nodeAnn.NodeID)
-	}
-
-	nMsg.err <- nil
-	// TODO(roasbeef): get rid of the above
-	return announcements, true
-}
+		log.Infof("Received new %v channel announcement for %v", prefix,
+			msg.ShortChannelID)
 
-// handleChanAnnouncement processes a new channel announcement.
-func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
-	ann *lnwire.ChannelAnnouncement,
-	ops []batch.SchedulerOption) ([]networkMsg, bool) {
-
-	// We'll ignore any channel announcements that target any chain other
-	// than the set of chains we know of.
-	if !bytes.Equal(ann.ChainHash[:], d.cfg.ChainHash[:]) {
-		err := fmt.Errorf("ignoring ChannelAnnouncement from chain=%v"+
-			", gossiper on chain=%v", ann.ChainHash,
-			d.cfg.ChainHash)
-		log.Errorf(err.Error())
-
-		key := newRejectCacheKey(
-			ann.ShortChannelID.ToUint64(),
-			sourceToPub(nMsg.source),
+		// By the specification, channel announcement proofs should be
+		// sent after some number of confirmations after channel was
+		// registered in bitcoin blockchain. Therefore, we check if the
+		// proof is premature.
+		d.Lock()
+		premature := d.isPremature(
+			msg.ShortChannelID, d.cfg.ProofMatureDelta, nMsg,
 		)
-		_, _ = d.recentRejects.Put(key, &cachedReject{})
-
-		nMsg.err <- err
-		return nil, false
-	}
-
-	// If the advertised inclusionary block is beyond our knowledge of the
-	// chain tip, then we'll ignore it for now.
-	d.Lock()
-	if nMsg.isRemote && d.isPremature(ann.ShortChannelID, 0, nMsg) {
-		log.Warnf("Announcement for chan_id=(%v), is premature: "+
-			"advertises height %v, only height %v is known",
-			ann.ShortChannelID.ToUint64(),
-			ann.ShortChannelID.BlockHeight, d.bestHeight)
+		if premature {
+			log.Warnf("Premature proof announcement, current "+
+				"block height lower than needed: %v < %v",
+				d.bestHeight, needBlockHeight)
+			d.Unlock()
+			nMsg.err <- nil
+			return nil, false
+		}
 		d.Unlock()
-		nMsg.err <- nil
-		return nil, false
-	}
-	d.Unlock()
 
-	// At this point, we'll now ask the router if this is a zombie/known
-	// edge. If so we can skip all the processing below.
-	if d.cfg.Router.IsKnownEdge(ann.ShortChannelID) {
-		nMsg.err <- nil
-		return nil, true
-	}
+		// Ensure that we know of a channel with the target channel ID
+		// before proceeding further.
+		//
+		// We must acquire the mutex for this channel ID before getting
+		// the channel from the database, to ensure what we read does
+		// not change before we call AddProof() later.
+		d.channelMtx.Lock(msg.ShortChannelID.ToUint64())
+		defer d.channelMtx.Unlock(msg.ShortChannelID.ToUint64())
+
+		chanInfo, e1, e2, err := d.cfg.Router.GetChannelByID(
+			msg.ShortChannelID)
+		if err != nil {
+			// TODO(andrew.shvv) this is dangerous because remote
+			// node might rewrite the waiting proof.
+			proof := channeldb.NewWaitingProof(nMsg.isRemote, msg)
+			err := d.cfg.WaitingProofStore.Add(proof)
+			if err != nil {
+				err := fmt.Errorf("unable to store "+
+					"the proof for short_chan_id=%v: %v",
+					shortChanID, err)
+				log.Error(err)
+				nMsg.err <- err
+				return nil, false
+			}
 
-	// If this is a remote channel announcement, then we'll validate all
-	// the signatures within the proof as it should be well formed.
-	var proof *channeldb.ChannelAuthProof
-	if nMsg.isRemote {
-		if err := routing.ValidateChannelAnn(ann); err != nil {
-			err := fmt.Errorf("unable to validate announcement: "+
-				"%v", err)
+			log.Infof("Orphan %v proof announcement with "+
+				"short_chan_id=%v, adding "+
+				"to waiting batch", prefix, shortChanID)
+			nMsg.err <- nil
+			return nil, false
+		}
 
-			key := newRejectCacheKey(
-				ann.ShortChannelID.ToUint64(),
-				sourceToPub(nMsg.source),
-			)
-			_, _ = d.recentRejects.Put(key, &cachedReject{})
+		nodeID := nMsg.source.SerializeCompressed()
+		isFirstNode := bytes.Equal(nodeID, chanInfo.NodeKey1Bytes[:])
+		isSecondNode := bytes.Equal(nodeID, chanInfo.NodeKey2Bytes[:])
 
+		// Ensure that channel that was retrieved belongs to the peer
+		// which sent the proof announcement.
+		if !(isFirstNode || isSecondNode) {
+			err := fmt.Errorf("channel that was received not "+
+				"belongs to the peer which sent the proof, "+
+				"short_chan_id=%v", shortChanID)
 			log.Error(err)
 			nMsg.err <- err
 			return nil, false
 		}
 
-		// If the proof checks out, then we'll save the proof itself to
-		// the database so we can fetch it later when gossiping with
-		// other nodes.
-		proof = &channeldb.ChannelAuthProof{
-			NodeSig1Bytes:    ann.NodeSig1.ToSignatureBytes(),
-			NodeSig2Bytes:    ann.NodeSig2.ToSignatureBytes(),
-			BitcoinSig1Bytes: ann.BitcoinSig1.ToSignatureBytes(),
-			BitcoinSig2Bytes: ann.BitcoinSig2.ToSignatureBytes(),
+		// If proof was sent by a local sub-system, then we'll
+		// send the announcement signature to the remote node
+		// so they can also reconstruct the full channel
+		// announcement.
+		if !nMsg.isRemote {
+			var remotePubKey [33]byte
+			if isFirstNode {
+				remotePubKey = chanInfo.NodeKey2Bytes
+			} else {
+				remotePubKey = chanInfo.NodeKey1Bytes
+			}
+			// Since the remote peer might not be online
+			// we'll call a method that will attempt to
+			// deliver the proof when it comes online.
+			err := d.reliableSender.sendMessage(msg, remotePubKey)
+			if err != nil {
+				err := fmt.Errorf("unable to reliably send %v "+
+					"for channel=%v to peer=%x: %v",
+					msg.MsgType(), msg.ShortChannelID,
+					remotePubKey, err)
+				nMsg.err <- err
+				return nil, false
+			}
 		}
-	}
-
-	// With the proof validated (if necessary), we can now store it within
-	// the database for our path finding and syncing needs.
-	var featureBuf bytes.Buffer
-	if err := ann.Features.Encode(&featureBuf); err != nil {
-		log.Errorf("unable to encode features: %v", err)
-		nMsg.err <- err
-		return nil, false
-	}
 
-	edge := &channeldb.ChannelEdgeInfo{
-		ChannelID:        ann.ShortChannelID.ToUint64(),
-		ChainHash:        ann.ChainHash,
-		NodeKey1Bytes:    ann.NodeID1,
-		NodeKey2Bytes:    ann.NodeID2,
-		BitcoinKey1Bytes: ann.BitcoinKey1,
-		BitcoinKey2Bytes: ann.BitcoinKey2,
-		AuthProof:        proof,
-		Features:         featureBuf.Bytes(),
-		ExtraOpaqueData:  ann.ExtraOpaqueData,
-	}
+		// Check if we already have the full proof for this channel.
+		if chanInfo.AuthProof != nil {
+			// If we already have the fully assembled proof, then
+			// the peer sending us their proof has probably not
+			// received our local proof yet. So be kind and send
+			// them the full proof.
+			if nMsg.isRemote {
+				peerID := nMsg.source.SerializeCompressed()
+				log.Debugf("Got AnnounceSignatures for " +
+					"channel with full proof.")
+
+				d.wg.Add(1)
+				go func() {
+					defer d.wg.Done()
+					log.Debugf("Received half proof for "+
+						"channel %v with existing "+
+						"full proof. Sending full "+
+						"proof to peer=%x",
+						msg.ChannelID,
+						peerID)
+
+					chanAnn, _, _, err := netann.CreateChanAnnouncement(
+						chanInfo.AuthProof, chanInfo,
+						e1, e2,
+					)
+					if err != nil {
+						log.Errorf("unable to gen "+
+							"ann: %v", err)
+						return
+					}
+					err = nMsg.peer.SendMessage(
+						false, chanAnn,
+					)
+					if err != nil {
+						log.Errorf("Failed sending "+
+							"full proof to "+
+							"peer=%x: %v",
+							peerID, err)
+						return
+					}
+					log.Debugf("Full proof sent to peer=%x"+
+						" for chanID=%v", peerID,
+						msg.ChannelID)
+				}()
+			}
 
-	// If there were any optional message fields provided, we'll include
-	// them in its serialized disk representation now.
-	if nMsg.optionalMsgFields != nil {
-		if nMsg.optionalMsgFields.capacity != nil {
-			edge.Capacity = *nMsg.optionalMsgFields.capacity
-		}
-		if nMsg.optionalMsgFields.channelPoint != nil {
-			cp := *nMsg.optionalMsgFields.channelPoint
-			edge.ChannelPoint = cp
+			log.Debugf("Already have proof for channel "+
+				"with chanID=%v", msg.ChannelID)
+			nMsg.err <- nil
+			return nil, true
 		}
-	}
 
-	// We will add the edge to the channel router. If the nodes present in
-	// this channel are not present in the database, a partial node will be
-	// added to represent each node while we wait for a node announcement.
-	//
-	// Before we add the edge to the database, we obtain the mutex for this
-	// channel ID. We do this to ensure no other goroutine has read the
-	// database and is now making decisions based on this DB state, before
-	// it writes to the DB.
-	d.channelMtx.Lock(ann.ShortChannelID.ToUint64())
-	err := d.cfg.Router.AddEdge(edge, ops...)
-	if err != nil {
-		defer d.channelMtx.Unlock(ann.ShortChannelID.ToUint64())
-
-		// If the edge was rejected due to already being known, then it
-		// may be the case that this new message has a fresh channel
-		// proof, so we'll check.
-		if routing.IsError(err, routing.ErrIgnored) {
-			// Attempt to process the rejected message to see if we
-			// get any new announcements.
-			anns, rErr := d.processRejectedEdge(ann, proof)
-			if rErr != nil {
-				key := newRejectCacheKey(
-					ann.ShortChannelID.ToUint64(),
-					sourceToPub(nMsg.source),
-				)
-				cr := &cachedReject{}
-				_, _ = d.recentRejects.Put(key, cr)
+		// Check that we received the opposite proof. If so, then we're
+		// now able to construct the full proof, and create the channel
+		// announcement. If we didn't receive the opposite half of the
+		// proof than we should store it this one, and wait for
+		// opposite to be received.
+		proof := channeldb.NewWaitingProof(nMsg.isRemote, msg)
+		oppositeProof, err := d.cfg.WaitingProofStore.Get(
+			proof.OppositeKey(),
+		)
+		if err != nil && err != channeldb.ErrWaitingProofNotFound {
+			err := fmt.Errorf("unable to get "+
+				"the opposite proof for short_chan_id=%v: %v",
+				shortChanID, err)
+			log.Error(err)
+			nMsg.err <- err
+			return nil, false
+		}
 
-				nMsg.err <- rErr
+		if err == channeldb.ErrWaitingProofNotFound {
+			err := d.cfg.WaitingProofStore.Add(proof)
+			if err != nil {
+				err := fmt.Errorf("unable to store "+
+					"the proof for short_chan_id=%v: %v",
+					shortChanID, err)
+				log.Error(err)
+				nMsg.err <- err
 				return nil, false
 			}
 
-			// If while processing this rejected edge, we realized
-			// there's a set of announcements we could extract,
-			// then we'll return those directly.
-			if len(anns) != 0 {
-				nMsg.err <- nil
-				return anns, true
-			}
-
-			// Otherwise, this is just a regular rejected edge.
-			log.Debugf("Router rejected channel edge: %v", err)
-		} else {
-			log.Debugf("Router rejected channel edge: %v", err)
+			log.Infof("1/2 of channel ann proof received for "+
+				"short_chan_id=%v, waiting for other half",
+				shortChanID)
 
-			key := newRejectCacheKey(
-				ann.ShortChannelID.ToUint64(),
-				sourceToPub(nMsg.source),
-			)
-			_, _ = d.recentRejects.Put(key, &cachedReject{})
+			nMsg.err <- nil
+			return nil, false
 		}
 
-		nMsg.err <- err
-		return nil, false
-	}
+		// We now have both halves of the channel announcement proof,
+		// then we'll reconstruct the initial announcement so we can
+		// validate it shortly below.
+		var dbProof channeldb.ChannelAuthProof
+		if isFirstNode {
+			dbProof.NodeSig1Bytes = msg.NodeSignature.ToSignatureBytes()
+			dbProof.NodeSig2Bytes = oppositeProof.NodeSignature.ToSignatureBytes()
+			dbProof.BitcoinSig1Bytes = msg.BitcoinSignature.ToSignatureBytes()
+			dbProof.BitcoinSig2Bytes = oppositeProof.BitcoinSignature.ToSignatureBytes()
+		} else {
+			dbProof.NodeSig1Bytes = oppositeProof.NodeSignature.ToSignatureBytes()
+			dbProof.NodeSig2Bytes = msg.NodeSignature.ToSignatureBytes()
+			dbProof.BitcoinSig1Bytes = oppositeProof.BitcoinSignature.ToSignatureBytes()
+			dbProof.BitcoinSig2Bytes = msg.BitcoinSignature.ToSignatureBytes()
+		}
+		chanAnn, e1Ann, e2Ann, err := netann.CreateChanAnnouncement(
+			&dbProof, chanInfo, e1, e2,
+		)
+		if err != nil {
+			log.Error(err)
+			nMsg.err <- err
+			return nil, false
+		}
 
-	// If err is nil, release the lock immediately.
-	d.channelMtx.Unlock(ann.ShortChannelID.ToUint64())
+		// With all the necessary components assembled validate the
+		// full channel announcement proof.
+		if err := routing.ValidateChannelAnn(chanAnn); err != nil {
+			err := fmt.Errorf("channel  announcement proof "+
+				"for short_chan_id=%v isn't valid: %v",
+				shortChanID, err)
 
-	// If we earlier received any ChannelUpdates for this channel, we can
-	// now process them, as the channel is added to the graph.
-	shortChanID := ann.ShortChannelID.ToUint64()
-	var channelUpdates []*networkMsg
+			log.Error(err)
+			nMsg.err <- err
+			return nil, false
+		}
 
-	earlyChanUpdates, err := d.prematureChannelUpdates.Get(shortChanID)
-	if err == nil {
-		// There was actually an entry in the map, so we'll accumulate
-		// it. We don't worry about deletion, since it'll eventually
-		// fall out anyway.
-		chanMsgs := earlyChanUpdates.(*cachedNetworkMsg)
-		channelUpdates = append(channelUpdates, chanMsgs.msgs...)
-	}
-
-	// Launch a new goroutine to handle each ChannelUpdate, this is to
-	// ensure we don't block here, as we can handle only one announcement
-	// at a time.
-	for _, cu := range channelUpdates {
-		d.wg.Add(1)
-		go func(updMsg *networkMsg) {
-			defer d.wg.Done()
-
-			switch msg := updMsg.msg.(type) {
-			// Reprocess the message, making sure we return an
-			// error to the original caller in case the gossiper
-			// shuts down.
-			case *lnwire.ChannelUpdate:
-				log.Debugf("Reprocessing ChannelUpdate for "+
-					"shortChanID=%v",
-					msg.ShortChannelID.ToUint64())
-
-				select {
-				case d.networkMsgs <- updMsg:
-				case <-d.quit:
-					updMsg.err <- ErrGossiperShuttingDown
-				}
+		// If the channel was returned by the router it means that
+		// existence of funding point and inclusion of nodes bitcoin
+		// keys in it already checked by the router. In this stage we
+		// should check that node keys are attest to the bitcoin keys
+		// by validating the signatures of announcement.  If proof is
+		// valid then we'll populate the channel edge with it, so we
+		// can announce it on peer connect.
+		err = d.cfg.Router.AddProof(msg.ShortChannelID, &dbProof)
+		if err != nil {
+			err := fmt.Errorf("unable add proof to the "+
+				"channel chanID=%v: %v", msg.ChannelID, err)
+			log.Error(err)
+			nMsg.err <- err
+			return nil, false
+		}
 
-			// We don't expect any other message type than
-			// ChannelUpdate to be in this cache.
-			default:
-				log.Errorf("Unsupported message type found "+
-					"among ChannelUpdates: %T", msg)
-			}
-		}(cu)
-	}
+		err = d.cfg.WaitingProofStore.Remove(proof.OppositeKey())
+		if err != nil {
+			err := fmt.Errorf("unable remove opposite proof "+
+				"for the channel with chanID=%v: %v",
+				msg.ChannelID, err)
+			log.Error(err)
+			nMsg.err <- err
+			return nil, false
+		}
 
-	// Channel announcement was successfully processed and now it might be
-	// broadcast to other connected nodes if it was an announcement with
-	// proof (remote).
-	var announcements []networkMsg
+		// Proof was successfully created and now can announce the
+		// channel to the remain network.
+		log.Infof("Fully valid channel proof for short_chan_id=%v "+
+			"constructed, adding to next ann batch",
+			shortChanID)
 
-	if proof != nil {
+		// Assemble the necessary announcements to add to the next
+		// broadcasting batch.
 		announcements = append(announcements, networkMsg{
 			peer:   nMsg.peer,
 			source: nMsg.source,
-			msg:    ann,
+			msg:    chanAnn,
 		})
-	}
-
-	nMsg.err <- nil
-	return announcements, true
-}
-
-// handleChanUpdate processes a new channel update.
-func (d *AuthenticatedGossiper) handleChanUpdate(nMsg *networkMsg,
-	upd *lnwire.ChannelUpdate,
-	ops []batch.SchedulerOption) ([]networkMsg, bool) {
-
-	// We'll ignore any channel updates that target any chain other than
-	// the set of chains we know of.
-	if !bytes.Equal(upd.ChainHash[:], d.cfg.ChainHash[:]) {
-		err := fmt.Errorf("ignoring ChannelUpdate from chain=%v, "+
-			"gossiper on chain=%v", upd.ChainHash, d.cfg.ChainHash)
-		log.Errorf(err.Error())
-
-		key := newRejectCacheKey(
-			upd.ShortChannelID.ToUint64(),
-			sourceToPub(nMsg.source),
-		)
-		_, _ = d.recentRejects.Put(key, &cachedReject{})
-
-		nMsg.err <- err
-		return nil, false
-	}
-
-	blockHeight := upd.ShortChannelID.BlockHeight
-	shortChanID := upd.ShortChannelID.ToUint64()
-
-	// If the advertised inclusionary block is beyond our knowledge of the
-	// chain tip, then we'll put the announcement in limbo to be fully
-	// verified once we advance forward in the chain.
-	d.Lock()
-	if nMsg.isRemote && d.isPremature(upd.ShortChannelID, 0, nMsg) {
-		log.Warnf("Update announcement for short_chan_id(%v), is "+
-			"premature: advertises height %v, only height %v is "+
-			"known", shortChanID, blockHeight, d.bestHeight)
-		d.Unlock()
-		nMsg.err <- nil
-		return nil, false
-	}
-	d.Unlock()
-
-	// Before we perform any of the expensive checks below, we'll check
-	// whether this update is stale or is for a zombie channel in order to
-	// quickly reject it.
-	timestamp := time.Unix(int64(upd.Timestamp), 0)
-	if d.cfg.Router.IsStaleEdgePolicy(
-		upd.ShortChannelID, timestamp, upd.ChannelFlags,
-	) {
-
-		log.Debugf("Ignored stale edge policy: peer=%v, source=%x, "+
-			"msg=%s, is_remote=%v", nMsg.peer,
-			nMsg.source.SerializeCompressed(), nMsg.msg.MsgType(),
-			nMsg.isRemote,
-		)
-
-		nMsg.err <- nil
-		return nil, true
-	}
-
-	// Get the node pub key as far since we don't have it in the channel
-	// update announcement message. We'll need this to properly verify the
-	// message's signature.
-	//
-	// We make sure to obtain the mutex for this channel ID before we
-	// access the database. This ensures the state we read from the
-	// database has not changed between this point and when we call
-	// UpdateEdge() later.
-	d.channelMtx.Lock(upd.ShortChannelID.ToUint64())
-	defer d.channelMtx.Unlock(upd.ShortChannelID.ToUint64())
-	chanInfo, e1, e2, err := d.cfg.Router.GetChannelByID(
-		upd.ShortChannelID,
-	)
-	switch err {
-	// No error, break.
-	case nil:
-		break
-
-	case channeldb.ErrZombieEdge:
-		err = d.processZombieUpdate(chanInfo, upd)
-		if err != nil {
-			log.Debug(err)
-			nMsg.err <- err
-			return nil, false
+		if src, err := chanInfo.NodeKey1(); err == nil && e1Ann != nil {
+			announcements = append(announcements, networkMsg{
+				peer:   nMsg.peer,
+				source: src,
+				msg:    e1Ann,
+			})
+		}
+		if src, err := chanInfo.NodeKey2(); err == nil && e2Ann != nil {
+			announcements = append(announcements, networkMsg{
+				peer:   nMsg.peer,
+				source: src,
+				msg:    e2Ann,
+			})
 		}
 
-		// We'll fallthrough to ensure we stash the update until we
-		// receive its corresponding ChannelAnnouncement. This is
-		// needed to ensure the edge exists in the graph before
-		// applying the update.
-		fallthrough
-	case channeldb.ErrGraphNotFound:
-		fallthrough
-	case channeldb.ErrGraphNoEdgesFound:
-		fallthrough
-	case channeldb.ErrEdgeNotFound:
-		// If the edge corresponding to this ChannelUpdate was not
-		// found in the graph, this might be a channel in the process
-		// of being opened, and we haven't processed our own
-		// ChannelAnnouncement yet, hence it is not not found in the
-		// graph. This usually gets resolved after the channel proofs
-		// are exchanged and the channel is broadcasted to the rest of
-		// the network, but in case this is a private channel this
-		// won't ever happen. This can also happen in the case of a
-		// zombie channel with a fresh update for which we don't have a
-		// ChannelAnnouncement for since we reject them. Because of
-		// this, we temporarily add it to a map, and reprocess it after
-		// our own ChannelAnnouncement has been processed.
-		earlyMsgs, err := d.prematureChannelUpdates.Get(shortChanID)
-		switch {
-		// Nothing in the cache yet, we can just directly insert this
-		// element.
-		case err == cache.ErrElementNotFound:
-			_, _ = d.prematureChannelUpdates.Put(
-				shortChanID, &cachedNetworkMsg{
-					msgs: []*networkMsg{nMsg},
+		// We'll also send along the node announcements for each channel
+		// participant if we know of them. To ensure our node
+		// announcement propagates to our channel counterparty, we'll
+		// set the source for each announcement to the node it belongs
+		// to, otherwise we won't send it since the source gets skipped.
+		// This isn't necessary for channel updates and announcement
+		// signatures since we send those directly to our channel
+		// counterparty through the gossiper's reliable sender.
+		node1Ann, err := d.fetchNodeAnn(chanInfo.NodeKey1Bytes)
+		if err != nil {
+			log.Debugf("Unable to fetch node announcement for "+
+				"%x: %v", chanInfo.NodeKey1Bytes, err)
+		} else {
+			if nodeKey1, err := chanInfo.NodeKey1(); err == nil {
+				announcements = append(announcements, networkMsg{
+					peer:   nMsg.peer,
+					source: nodeKey1,
+					msg:    node1Ann,
 				})
-
-		// There's already something in the cache, so we'll combine the
-		// set of messages into a single value.
-		default:
-			msgs := earlyMsgs.(*cachedNetworkMsg).msgs
-			msgs = append(msgs, nMsg)
-			_, _ = d.prematureChannelUpdates.Put(
-				shortChanID, &cachedNetworkMsg{
-					msgs: msgs,
+			}
+		}
+		node2Ann, err := d.fetchNodeAnn(chanInfo.NodeKey2Bytes)
+		if err != nil {
+			log.Debugf("Unable to fetch node announcement for "+
+				"%x: %v", chanInfo.NodeKey2Bytes, err)
+		} else {
+			if nodeKey2, err := chanInfo.NodeKey2(); err == nil {
+				announcements = append(announcements, networkMsg{
+					peer:   nMsg.peer,
+					source: nodeKey2,
+					msg:    node2Ann,
 				})
+			}
 		}
 
-		log.Debugf("Got ChannelUpdate for edge not found in graph"+
-			"(shortChanID=%v), saving for reprocessing later",
-			shortChanID)
-
-		// 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.
-		return nil, false
+		nMsg.err <- nil
+		return announcements, true
 
 	default:
-		err := fmt.Errorf("unable to validate channel update "+
-			"short_chan_id=%v: %v", shortChanID, err)
-		log.Error(err)
+		err := errors.New("wrong type of the announcement")
 		nMsg.err <- err
-
-		key := newRejectCacheKey(
-			upd.ShortChannelID.ToUint64(),
-			sourceToPub(nMsg.source),
-		)
-		_, _ = d.recentRejects.Put(key, &cachedReject{})
-
 		return nil, false
 	}
+}
 
-	// The least-significant bit in the flag on the channel update
-	// announcement tells us "which" side of the channels directed edge is
-	// being updated.
-	var (
-		pubKey       *btcec.PublicKey
-		edgeToUpdate *channeldb.ChannelEdgePolicy
-	)
-	direction := upd.ChannelFlags & lnwire.ChanUpdateDirection
-	switch direction {
-	case 0:
+// processZombieUpdate determines whether the provided channel update should
+// resurrect a given zombie edge.
+func (d *AuthenticatedGossiper) processZombieUpdate(
+	chanInfo *channeldb.ChannelEdgeInfo, msg *lnwire.ChannelUpdate) error {
+
+	// The least-significant bit in the flag on the channel update tells us
+	// which edge is being updated.
+	isNode1 := msg.ChannelFlags&lnwire.ChanUpdateDirection == 0
+
+	// Since we've deemed the update as not stale above, before marking it
+	// live, we'll make sure it has been signed by the correct party. If we
+	// have both pubkeys, either party can resurect the channel. If we've
+	// already marked this with the stricter, single-sided resurrection we
+	// will only have the pubkey of the node with the oldest timestamp.
+	var pubKey *btcec.PublicKey
+	switch {
+	case isNode1 && chanInfo.NodeKey1Bytes != emptyPubkey:
 		pubKey, _ = chanInfo.NodeKey1()
-		edgeToUpdate = e1
-	case 1:
+	case !isNode1 && chanInfo.NodeKey2Bytes != emptyPubkey:
 		pubKey, _ = chanInfo.NodeKey2()
-		edgeToUpdate = e2
+	}
+	if pubKey == nil {
+		return fmt.Errorf("incorrect pubkey to resurrect zombie "+
+			"with chan_id=%v", msg.ShortChannelID)
 	}
 
-	// Validate the channel announcement with the expected public key and
-	// channel capacity. In the case of an invalid channel update, we'll
-	// return an error to the caller and exit early.
-	err = routing.ValidateChannelUpdateAnn(pubKey, chanInfo.Capacity, upd)
+	err := routing.VerifyChannelUpdateSignature(msg, pubKey)
 	if err != nil {
-		rErr := fmt.Errorf("unable to validate channel update "+
-			"announcement for short_chan_id=%v: %v",
-			spew.Sdump(upd.ShortChannelID), err)
+		return fmt.Errorf("unable to verify channel "+
+			"update signature: %v", err)
+	}
 
-		log.Error(rErr)
-		nMsg.err <- rErr
-		return nil, false
+	// With the signature valid, we'll proceed to mark the
+	// edge as live and wait for the channel announcement to
+	// come through again.
+	err = d.cfg.Router.MarkEdgeLive(msg.ShortChannelID)
+	if err != nil {
+		return fmt.Errorf("unable to remove edge with "+
+			"chan_id=%v from zombie index: %v",
+			msg.ShortChannelID, err)
 	}
 
-	// If we have a previous version of the edge being updated, we'll want
-	// to rate limit its updates to prevent spam throughout the network.
-	if nMsg.isRemote && edgeToUpdate != nil {
-		// If it's a keep-alive update, we'll only propagate one if
-		// it's been a day since the previous. This follows our own
-		// heuristic of sending keep-alive updates after the same
-		// duration (see retransmitStaleAnns).
-		timeSinceLastUpdate := timestamp.Sub(edgeToUpdate.LastUpdate)
-		if IsKeepAliveUpdate(upd, edgeToUpdate) {
-			if timeSinceLastUpdate < d.cfg.RebroadcastInterval {
-				log.Debugf("Ignoring keep alive update not "+
-					"within %v period for channel %v",
-					d.cfg.RebroadcastInterval, shortChanID)
-				nMsg.err <- nil
-				return nil, false
-			}
-		} else {
-			// If it's not, we'll allow an update per minute with a
-			// maximum burst of 10. If we haven't seen an update
-			// for this channel before, we'll need to initialize a
-			// rate limiter for each direction.
-			d.Lock()
-			rls, ok := d.chanUpdateRateLimiter[shortChanID]
-			if !ok {
-				r := rate.Every(d.cfg.ChannelUpdateInterval)
-				b := d.cfg.MaxChannelUpdateBurst
-				rls = [2]*rate.Limiter{
-					rate.NewLimiter(r, b),
-					rate.NewLimiter(r, b),
-				}
-				d.chanUpdateRateLimiter[shortChanID] = rls
-			}
-			d.Unlock()
+	log.Debugf("Removed edge with chan_id=%v from zombie "+
+		"index", msg.ShortChannelID)
 
-			if !rls[direction].Allow() {
-				log.Debugf("Rate limiting update for channel "+
-					"%v from direction %x", shortChanID,
-					pubKey.SerializeCompressed())
-				nMsg.err <- nil
-				return nil, false
-			}
-		}
-	}
+	return nil
+}
 
-	update := &channeldb.ChannelEdgePolicy{
-		SigBytes:                  upd.Signature.ToSignatureBytes(),
-		ChannelID:                 shortChanID,
-		LastUpdate:                timestamp,
-		MessageFlags:              upd.MessageFlags,
-		ChannelFlags:              upd.ChannelFlags,
-		TimeLockDelta:             upd.TimeLockDelta,
-		MinHTLC:                   upd.HtlcMinimumMsat,
-		MaxHTLC:                   upd.HtlcMaximumMsat,
-		FeeBaseMSat:               lnwire.MilliSatoshi(upd.BaseFee),
-		FeeProportionalMillionths: lnwire.MilliSatoshi(upd.FeeRate),
-		ExtraOpaqueData:           upd.ExtraOpaqueData,
+// fetchNodeAnn fetches the latest signed node announcement from our point of
+// view for the node with the given public key.
+func (d *AuthenticatedGossiper) fetchNodeAnn(
+	pubKey [33]byte) (*lnwire.NodeAnnouncement, error) {
+
+	node, err := d.cfg.Router.FetchLightningNode(pubKey)
+	if err != nil {
+		return nil, err
 	}
 
-	if err := d.cfg.Router.UpdateEdge(update, ops...); err != nil {
-		if routing.IsError(
-			err, routing.ErrOutdated,
-			routing.ErrIgnored,
-			routing.ErrVBarrierShuttingDown,
-		) {
+	return node.NodeAnnouncement(true)
+}
 
-			log.Debug(err)
-		} else {
-			key := newRejectCacheKey(
-				upd.ShortChannelID.ToUint64(),
-				sourceToPub(nMsg.source),
-			)
-			_, _ = d.recentRejects.Put(key, &cachedReject{})
+// isMsgStale determines whether a message retrieved from the backing
+// MessageStore is seen as stale by the current graph.
+func (d *AuthenticatedGossiper) isMsgStale(msg lnwire.Message) bool {
+	switch msg := msg.(type) {
+	case *lnwire.AnnounceSignatures:
+		chanInfo, _, _, err := d.cfg.Router.GetChannelByID(
+			msg.ShortChannelID,
+		)
 
-			log.Error(err)
+		// If the channel cannot be found, it is most likely a leftover
+		// message for a channel that was closed, so we can consider it
+		// stale.
+		if err == channeldb.ErrEdgeNotFound {
+			return true
+		}
+		if err != nil {
+			log.Debugf("Unable to retrieve channel=%v from graph: "+
+				"%v", err)
+			return false
 		}
 
-		nMsg.err <- err
-		return nil, false
-	}
-
-	// If this is a local ChannelUpdate without an AuthProof, it means it
-	// is an update to a channel that is not (yet) supposed to be announced
-	// to the greater network. However, our channel counter party will need
-	// to be given the update, so we'll try sending the update directly to
-	// the remote peer.
-	if !nMsg.isRemote && chanInfo.AuthProof == nil {
-		// Get our peer's public key.
-		remotePubKey := remotePubFromChanInfo(
-			chanInfo, upd.ChannelFlags,
-		)
+		// If the proof exists in the graph, then we have successfully
+		// received the remote proof and assembled the full proof, so we
+		// can safely delete the local proof from the database.
+		return chanInfo.AuthProof != nil
 
-		log.Debugf("The message %v has no AuthProof, sending the "+
-			"update to remote peer %x", upd.MsgType(),
-			remotePubKey)
+	case *lnwire.ChannelUpdate:
+		_, p1, p2, err := d.cfg.Router.GetChannelByID(msg.ShortChannelID)
 
-		// Now we'll attempt to send the channel update message
-		// reliably to the remote peer in the background, so that we
-		// don't block if the peer happens to be offline at the moment.
-		err := d.reliableSender.sendMessage(upd, remotePubKey)
+		// If the channel cannot be found, it is most likely a leftover
+		// message for a channel that was closed, so we can consider it
+		// stale.
+		if err == channeldb.ErrEdgeNotFound {
+			return true
+		}
 		if err != nil {
-			err := fmt.Errorf("unable to reliably send %v for "+
-				"channel=%v to peer=%x: %v", upd.MsgType(),
-				upd.ShortChannelID, remotePubKey, err)
-			nMsg.err <- err
-			return nil, false
+			log.Debugf("Unable to retrieve channel=%v from graph: "+
+				"%v", msg.ShortChannelID, err)
+			return false
 		}
-	}
 
-	// Channel update announcement was successfully processed and now it
-	// can be broadcast to the rest of the network. However, we'll only
-	// broadcast the channel update announcement if it has an attached
-	// authentication proof.
-	var announcements []networkMsg
-	if chanInfo.AuthProof != nil {
-		announcements = append(announcements, networkMsg{
-			peer:   nMsg.peer,
-			source: nMsg.source,
-			msg:    upd,
-		})
-	}
-
-	nMsg.err <- nil
-	return announcements, true
-}
+		// Otherwise, we'll retrieve the correct policy that we
+		// currently have stored within our graph to check if this
+		// message is stale by comparing its timestamp.
+		var p *channeldb.ChannelEdgePolicy
+		if msg.ChannelFlags&lnwire.ChanUpdateDirection == 0 {
+			p = p1
+		} else {
+			p = p2
+		}
 
-// handleAnnSig processes a new announcement signatures message.
-func (d *AuthenticatedGossiper) handleAnnSig(nMsg *networkMsg,
-	ann *lnwire.AnnounceSignatures) ([]networkMsg, bool) {
+		// If the policy is still unknown, then we can consider this
+		// policy fresh.
+		if p == nil {
+			return false
+		}
 
-	needBlockHeight := ann.ShortChannelID.BlockHeight +
-		d.cfg.ProofMatureDelta
-	shortChanID := ann.ShortChannelID.ToUint64()
+		timestamp := time.Unix(int64(msg.Timestamp), 0)
+		return p.LastUpdate.After(timestamp)
 
-	prefix := "local"
-	if nMsg.isRemote {
-		prefix = "remote"
+	default:
+		// We'll make sure to not mark any unsupported messages as stale
+		// to ensure they are not removed.
+		return false
 	}
+}
 
-	log.Infof("Received new %v channel announcement for %v", prefix,
-		ann.ShortChannelID)
+// updateChannel creates a new fully signed update for the channel, and updates
+// the underlying graph with the new state.
+func (d *AuthenticatedGossiper) updateChannel(info *channeldb.ChannelEdgeInfo,
+	edge *channeldb.ChannelEdgePolicy) (*lnwire.ChannelAnnouncement,
+	*lnwire.ChannelUpdate, error) {
 
-	// By the specification, channel announcement proofs should be sent
-	// after some number of confirmations after channel was registered in
-	// bitcoin blockchain. Therefore, we check if the proof is mature.
-	d.Lock()
-	premature := d.isPremature(
-		ann.ShortChannelID, d.cfg.ProofMatureDelta, nMsg,
+	// Parse the unsigned edge into a channel update.
+	chanUpdate := netann.UnsignedChannelUpdateFromEdge(info, edge)
+
+	// We'll generate a new signature over a digest of the channel
+	// announcement itself and update the timestamp to ensure it propagate.
+	err := netann.SignChannelUpdate(
+		d.cfg.AnnSigner, d.selfKeyLoc, chanUpdate,
+		netann.ChanUpdSetTimestamp,
 	)
-	if premature {
-		log.Warnf("Premature proof announcement, current block height"+
-			"lower than needed: %v < %v", d.bestHeight,
-			needBlockHeight)
-		d.Unlock()
-		nMsg.err <- nil
-		return nil, false
+	if err != nil {
+		return nil, nil, err
 	}
-	d.Unlock()
-
-	// Ensure that we know of a channel with the target channel ID before
-	// proceeding further.
-	//
-	// We must acquire the mutex for this channel ID before getting the
-	// channel from the database, to ensure what we read does not change
-	// before we call AddProof() later.
-	d.channelMtx.Lock(ann.ShortChannelID.ToUint64())
-	defer d.channelMtx.Unlock(ann.ShortChannelID.ToUint64())
 
-	chanInfo, e1, e2, err := d.cfg.Router.GetChannelByID(
-		ann.ShortChannelID,
-	)
+	// Next, we'll set the new signature in place, and update the reference
+	// in the backing slice.
+	edge.LastUpdate = time.Unix(int64(chanUpdate.Timestamp), 0)
+	edge.SigBytes = chanUpdate.Signature.ToSignatureBytes()
 
+	// To ensure that our signature is valid, we'll verify it ourself
+	// before committing it to the slice returned.
+	err = routing.ValidateChannelUpdateAnn(d.selfKey, info.Capacity, chanUpdate)
 	if err != nil {
-		proof := channeldb.NewWaitingProof(nMsg.isRemote, ann)
-		err := d.cfg.WaitingProofStore.Add(proof)
-		if err != nil {
-			err := fmt.Errorf("unable to store the proof for "+
-				"short_chan_id=%v: %v", shortChanID, err)
-			log.Error(err)
-			nMsg.err <- err
-			return nil, false
-		}
-
-		log.Infof("Orphan %v proof announcement with short_chan_id=%v"+
-			", adding to waiting batch", prefix, shortChanID)
-		nMsg.err <- nil
-		return nil, false
+		return nil, nil, fmt.Errorf("generated invalid channel "+
+			"update sig: %v", err)
 	}
 
-	nodeID := nMsg.source.SerializeCompressed()
-	isFirstNode := bytes.Equal(nodeID, chanInfo.NodeKey1Bytes[:])
-	isSecondNode := bytes.Equal(nodeID, chanInfo.NodeKey2Bytes[:])
-
-	// Ensure that channel that was retrieved belongs to the peer which
-	// sent the proof announcement.
-	if !(isFirstNode || isSecondNode) {
-		err := fmt.Errorf("channel that was received doesn't belong "+
-			"to the peer which sent the proof, short_chan_id=%v",
-			shortChanID)
-		log.Error(err)
-		nMsg.err <- err
-		return nil, false
+	// Finally, we'll write the new edge policy to disk.
+	if err := d.cfg.Router.UpdateEdge(edge); err != nil {
+		return nil, nil, err
 	}
 
-	// If proof was sent by a local sub-system, then we'll send the
-	// announcement signature to the remote node so they can also
-	// reconstruct the full channel announcement.
-	if !nMsg.isRemote {
-		var remotePubKey [33]byte
-		if isFirstNode {
-			remotePubKey = chanInfo.NodeKey2Bytes
-		} else {
-			remotePubKey = chanInfo.NodeKey1Bytes
+	// We'll also create the original channel announcement so the two can
+	// be broadcast along side each other (if necessary), but only if we
+	// have a full channel announcement for this channel.
+	var chanAnn *lnwire.ChannelAnnouncement
+	if info.AuthProof != nil {
+		chanID := lnwire.NewShortChanIDFromInt(info.ChannelID)
+		chanAnn = &lnwire.ChannelAnnouncement{
+			ShortChannelID:  chanID,
+			NodeID1:         info.NodeKey1Bytes,
+			NodeID2:         info.NodeKey2Bytes,
+			ChainHash:       info.ChainHash,
+			BitcoinKey1:     info.BitcoinKey1Bytes,
+			Features:        lnwire.NewRawFeatureVector(),
+			BitcoinKey2:     info.BitcoinKey2Bytes,
+			ExtraOpaqueData: edge.ExtraOpaqueData,
 		}
-		// Since the remote peer might not be online we'll call a
-		// method that will attempt to deliver the proof when it comes
-		// online.
-		err := d.reliableSender.sendMessage(ann, remotePubKey)
+		chanAnn.NodeSig1, err = lnwire.NewSigFromRawSignature(
+			info.AuthProof.NodeSig1Bytes,
+		)
 		if err != nil {
-			err := fmt.Errorf("unable to reliably send %v for "+
-				"channel=%v to peer=%x: %v", ann.MsgType(),
-				ann.ShortChannelID, remotePubKey, err)
-			nMsg.err <- err
-			return nil, false
+			return nil, nil, err
+		}
+		chanAnn.NodeSig2, err = lnwire.NewSigFromRawSignature(
+			info.AuthProof.NodeSig2Bytes,
+		)
+		if err != nil {
+			return nil, nil, err
+		}
+		chanAnn.BitcoinSig1, err = lnwire.NewSigFromRawSignature(
+			info.AuthProof.BitcoinSig1Bytes,
+		)
+		if err != nil {
+			return nil, nil, err
+		}
+		chanAnn.BitcoinSig2, err = lnwire.NewSigFromRawSignature(
+			info.AuthProof.BitcoinSig2Bytes,
+		)
+		if err != nil {
+			return nil, nil, err
 		}
 	}
 
-	// Check if we already have the full proof for this channel.
-	if chanInfo.AuthProof != nil {
-		// If we already have the fully assembled proof, then the peer
-		// sending us their proof has probably not received our local
-		// proof yet. So be kind and send them the full proof.
-		if nMsg.isRemote {
-			peerID := nMsg.source.SerializeCompressed()
-			log.Debugf("Got AnnounceSignatures for channel with " +
-				"full proof.")
-
-			d.wg.Add(1)
-			go func() {
-				defer d.wg.Done()
-				log.Debugf("Received half proof for channel "+
-					"%v with existing full proof. Sending"+
-					" full proof to peer=%x",
-					ann.ChannelID, peerID)
-
-				ca, _, _, err := netann.CreateChanAnnouncement(
-					chanInfo.AuthProof, chanInfo, e1, e2,
-				)
-				if err != nil {
-					log.Errorf("unable to gen ann: %v",
-						err)
-					return
-				}
-
-				err = nMsg.peer.SendMessage(false, ca)
-				if err != nil {
-					log.Errorf("Failed sending full proof"+
-						" to peer=%x: %v", peerID, err)
-					return
-				}
-
-				log.Debugf("Full proof sent to peer=%x for "+
-					"chanID=%v", peerID, ann.ChannelID)
-			}()
-		}
+	return chanAnn, chanUpdate, err
+}
 
-		log.Debugf("Already have proof for channel with chanID=%v",
-			ann.ChannelID)
-		nMsg.err <- nil
-		return nil, true
-	}
-
-	// Check that we received the opposite proof. If so, then we're now
-	// able to construct the full proof, and create the channel
-	// announcement. If we didn't receive the opposite half of the proof
-	// then we should store this one, and wait for the opposite to be
-	// received.
-	proof := channeldb.NewWaitingProof(nMsg.isRemote, ann)
-	oppProof, err := d.cfg.WaitingProofStore.Get(proof.OppositeKey())
-	if err != nil && err != channeldb.ErrWaitingProofNotFound {
-		err := fmt.Errorf("unable to get the opposite proof for "+
-			"short_chan_id=%v: %v", shortChanID, err)
-		log.Error(err)
-		nMsg.err <- err
-		return nil, false
-	}
+// SyncManager returns the gossiper's SyncManager instance.
+func (d *AuthenticatedGossiper) SyncManager() *SyncManager {
+	return d.syncMgr
+}
 
-	if err == channeldb.ErrWaitingProofNotFound {
-		err := d.cfg.WaitingProofStore.Add(proof)
-		if err != nil {
-			err := fmt.Errorf("unable to store the proof for "+
-				"short_chan_id=%v: %v", shortChanID, err)
-			log.Error(err)
-			nMsg.err <- err
-			return nil, false
-		}
+// IsKeepAliveUpdate determines whether this channel update is considered a
+// keep-alive update based on the previous channel update processed for the same
+// direction.
+func IsKeepAliveUpdate(update *lnwire.ChannelUpdate,
+	prev *channeldb.ChannelEdgePolicy) bool {
 
-		log.Infof("1/2 of channel ann proof received for "+
-			"short_chan_id=%v, waiting for other half",
-			shortChanID)
+	// Both updates should be from the same direction.
+	if update.ChannelFlags&lnwire.ChanUpdateDirection !=
+		prev.ChannelFlags&lnwire.ChanUpdateDirection {
 
-		nMsg.err <- nil
-		return nil, false
+		return false
 	}
 
-	// We now have both halves of the channel announcement proof, then
-	// we'll reconstruct the initial announcement so we can validate it
-	// shortly below.
-	var dbProof channeldb.ChannelAuthProof
-	if isFirstNode {
-		dbProof.NodeSig1Bytes = ann.NodeSignature.ToSignatureBytes()
-		dbProof.NodeSig2Bytes = oppProof.NodeSignature.ToSignatureBytes()
-		dbProof.BitcoinSig1Bytes = ann.BitcoinSignature.ToSignatureBytes()
-		dbProof.BitcoinSig2Bytes = oppProof.BitcoinSignature.ToSignatureBytes()
-	} else {
-		dbProof.NodeSig1Bytes = oppProof.NodeSignature.ToSignatureBytes()
-		dbProof.NodeSig2Bytes = ann.NodeSignature.ToSignatureBytes()
-		dbProof.BitcoinSig1Bytes = oppProof.BitcoinSignature.ToSignatureBytes()
-		dbProof.BitcoinSig2Bytes = ann.BitcoinSignature.ToSignatureBytes()
+	// The timestamp should always increase for a keep-alive update.
+	timestamp := time.Unix(int64(update.Timestamp), 0)
+	if !timestamp.After(prev.LastUpdate) {
+		return false
 	}
 
-	chanAnn, e1Ann, e2Ann, err := netann.CreateChanAnnouncement(
-		&dbProof, chanInfo, e1, e2,
-	)
-	if err != nil {
-		log.Error(err)
-		nMsg.err <- err
-		return nil, false
+	// None of the remaining fields should change for a keep-alive update.
+	if update.ChannelFlags.IsDisabled() != prev.ChannelFlags.IsDisabled() {
+		return false
 	}
-
-	// With all the necessary components assembled validate the full
-	// channel announcement proof.
-	if err := routing.ValidateChannelAnn(chanAnn); err != nil {
-		err := fmt.Errorf("channel announcement proof for "+
-			"short_chan_id=%v isn't valid: %v", shortChanID, err)
-
-		log.Error(err)
-		nMsg.err <- err
-		return nil, false
+	if lnwire.MilliSatoshi(update.BaseFee) != prev.FeeBaseMSat {
+		return false
 	}
-
-	// If the channel was returned by the router it means that existence of
-	// funding point and inclusion of nodes bitcoin keys in it already
-	// checked by the router. In this stage we should check that node keys
-	// attest to the bitcoin keys by validating the signatures of
-	// announcement. If proof is valid then we'll populate the channel edge
-	// with it, so we can announce it on peer connect.
-	err = d.cfg.Router.AddProof(ann.ShortChannelID, &dbProof)
-	if err != nil {
-		err := fmt.Errorf("unable add proof to the channel chanID=%v:"+
-			" %v", ann.ChannelID, err)
-		log.Error(err)
-		nMsg.err <- err
-		return nil, false
+	if lnwire.MilliSatoshi(update.FeeRate) != prev.FeeProportionalMillionths {
+		return false
 	}
-
-	err = d.cfg.WaitingProofStore.Remove(proof.OppositeKey())
-	if err != nil {
-		err := fmt.Errorf("unable to remove opposite proof for the "+
-			"channel with chanID=%v: %v", ann.ChannelID, err)
-		log.Error(err)
-		nMsg.err <- err
-		return nil, false
+	if update.TimeLockDelta != prev.TimeLockDelta {
+		return false
 	}
-
-	// Proof was successfully created and now can announce the channel to
-	// the remain network.
-	log.Infof("Fully valid channel proof for short_chan_id=%v constructed"+
-		", adding to next ann batch", shortChanID)
-
-	// Assemble the necessary announcements to add to the next broadcasting
-	// batch.
-	var announcements []networkMsg
-	announcements = append(announcements, networkMsg{
-		peer:   nMsg.peer,
-		source: nMsg.source,
-		msg:    chanAnn,
-	})
-	if src, err := chanInfo.NodeKey1(); err == nil && e1Ann != nil {
-		announcements = append(announcements, networkMsg{
-			peer:   nMsg.peer,
-			source: src,
-			msg:    e1Ann,
-		})
+	if update.HtlcMinimumMsat != prev.MinHTLC {
+		return false
 	}
-	if src, err := chanInfo.NodeKey2(); err == nil && e2Ann != nil {
-		announcements = append(announcements, networkMsg{
-			peer:   nMsg.peer,
-			source: src,
-			msg:    e2Ann,
-		})
+	if update.MessageFlags.HasMaxHtlc() && !prev.MessageFlags.HasMaxHtlc() {
+		return false
 	}
-
-	// We'll also send along the node announcements for each channel
-	// participant if we know of them. To ensure our node announcement
-	// propagates to our channel counterparty, we'll set the source for
-	// each announcement to the node it belongs to, otherwise we won't send
-	// it since the source gets skipped. This isn't necessary for channel
-	// updates and announcement signatures since we send those directly to
-	// our channel counterparty through the gossiper's reliable sender.
-	node1Ann, err := d.fetchNodeAnn(chanInfo.NodeKey1Bytes)
-	if err != nil {
-		log.Debugf("Unable to fetch node announcement for %x: %v",
-			chanInfo.NodeKey1Bytes, err)
-	} else {
-		if nodeKey1, err := chanInfo.NodeKey1(); err == nil {
-			announcements = append(announcements, networkMsg{
-				peer:   nMsg.peer,
-				source: nodeKey1,
-				msg:    node1Ann,
-			})
-		}
+	if update.HtlcMaximumMsat != prev.MaxHTLC {
+		return false
 	}
-
-	node2Ann, err := d.fetchNodeAnn(chanInfo.NodeKey2Bytes)
-	if err != nil {
-		log.Debugf("Unable to fetch node announcement for %x: %v",
-			chanInfo.NodeKey2Bytes, err)
-	} else {
-		if nodeKey2, err := chanInfo.NodeKey2(); err == nil {
-			announcements = append(announcements, networkMsg{
-				peer:   nMsg.peer,
-				source: nodeKey2,
-				msg:    node2Ann,
-			})
-		}
+	if !bytes.Equal(update.ExtraOpaqueData, prev.ExtraOpaqueData) {
+		return false
 	}
+	return true
+}
 
-	nMsg.err <- nil
-	return announcements, true
+// latestHeight returns the gossiper's latest height known of the chain.
+func (d *AuthenticatedGossiper) latestHeight() uint32 {
+	d.Lock()
+	defer d.Unlock()
+	return d.bestHeight
 }
diff --git a/docs/release-notes/release-notes-0.15.0.md b/docs/release-notes/release-notes-0.15.0.md
index ba5a0dd68..f521ef3cc 100644
--- a/docs/release-notes/release-notes-0.15.0.md
+++ b/docs/release-notes/release-notes-0.15.0.md
@@ -48,9 +48,6 @@
   validation](https://github.com/lightningnetwork/lnd/pull/6314) when calling
   `NewSigFromRawSignature`.
 
-* [Fixed deadlock in invoice
-  registry](https://github.com/lightningnetwork/lnd/pull/6332).
-
 
 ## Misc
 
@@ -74,8 +71,6 @@
 * [A nightly build of the `lnd` docker image is now created
   automatically](https://github.com/lightningnetwork/lnd/pull/6160).
   
-* Add default values to [walletrpc.ListUnspent RPC call](https://github.com/lightningnetwork/lnd/pull/6190).
-
 * [Add `.vs/` folder to `.gitignore`](https://github.com/lightningnetwork/lnd/pull/6178). 
 
 * [Chain backend healthchecks disabled for --nochainbackend mode](https://github.com/lightningnetwork/lnd/pull/6184)
@@ -106,6 +101,10 @@
   change, it allows encrypted failure messages to be returned to the sender.
   Additionally it is possible to signal a malformed htlc.
 
+* Add an [always on](https://github.com/lightningnetwork/lnd/pull/6232) mode to
+  the HTLC interceptor API. This enables interception applications where every
+  packet must be intercepted.
+
 ## Database
 
 * [Add ForAll implementation for etcd to speed up
@@ -157,8 +156,6 @@ gRPC performance metrics (latency to process `GetInfo`, etc)](https://github.com
 
 * [The channel-commit-interval is now clamped to a reasonable timeframe of 1h.](https://github.com/lightningnetwork/lnd/pull/6220)
 
-* [A function in the gossiper `processNetworkAnnouncements` has been refactored for readability and for future deduplication efforts.](https://github.com/lightningnetwork/lnd/pull/6278)
-
 # Contributors (Alphabetical Order)
 
 * 3nprob
@@ -186,4 +183,4 @@ gRPC performance metrics (latency to process `GetInfo`, etc)](https://github.com
 * Thebora Kompanioni
 * Torkel Rogstad
 * Vsevolod Kaganovych
-* Yong Yu
+* Yong Yu
\ No newline at end of file
diff --git a/htlcswitch/interceptable_switch.go b/htlcswitch/interceptable_switch.go
index 72e8ae3b7..87ef1e61d 100644
--- a/htlcswitch/interceptable_switch.go
+++ b/htlcswitch/interceptable_switch.go
@@ -30,63 +30,289 @@ var (
 // Settle - routes UpdateFulfillHTLC to the originating link.
 // Fail - routes UpdateFailHTLC to the originating link.
 type InterceptableSwitch struct {
-	sync.RWMutex
-
 	// htlcSwitch is the underline switch
 	htlcSwitch *Switch
 
-	// fwdInterceptor is the callback that is called for each forward of
-	// an incoming htlc. It should return true if it is interested in handling
-	// it.
-	fwdInterceptor ForwardInterceptor
+	// intercepted is where we stream all intercepted packets coming from
+	// the switch.
+	intercepted chan *interceptedPackets
+
+	// resolutionChan is where we stream all responses coming from the
+	// interceptor client.
+	resolutionChan chan *fwdResolution
+
+	// interceptorRegistration is a channel that we use to synchronize
+	// client connect and disconnect.
+	interceptorRegistration chan ForwardInterceptor
+
+	// requireInterceptor indicates whether processing should block if no
+	// interceptor is connected.
+	requireInterceptor bool
+
+	// interceptor is the handler for intercepted packets.
+	interceptor ForwardInterceptor
+
+	// holdForwards keeps track of outstanding intercepted forwards.
+	holdForwards map[channeldb.CircuitKey]InterceptedForward
+
+	wg   sync.WaitGroup
+	quit chan struct{}
+}
+
+type interceptedPackets struct {
+	packets  []*htlcPacket
+	linkQuit chan struct{}
+	isReplay bool
+}
+
+// FwdAction defines the various resolution types.
+type FwdAction int
+
+const (
+	// FwdActionResume forwards the intercepted packet to the switch.
+	FwdActionResume FwdAction = iota
+
+	// FwdActionSettle settles the intercepted packet with a preimage.
+	FwdActionSettle
+
+	// FwdActionFail fails the intercepted packet back to the sender.
+	FwdActionFail
+)
+
+// FwdResolution defines the action to be taken on an intercepted packet.
+type FwdResolution struct {
+	// Key is the incoming circuit key of the htlc.
+	Key channeldb.CircuitKey
+
+	// Action is the action to take on the intercepted htlc.
+	Action FwdAction
+
+	// Preimage is the preimage that is to be used for settling if Action is
+	// FwdActionSettle.
+	Preimage lntypes.Preimage
+
+	// FailureMessage is the encrypted failure message that is to be passed
+	// back to the sender if action is FwdActionFail.
+	FailureMessage []byte
+
+	// FailureCode is the failure code that is to be passed back to the
+	// sender if action is FwdActionFail.
+	FailureCode lnwire.FailCode
+}
+
+type fwdResolution struct {
+	resolution *FwdResolution
+	errChan    chan error
 }
 
 // NewInterceptableSwitch returns an instance of InterceptableSwitch.
-func NewInterceptableSwitch(s *Switch) *InterceptableSwitch {
-	return &InterceptableSwitch{htlcSwitch: s}
+func NewInterceptableSwitch(s *Switch,
+	requireInterceptor bool) *InterceptableSwitch {
+
+	return &InterceptableSwitch{
+		htlcSwitch:              s,
+		intercepted:             make(chan *interceptedPackets),
+		interceptorRegistration: make(chan ForwardInterceptor),
+		holdForwards:            make(map[channeldb.CircuitKey]InterceptedForward),
+		resolutionChan:          make(chan *fwdResolution),
+		requireInterceptor:      requireInterceptor,
+
+		quit: make(chan struct{}),
+	}
 }
 
-// SetInterceptor sets the ForwardInterceptor to be used.
+// SetInterceptor sets the ForwardInterceptor to be used. A nil argument
+// unregisters the current interceptor.
 func (s *InterceptableSwitch) SetInterceptor(
 	interceptor ForwardInterceptor) {
 
-	s.Lock()
-	defer s.Unlock()
-	s.fwdInterceptor = interceptor
+	// Synchronize setting the handler with the main loop to prevent race
+	// conditions.
+	select {
+	case s.interceptorRegistration <- interceptor:
+
+	case <-s.quit:
+	}
 }
 
-// ForwardPackets attempts to forward the batch of htlcs through the
-// switch, any failed packets will be returned to the provided
-// ChannelLink. The link's quit signal should be provided to allow
-// cancellation of forwarding during link shutdown.
-func (s *InterceptableSwitch) ForwardPackets(linkQuit chan struct{},
-	packets ...*htlcPacket) error {
+func (s *InterceptableSwitch) Start() error {
+	s.wg.Add(1)
+	go func() {
+		defer s.wg.Done()
+
+		s.run()
+	}()
 
-	var interceptor ForwardInterceptor
-	s.Lock()
-	interceptor = s.fwdInterceptor
-	s.Unlock()
+	return nil
+}
 
-	// Optimize for the case we don't have an interceptor.
-	if interceptor == nil {
-		return s.htlcSwitch.ForwardPackets(linkQuit, packets...)
+func (s *InterceptableSwitch) Stop() error {
+	close(s.quit)
+	s.wg.Wait()
+
+	return nil
+}
+
+func (s *InterceptableSwitch) run() {
+	for {
+		select {
+		// An interceptor registration or de-registration came in.
+		case interceptor := <-s.interceptorRegistration:
+			s.setInterceptor(interceptor)
+
+		case packets := <-s.intercepted:
+			var notIntercepted []*htlcPacket
+			for _, p := range packets.packets {
+				if !s.interceptForward(p, packets.isReplay) {
+					notIntercepted = append(
+						notIntercepted, p,
+					)
+				}
+			}
+			err := s.htlcSwitch.ForwardPackets(
+				packets.linkQuit, notIntercepted...,
+			)
+			if err != nil {
+				log.Errorf("Cannot forward packets: %v", err)
+			}
+
+		case res := <-s.resolutionChan:
+			res.errChan <- s.resolve(res.resolution)
+
+		case <-s.quit:
+			return
+		}
+	}
+}
+func (s *InterceptableSwitch) sendForward(fwd InterceptedForward) {
+	err := s.interceptor(fwd.Packet())
+	if err != nil {
+		// Only log the error. If we couldn't send the packet, we assume
+		// that the interceptor will reconnect so that we can retry.
+		log.Debugf("Interceptor cannot handle forward: %v", err)
 	}
+}
 
-	var notIntercepted []*htlcPacket
-	for _, p := range packets {
-		if !s.interceptForward(p, interceptor, linkQuit) {
-			notIntercepted = append(notIntercepted, p)
+func (s *InterceptableSwitch) setInterceptor(interceptor ForwardInterceptor) {
+	s.interceptor = interceptor
+
+	// Replay all currently held htlcs. When an interceptor is not required,
+	// there may be none because they've been cleared after the previous
+	// disconnect.
+	if interceptor != nil {
+		log.Debugf("Interceptor connected")
+
+		for _, fwd := range s.holdForwards {
+			s.sendForward(fwd)
 		}
+
+		return
+	}
+
+	// The interceptor disconnects. If an interceptor is required, keep the
+	// held htlcs.
+	if s.requireInterceptor {
+		log.Infof("Interceptor disconnected, retaining held packets")
+
+		return
+	}
+
+	// Interceptor is not required. Release held forwards.
+	log.Infof("Interceptor disconnected, resolving held packets")
+
+	for _, fwd := range s.holdForwards {
+		if err := fwd.Resume(); err != nil {
+			log.Errorf("Failed to resume hold forward %v", err)
+		}
+	}
+	s.holdForwards = make(map[channeldb.CircuitKey]InterceptedForward)
+}
+
+func (s *InterceptableSwitch) resolve(res *FwdResolution) error {
+	intercepted, ok := s.holdForwards[res.Key]
+	if !ok {
+		return fmt.Errorf("fwd %v not found", res.Key)
+	}
+	delete(s.holdForwards, res.Key)
+
+	switch res.Action {
+	case FwdActionResume:
+		return intercepted.Resume()
+
+	case FwdActionSettle:
+		return intercepted.Settle(res.Preimage)
+
+	case FwdActionFail:
+		if len(res.FailureMessage) > 0 {
+			return intercepted.Fail(res.FailureMessage)
+		}
+
+		return intercepted.FailWithCode(res.FailureCode)
+
+	default:
+		return fmt.Errorf("unrecognized action %v", res.Action)
+	}
+}
+
+// Resolve resolves an intercepted packet.
+func (s *InterceptableSwitch) Resolve(res *FwdResolution) error {
+	internalRes := &fwdResolution{
+		resolution: res,
+		errChan:    make(chan error, 1),
+	}
+
+	select {
+	case s.resolutionChan <- internalRes:
+
+	case <-s.quit:
+		return errors.New("switch shutting down")
+	}
+
+	select {
+	case err := <-internalRes.errChan:
+		return err
+
+	case <-s.quit:
+		return errors.New("switch shutting down")
 	}
-	return s.htlcSwitch.ForwardPackets(linkQuit, notIntercepted...)
 }
 
-// interceptForward checks if there is any external interceptor interested in
-// this packet. Currently only htlc type of UpdateAddHTLC that are forwarded
-// are being checked for interception. It can be extended in the future given
-// the right use case.
+// ForwardPackets attempts to forward the batch of htlcs to a connected
+// interceptor. If the interceptor signals the resume action, the htlcs are
+// forwarded to the switch. The link's quit signal should be provided to allow
+// cancellation of forwarding during link shutdown.
+func (s *InterceptableSwitch) ForwardPackets(linkQuit chan struct{}, isReplay bool,
+	packets ...*htlcPacket) error {
+
+	// Synchronize with the main event loop. This should be light in the
+	// case where there is no interceptor.
+	select {
+	case s.intercepted <- &interceptedPackets{
+		packets:  packets,
+		linkQuit: linkQuit,
+		isReplay: isReplay,
+	}:
+
+	case <-linkQuit:
+		log.Debugf("Forward cancelled because link quit")
+
+	case <-s.quit:
+		return errors.New("interceptable switch quit")
+	}
+
+	return nil
+}
+
+// interceptForward forwards the packet to the external interceptor after
+// checking the interception criteria.
 func (s *InterceptableSwitch) interceptForward(packet *htlcPacket,
-	interceptor ForwardInterceptor, linkQuit chan struct{}) bool {
+	isReplay bool) bool {
+
+	// Process normally if an interceptor is not required and not
+	// registered.
+	if !s.requireInterceptor && s.interceptor == nil {
+		return false
+	}
 
 	switch htlc := packet.htlc.(type) {
 	case *lnwire.UpdateAddHTLC:
@@ -95,15 +321,50 @@ func (s *InterceptableSwitch) interceptForward(packet *htlcPacket,
 			return false
 		}
 
+		inKey := channeldb.CircuitKey{
+			ChanID: packet.incomingChanID,
+			HtlcID: packet.incomingHTLCID,
+		}
+
+		// Ignore already held htlcs.
+		if _, ok := s.holdForwards[inKey]; ok {
+			return true
+		}
+
 		intercepted := &interceptedForward{
-			linkQuit:   linkQuit,
 			htlc:       htlc,
 			packet:     packet,
 			htlcSwitch: s.htlcSwitch,
 		}
 
-		// If this htlc was intercepted, don't handle the forward.
-		return interceptor(intercepted)
+		if s.interceptor == nil && !isReplay {
+			// There is no interceptor registered, we are in
+			// interceptor-required mode, and this is a new packet
+			//
+			// Because the interceptor has never seen this packet
+			// yet, it is still safe to fail back. This limits the
+			// backlog of htlcs when the interceptor is down.
+			err := intercepted.FailWithCode(
+				lnwire.CodeTemporaryChannelFailure,
+			)
+			if err != nil {
+				log.Errorf("Cannot fail packet: %v", err)
+			}
+
+			return true
+		}
+
+		s.holdForwards[inKey] = intercepted
+
+		// If there is no interceptor registered, we must be in
+		// interceptor-required mode. The packet is kept in the queue
+		// until the interceptor registers itself.
+		if s.interceptor != nil {
+			s.sendForward(intercepted)
+		}
+
+		return true
+
 	default:
 		return false
 	}
@@ -113,7 +374,6 @@ func (s *InterceptableSwitch) interceptForward(packet *htlcPacket,
 // It is passed from the switch to external interceptors that are interested
 // in holding forwards and resolve them manually.
 type interceptedForward struct {
-	linkQuit   chan struct{}
 	htlc       *lnwire.UpdateAddHTLC
 	packet     *htlcPacket
 	htlcSwitch *Switch
@@ -139,10 +399,12 @@ func (f *interceptedForward) Packet() InterceptedPacket {
 
 // Resume resumes the default behavior as if the packet was not intercepted.
 func (f *interceptedForward) Resume() error {
-	return f.htlcSwitch.ForwardPackets(f.linkQuit, f.packet)
+	// Forward to the switch. A link quit channel isn't needed, because we
+	// are on a different thread now.
+	return f.htlcSwitch.ForwardPackets(nil, f.packet)
 }
 
-// Fail notifies the intention to fail an existing hold forward with an
+// Fail notifies the intention to Fail an existing hold forward with an
 // encrypted failure reason.
 func (f *interceptedForward) Fail(reason []byte) error {
 	obfuscatedReason := f.packet.obfuscator.IntermediateEncrypt(reason)
diff --git a/htlcswitch/interfaces.go b/htlcswitch/interfaces.go
index 4b201ee1e..1b80adab3 100644
--- a/htlcswitch/interfaces.go
+++ b/htlcswitch/interfaces.go
@@ -234,6 +234,9 @@ type TowerClient interface {
 type InterceptableHtlcForwarder interface {
 	// SetInterceptor sets a ForwardInterceptor.
 	SetInterceptor(interceptor ForwardInterceptor)
+
+	// Resolve resolves an intercepted packet.
+	Resolve(res *FwdResolution) error
 }
 
 // ForwardInterceptor is a function that is invoked from the switch for every
@@ -242,7 +245,7 @@ type InterceptableHtlcForwarder interface {
 // to resolve it manually later in case it is held.
 // The return value indicates if this handler will take control of this forward
 // and resolve it later or let the switch execute its default behavior.
-type ForwardInterceptor func(InterceptedForward) bool
+type ForwardInterceptor func(InterceptedPacket) error
 
 // InterceptedPacket contains the relevant information for the interceptor about
 // an htlc.
diff --git a/htlcswitch/link.go b/htlcswitch/link.go
index d71d20d14..a867f6b00 100644
--- a/htlcswitch/link.go
+++ b/htlcswitch/link.go
@@ -141,7 +141,7 @@ type ChannelLinkConfig struct {
 	// switch. The function returns and error in case it fails to send one or
 	// more packets. The link's quit signal should be provided to allow
 	// cancellation of forwarding during link shutdown.
-	ForwardPackets func(chan struct{}, ...*htlcPacket) error
+	ForwardPackets func(chan struct{}, bool, ...*htlcPacket) error
 
 	// DecodeHopIterators facilitates batched decoding of HTLC Sphinx onion
 	// blobs, which are then used to inform how to forward an HTLC.
@@ -1720,7 +1720,7 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) {
 		l.uncommittedPreimages = append(l.uncommittedPreimages, pre)
 
 		// Pipeline this settle, send it to the switch.
-		go l.forwardBatch(settlePacket)
+		go l.forwardBatch(false, settlePacket)
 
 	case *lnwire.UpdateFailMalformedHTLC:
 		// Convert the failure type encoded within the HTLC fail
@@ -2744,7 +2744,7 @@ func (l *channelLink) processRemoteSettleFails(fwdPkg *channeldb.FwdPkg,
 
 	// Only spawn the task forward packets we have a non-zero number.
 	if len(switchPackets) > 0 {
-		go l.forwardBatch(switchPackets...)
+		go l.forwardBatch(false, switchPackets...)
 	}
 }
 
@@ -3043,14 +3043,17 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg,
 		return
 	}
 
-	l.log.Debugf("forwarding %d packets to switch", len(switchPackets))
+	replay := fwdPkg.State != channeldb.FwdStateLockedIn
+
+	l.log.Debugf("forwarding %d packets to switch: replay=%v",
+		len(switchPackets), replay)
 
 	// 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(switchPackets...)
+	l.forwardBatch(replay, switchPackets...)
 }
 
 // processExitHop handles an htlc for which this link is the exit hop. It
@@ -3184,7 +3187,7 @@ func (l *channelLink) settleHTLC(preimage lntypes.Preimage,
 // forwardBatch forwards the given htlcPackets to the switch, and waits on the
 // err chan for the individual responses. This method is intended to be spawned
 // as a goroutine so the responses can be handled in the background.
-func (l *channelLink) forwardBatch(packets ...*htlcPacket) {
+func (l *channelLink) forwardBatch(replay bool, packets ...*htlcPacket) {
 	// Don't forward packets for which we already have a response in our
 	// mailbox. This could happen if a packet fails and is buffered in the
 	// mailbox, and the incoming link flaps.
@@ -3197,7 +3200,8 @@ func (l *channelLink) forwardBatch(packets ...*htlcPacket) {
 		filteredPkts = append(filteredPkts, pkt)
 	}
 
-	if err := l.cfg.ForwardPackets(l.quit, filteredPkts...); err != nil {
+	err := l.cfg.ForwardPackets(l.quit, replay, filteredPkts...)
+	if err != nil {
 		log.Errorf("Unhandled error while reforwarding htlc "+
 			"settle/fail over htlcswitch: %v", err)
 	}
diff --git a/invoices/invoiceregistry.go b/invoices/invoiceregistry.go
index aaee2a775..11eca9dd3 100644
--- a/invoices/invoiceregistry.go
+++ b/invoices/invoiceregistry.go
@@ -939,18 +939,12 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
 
 	// Execute locked notify exit hop logic.
 	i.Lock()
-	resolution, invoiceToExpire, err := i.notifyExitHopHtlcLocked(
-		&ctx, hodlChan,
-	)
+	resolution, err := i.notifyExitHopHtlcLocked(&ctx, hodlChan)
 	i.Unlock()
 	if err != nil {
 		return nil, err
 	}
 
-	if invoiceToExpire != nil {
-		i.expiryWatcher.AddInvoices(invoiceToExpire)
-	}
-
 	switch r := resolution.(type) {
 	// The htlc is held. Start a timer outside the lock if the htlc should
 	// be auto-released, because otherwise a deadlock may happen with the
@@ -981,11 +975,10 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
 }
 
 // notifyExitHopHtlcLocked is the internal implementation of NotifyExitHopHtlc
-// that should be executed inside the registry lock. The returned invoiceExpiry
-// (if not nil) needs to be added to the expiry watcher outside of the lock.
+// that should be executed inside the registry lock.
 func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
 	ctx *invoiceUpdateCtx, hodlChan chan<- interface{}) (
-	HtlcResolution, invoiceExpiry, error) {
+	HtlcResolution, error) {
 
 	// We'll attempt to settle an invoice matching this rHash on disk (if
 	// one exists). The callback will update the invoice state and/or htlcs.
@@ -1021,17 +1014,15 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
 		return NewFailResolution(
 			ctx.circuitKey, ctx.currentHeight,
 			ResultInvoiceNotFound,
-		), nil, nil
+		), nil
 
 	case nil:
 
 	default:
 		ctx.log(err.Error())
-		return nil, nil, err
+		return nil, err
 	}
 
-	var invoiceToExpire invoiceExpiry
-
 	switch res := resolution.(type) {
 	case *HtlcFailResolution:
 		// Inspect latest htlc state on the invoice. If it is found,
@@ -1125,7 +1116,7 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
 	case *htlcAcceptResolution:
 		invoiceHtlc, ok := invoice.Htlcs[ctx.circuitKey]
 		if !ok {
-			return nil, nil, fmt.Errorf("accepted htlc: %v not"+
+			return nil, fmt.Errorf("accepted htlc: %v not"+
 				" present on invoice: %x", ctx.circuitKey,
 				ctx.hash[:])
 		}
@@ -1154,7 +1145,8 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
 		// possible that we MppTimeout the htlcs, and then our relevant
 		// expiry height could change.
 		if res.outcome == resultAccepted {
-			invoiceToExpire = makeInvoiceExpiry(ctx.hash, invoice)
+			expiry := makeInvoiceExpiry(ctx.hash, invoice)
+			i.expiryWatcher.AddInvoices(expiry)
 		}
 
 		i.hodlSubscribe(hodlChan, ctx.circuitKey)
@@ -1177,7 +1169,7 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
 		i.notifyClients(ctx.hash, invoice, setID)
 	}
 
-	return resolution, invoiceToExpire, nil
+	return resolution, nil
 }
 
 // SettleHodlInvoice sets the preimage of a hodl invoice.
diff --git a/lnrpc/routerrpc/forward_interceptor.go b/lnrpc/routerrpc/forward_interceptor.go
index c52a23d2a..9fba60f1f 100644
--- a/lnrpc/routerrpc/forward_interceptor.go
+++ b/lnrpc/routerrpc/forward_interceptor.go
@@ -2,7 +2,6 @@ package routerrpc
 
 import (
 	"errors"
-	"sync"
 
 	"github.com/lightningnetwork/lnd/channeldb"
 	"github.com/lightningnetwork/lnd/htlcswitch"
@@ -27,36 +26,19 @@ var (
 // interceptor streaming session.
 // It is created when the stream opens and disconnects when the stream closes.
 type forwardInterceptor struct {
-	// server is the Server reference
-	server *Server
-
-	// holdForwards is a map of current hold forwards and their corresponding
-	// ForwardResolver.
-	holdForwards map[channeldb.CircuitKey]htlcswitch.InterceptedForward
-
 	// stream is the bidirectional RPC stream
 	stream Router_HtlcInterceptorServer
 
-	// quit is a channel that is closed when this forwardInterceptor is shutting
-	// down.
-	quit chan struct{}
-
-	// intercepted is where we stream all intercepted packets coming from
-	// the switch.
-	intercepted chan htlcswitch.InterceptedForward
-
-	wg sync.WaitGroup
+	htlcSwitch htlcswitch.InterceptableHtlcForwarder
 }
 
 // newForwardInterceptor creates a new forwardInterceptor.
-func newForwardInterceptor(server *Server, stream Router_HtlcInterceptorServer) *forwardInterceptor {
+func newForwardInterceptor(htlcSwitch htlcswitch.InterceptableHtlcForwarder,
+	stream Router_HtlcInterceptorServer) *forwardInterceptor {
+
 	return &forwardInterceptor{
-		server: server,
-		stream: stream,
-		holdForwards: make(
-			map[channeldb.CircuitKey]htlcswitch.InterceptedForward),
-		quit:        make(chan struct{}),
-		intercepted: make(chan htlcswitch.InterceptedForward),
+		htlcSwitch: htlcSwitch,
+		stream:     stream,
 	}
 }
 
@@ -67,42 +49,18 @@ func newForwardInterceptor(server *Server, stream Router_HtlcInterceptorServer)
 // To coordinate all this and make sure it is safe for concurrent access all
 // packets are sent to the main where they are handled.
 func (r *forwardInterceptor) run() error {
-	// make sure we disconnect and resolves all remaining packets if any.
-	defer r.onDisconnect()
-
 	// Register our interceptor so we receive all forwarded packets.
-	interceptableForwarder := r.server.cfg.RouterBackend.InterceptableForwarder
-	interceptableForwarder.SetInterceptor(r.onIntercept)
-	defer interceptableForwarder.SetInterceptor(nil)
-
-	// start a go routine that reads client resolutions.
-	errChan := make(chan error)
-	resolutionRequests := make(chan *ForwardHtlcInterceptResponse)
-	r.wg.Add(1)
-	go r.readClientResponses(resolutionRequests, errChan)
+	r.htlcSwitch.SetInterceptor(r.onIntercept)
+	defer r.htlcSwitch.SetInterceptor(nil)
 
-	// run the main loop that synchronizes both sides input into one go routine.
 	for {
-		select {
-		case intercepted := <-r.intercepted:
-			log.Tracef("sending intercepted packet to client %v", intercepted)
-			// in case we couldn't forward we exit the loop and drain the
-			// current interceptor as this indicates on a connection problem.
-			if err := r.holdAndForwardToClient(intercepted); err != nil {
-				return err
-			}
-		case resolution := <-resolutionRequests:
-			log.Tracef("resolving intercepted packet %v", resolution)
-			// in case we couldn't resolve we just add a log line since this
-			// does not indicate on any connection problem.
-			if err := r.resolveFromClient(resolution); err != nil {
-				log.Warnf("client resolution of intercepted "+
-					"packet failed %v", err)
-			}
-		case err := <-errChan:
+		resp, err := r.stream.Recv()
+		if err != nil {
+			return err
+		}
+
+		if err := r.resolveFromClient(resp); err != nil {
 			return err
-		case <-r.server.quit:
-			return nil
 		}
 	}
 }
@@ -111,54 +69,14 @@ func (r *forwardInterceptor) run() error {
 // packet. Our interceptor makes sure we hold the packet and then signal to the
 // main loop to handle the packet. We only return true if we were able
 // to deliver the packet to the main loop.
-func (r *forwardInterceptor) onIntercept(p htlcswitch.InterceptedForward) bool {
-	select {
-	case r.intercepted <- p:
-		return true
-	case <-r.quit:
-		return false
-	case <-r.server.quit:
-		return false
-	}
-}
+func (r *forwardInterceptor) onIntercept(
+	htlc htlcswitch.InterceptedPacket) error {
 
-func (r *forwardInterceptor) readClientResponses(
-	resolutionChan chan *ForwardHtlcInterceptResponse, errChan chan error) {
+	log.Tracef("Sending intercepted packet to client %v", htlc)
 
-	defer r.wg.Done()
-	for {
-		resp, err := r.stream.Recv()
-		if err != nil {
-			errChan <- err
-			return
-		}
-
-		// Now that we have the response from the RPC client, send it to
-		// the responses chan.
-		select {
-		case resolutionChan <- resp:
-		case <-r.quit:
-			return
-		case <-r.server.quit:
-			return
-		}
-	}
-}
-
-// holdAndForwardToClient forwards the intercepted htlc to the client.
-func (r *forwardInterceptor) holdAndForwardToClient(
-	forward htlcswitch.InterceptedForward) error {
-
-	htlc := forward.Packet()
 	inKey := htlc.IncomingCircuit
 
-	// Ignore already held htlcs.
-	if _, ok := r.holdForwards[inKey]; ok {
-		return nil
-	}
-
 	// First hold the forward, then send to client.
-	r.holdForwards[inKey] = forward
 	interceptionRequest := &ForwardHtlcInterceptRequest{
 		IncomingCircuitKey: &CircuitKey{
 			ChanId: inKey.ChanID.ToUint64(),
@@ -181,20 +99,19 @@ func (r *forwardInterceptor) holdAndForwardToClient(
 func (r *forwardInterceptor) resolveFromClient(
 	in *ForwardHtlcInterceptResponse) error {
 
+	log.Tracef("Resolving intercepted packet %v", in)
+
 	circuitKey := channeldb.CircuitKey{
 		ChanID: lnwire.NewShortChanIDFromInt(in.IncomingCircuitKey.ChanId),
 		HtlcID: in.IncomingCircuitKey.HtlcId,
 	}
-	var interceptedForward htlcswitch.InterceptedForward
-	interceptedForward, ok := r.holdForwards[circuitKey]
-	if !ok {
-		return ErrFwdNotExists
-	}
-	delete(r.holdForwards, circuitKey)
 
 	switch in.Action {
 	case ResolveHoldForwardAction_RESUME:
-		return interceptedForward.Resume()
+		return r.htlcSwitch.Resolve(&htlcswitch.FwdResolution{
+			Key:    circuitKey,
+			Action: htlcswitch.FwdActionResume,
+		})
 
 	case ResolveHoldForwardAction_FAIL:
 		// Fail with an encrypted reason.
@@ -219,7 +136,11 @@ func (r *forwardInterceptor) resolveFromClient(
 				)
 			}
 
-			return interceptedForward.Fail(in.FailureMessage)
+			return r.htlcSwitch.Resolve(&htlcswitch.FwdResolution{
+				Key:            circuitKey,
+				Action:         htlcswitch.FwdActionFail,
+				FailureMessage: in.FailureMessage,
+			})
 		}
 
 		var code lnwire.FailCode
@@ -244,14 +165,11 @@ func (r *forwardInterceptor) resolveFromClient(
 			)
 		}
 
-		err := interceptedForward.FailWithCode(code)
-		if err == htlcswitch.ErrUnsupportedFailureCode {
-			return status.Errorf(
-				codes.InvalidArgument, err.Error(),
-			)
-		}
-
-		return err
+		return r.htlcSwitch.Resolve(&htlcswitch.FwdResolution{
+			Key:         circuitKey,
+			Action:      htlcswitch.FwdActionFail,
+			FailureCode: code,
+		})
 
 	case ResolveHoldForwardAction_SETTLE:
 		if in.Preimage == nil {
@@ -261,7 +179,12 @@ func (r *forwardInterceptor) resolveFromClient(
 		if err != nil {
 			return err
 		}
-		return interceptedForward.Settle(preimage)
+
+		return r.htlcSwitch.Resolve(&htlcswitch.FwdResolution{
+			Key:      circuitKey,
+			Action:   htlcswitch.FwdActionSettle,
+			Preimage: preimage,
+		})
 
 	default:
 		return status.Errorf(
@@ -270,20 +193,3 @@ func (r *forwardInterceptor) resolveFromClient(
 		)
 	}
 }
-
-// onDisconnect removes all previousely held forwards from
-// the store. Before they are removed it ensure to resume as the default
-// behavior.
-func (r *forwardInterceptor) onDisconnect() {
-	// Then close the channel so all go routine will exit.
-	close(r.quit)
-
-	log.Infof("RPC interceptor disconnected, resolving held packets")
-	for key, forward := range r.holdForwards {
-		if err := forward.Resume(); err != nil {
-			log.Errorf("failed to resume hold forward %v", err)
-		}
-		delete(r.holdForwards, key)
-	}
-	r.wg.Wait()
-}
diff --git a/lnrpc/routerrpc/router_server.go b/lnrpc/routerrpc/router_server.go
index dc14af4ef..0c60a62d5 100644
--- a/lnrpc/routerrpc/router_server.go
+++ b/lnrpc/routerrpc/router_server.go
@@ -890,7 +890,9 @@ func (s *Server) HtlcInterceptor(stream Router_HtlcInterceptorServer) error {
 	defer atomic.CompareAndSwapInt32(&s.forwardInterceptorActive, 1, 0)
 
 	// run the forward interceptor.
-	return newForwardInterceptor(s, stream).run()
+	return newForwardInterceptor(
+		s.cfg.RouterBackend.InterceptableForwarder, stream,
+	).run()
 }
 
 func extractOutPoint(req *UpdateChanStatusRequest) (*wire.OutPoint, error) {
diff --git a/lnrpc/walletrpc/walletkit.pb.go b/lnrpc/walletrpc/walletkit.pb.go
index baddd9d11..4a1686da2 100644
--- a/lnrpc/walletrpc/walletkit.pb.go
+++ b/lnrpc/walletrpc/walletkit.pb.go
@@ -214,12 +214,6 @@ type ListUnspentRequest struct {
 	MaxConfs int32 `protobuf:"varint,2,opt,name=max_confs,json=maxConfs,proto3" json:"max_confs,omitempty"`
 	// An optional filter to only include outputs belonging to an account.
 	Account string `protobuf:"bytes,3,opt,name=account,proto3" json:"account,omitempty"`
-	//
-	//When min_confs and max_confs are zero, setting false implicitly
-	//overrides max_confs to be MaxInt32, otherwise max_confs remains
-	//zero. An error is returned if the value is true and both min_confs
-	//and max_confs are non-zero. (default: false)
-	UnconfirmedOnly bool `protobuf:"varint,4,opt,name=unconfirmed_only,json=unconfirmedOnly,proto3" json:"unconfirmed_only,omitempty"`
 }
 
 func (x *ListUnspentRequest) Reset() {
@@ -275,13 +269,6 @@ func (x *ListUnspentRequest) GetAccount() string {
 	return ""
 }
 
-func (x *ListUnspentRequest) GetUnconfirmedOnly() bool {
-	if x != nil {
-		return x.UnconfirmedOnly
-	}
-	return false
-}
-
 type ListUnspentResponse struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -2916,430 +2903,428 @@ var file_walletrpc_walletkit_proto_rawDesc = []byte{
 	0x65, 0x74, 0x6b, 0x69, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x77, 0x61, 0x6c,
 	0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x1a, 0x0f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e,
 	0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x14, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63,
-	0x2f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x93, 0x01,
-	0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71,
-	0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66,
-	0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66,
-	0x73, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x02,
-	0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x18,
-	0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
-	0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x75, 0x6e, 0x63, 0x6f,
-	0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01,
-	0x28, 0x08, 0x52, 0x0f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x4f,
-	0x6e, 0x6c, 0x79, 0x22, 0x38, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65,
-	0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x05, 0x75, 0x74,
-	0x78, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6c, 0x6e, 0x72, 0x70,
-	0x63, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x52, 0x05, 0x75, 0x74, 0x78, 0x6f, 0x73, 0x22, 0x80, 0x01,
-	0x0a, 0x12, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71,
-	0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,
-	0x52, 0x02, 0x69, 0x64, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74,
-	0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f,
-	0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e,
-	0x74, 0x12, 0x2d, 0x0a, 0x12, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f,
-	0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x65,
-	0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73,
-	0x22, 0x35, 0x0a, 0x13, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52,
-	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72,
-	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x65, 0x78, 0x70,
-	0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x53, 0x0a, 0x14, 0x52, 0x65, 0x6c, 0x65, 0x61,
-	0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
-	0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12,
-	0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28,
-	0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69,
-	0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x17, 0x0a, 0x15,
-	0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x73,
-	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x51, 0x0a, 0x06, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x12,
-	0x28, 0x0a, 0x10, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x5f, 0x70, 0x72,
-	0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x6b, 0x65, 0x79, 0x46, 0x69,
-	0x6e, 0x67, 0x65, 0x72, 0x50, 0x72, 0x69, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6b, 0x65, 0x79,
-	0x5f, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x6b,
-	0x65, 0x79, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x22, 0x6b, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x72,
-	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75,
-	0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e,
-	0x74, 0x12, 0x2a, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32,
-	0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72,
-	0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a,
-	0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x63,
-	0x68, 0x61, 0x6e, 0x67, 0x65, 0x22, 0x22, 0x0a, 0x0c, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x73,
-	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x22, 0xe2, 0x02, 0x0a, 0x07, 0x41, 0x63,
-	0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x0c, 0x61, 0x64, 0x64,
-	0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32,
-	0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72,
-	0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
-	0x54, 0x79, 0x70, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64,
-	0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x11, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x75, 0x62, 0x6c, 0x69,
-	0x63, 0x4b, 0x65, 0x79, 0x12, 0x34, 0x0a, 0x16, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6b,
-	0x65, 0x79, 0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x18, 0x04,
-	0x20, 0x01, 0x28, 0x0c, 0x52, 0x14, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x46,
-	0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x65,
-	0x72, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x65, 0x72, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50,
-	0x61, 0x74, 0x68, 0x12, 0x2c, 0x0a, 0x12, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f,
-	0x6b, 0x65, 0x79, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52,
-	0x10, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b, 0x65, 0x79, 0x43, 0x6f, 0x75, 0x6e,
-	0x74, 0x12, 0x2c, 0x0a, 0x12, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6b, 0x65,
-	0x79, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x69,
-	0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b, 0x65, 0x79, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12,
-	0x1d, 0x0a, 0x0a, 0x77, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x08, 0x20,
-	0x01, 0x28, 0x08, 0x52, 0x09, 0x77, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x64,
-	0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65,
-	0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x0c, 0x61, 0x64, 0x64,
-	0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32,
-	0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72,
-	0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
-	0x54, 0x79, 0x70, 0x65, 0x22, 0x46, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f,
-	0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x08,
-	0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12,
-	0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75,
-	0x6e, 0x74, 0x52, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22, 0xe4, 0x01, 0x0a,
-	0x14, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65,
-	0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x65, 0x78, 0x74,
-	0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79,
-	0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64,
-	0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x34, 0x0a, 0x16, 0x6d, 0x61, 0x73,
-	0x74, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72,
-	0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x14, 0x6d, 0x61, 0x73, 0x74, 0x65,
-	0x72, 0x4b, 0x65, 0x79, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x12,
-	0x39, 0x0a, 0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18,
-	0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70,
-	0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x61,
-	0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72,
-	0x79, 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x72, 0x79,
-	0x52, 0x75, 0x6e, 0x22, 0xaf, 0x01, 0x0a, 0x15, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63,
-	0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a,
-	0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12,
-	0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75,
-	0x6e, 0x74, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x33, 0x0a, 0x16, 0x64,
-	0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f,
-	0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x64, 0x72, 0x79,
-	0x52, 0x75, 0x6e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x73,
-	0x12, 0x33, 0x0a, 0x16, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x69, 0x6e, 0x74, 0x65,
-	0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09,
-	0x52, 0x13, 0x64, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
-	0x41, 0x64, 0x64, 0x72, 0x73, 0x22, 0x72, 0x0a, 0x16, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50,
-	0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
-	0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x39,
-	0x0a, 0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02,
-	0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63,
-	0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x61, 0x64,
-	0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x22, 0x19, 0x0a, 0x17, 0x49, 0x6d, 0x70,
-	0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70,
-	0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3a, 0x0a, 0x0b, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74,
-	0x69, 0x6f, 0x6e, 0x12, 0x15, 0x0a, 0x06, 0x74, 0x78, 0x5f, 0x68, 0x65, 0x78, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x0c, 0x52, 0x05, 0x74, 0x78, 0x48, 0x65, 0x78, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61,
-	0x62, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c,
-	0x22, 0x36, 0x0a, 0x0f, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f,
-	0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x5f, 0x65,
-	0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x75, 0x62, 0x6c,
-	0x69, 0x73, 0x68, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x22, 0xbc, 0x01, 0x0a, 0x12, 0x53, 0x65, 0x6e,
-	0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
-	0x1c, 0x0a, 0x0a, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x03, 0x52, 0x08, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x4b, 0x77, 0x12, 0x28, 0x0a,
-	0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e,
-	0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x78, 0x4f, 0x75, 0x74, 0x52, 0x07,
-	0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c,
-	0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x1b, 0x0a,
-	0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05,
-	0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x73, 0x70,
-	0x65, 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x18,
-	0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f,
-	0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x22, 0x2c, 0x0a, 0x13, 0x53, 0x65, 0x6e, 0x64, 0x4f,
-	0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x15,
-	0x0a, 0x06, 0x72, 0x61, 0x77, 0x5f, 0x74, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05,
-	0x72, 0x61, 0x77, 0x54, 0x78, 0x22, 0x35, 0x0a, 0x12, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74,
-	0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x63,
-	0x6f, 0x6e, 0x66, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05,
-	0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x22, 0x33, 0x0a, 0x13,
-	0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,
-	0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x0a, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6b,
+	0x2f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x68, 0x0a,
+	0x12, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75,
+	0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73,
+	0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x02, 0x20,
+	0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x18, 0x0a,
+	0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
+	0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x38, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x55,
+	0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21,
+	0x0a, 0x05, 0x75, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e,
+	0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x52, 0x05, 0x75, 0x74, 0x78, 0x6f,
+	0x73, 0x22, 0x80, 0x01, 0x0a, 0x12, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75,
+	0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70,
+	0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72,
+	0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74,
+	0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x2d, 0x0a, 0x12, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74,
+	0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28,
+	0x04, 0x52, 0x11, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x63,
+	0x6f, 0x6e, 0x64, 0x73, 0x22, 0x35, 0x0a, 0x13, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74,
+	0x70, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x65,
+	0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52,
+	0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x53, 0x0a, 0x14, 0x52,
+	0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75,
+	0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52,
+	0x02, 0x69, 0x64, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18,
+	0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75,
+	0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74,
+	0x22, 0x17, 0x0a, 0x15, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75,
+	0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x51, 0x0a, 0x06, 0x4b, 0x65, 0x79,
+	0x52, 0x65, 0x71, 0x12, 0x28, 0x0a, 0x10, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65,
+	0x72, 0x5f, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x6b,
+	0x65, 0x79, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x50, 0x72, 0x69, 0x6e, 0x74, 0x12, 0x1d, 0x0a,
+	0x0a, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28,
+	0x05, 0x52, 0x09, 0x6b, 0x65, 0x79, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x22, 0x6b, 0x0a, 0x0b,
+	0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61,
+	0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63,
+	0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20,
+	0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e,
+	0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70,
+	0x65, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
+	0x08, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x22, 0x22, 0x0a, 0x0c, 0x41, 0x64, 0x64,
+	0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64,
+	0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x22, 0xe2, 0x02,
+	0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,
+	0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x39, 0x0a,
+	0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20,
+	0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e,
+	0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x61, 0x64, 0x64,
+	0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x65, 0x78, 0x74, 0x65,
+	0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18,
+	0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x50,
+	0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x34, 0x0a, 0x16, 0x6d, 0x61, 0x73, 0x74,
+	0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69,
+	0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x14, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72,
+	0x4b, 0x65, 0x79, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x12, 0x27,
+	0x0a, 0x0f, 0x64, 0x65, 0x72, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x61, 0x74,
+	0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x65, 0x72, 0x69, 0x76, 0x61, 0x74,
+	0x69, 0x6f, 0x6e, 0x50, 0x61, 0x74, 0x68, 0x12, 0x2c, 0x0a, 0x12, 0x65, 0x78, 0x74, 0x65, 0x72,
+	0x6e, 0x61, 0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20,
+	0x01, 0x28, 0x0d, 0x52, 0x10, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b, 0x65, 0x79,
+	0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61,
+	0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28,
+	0x0d, 0x52, 0x10, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b, 0x65, 0x79, 0x43, 0x6f,
+	0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x77, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x6f, 0x6e, 0x6c,
+	0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x77, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x6e,
+	0x6c, 0x79, 0x22, 0x64, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e,
+	0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,
+	0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x39, 0x0a,
+	0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20,
+	0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e,
+	0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x61, 0x64, 0x64,
+	0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x22, 0x46, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74,
+	0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
+	0x12, 0x2e, 0x0a, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03,
+	0x28, 0x0b, 0x32, 0x12, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41,
+	0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73,
+	0x22, 0xe4, 0x01, 0x0a, 0x14, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75,
+	0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,
+	0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2e, 0x0a,
+	0x13, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63,
+	0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x65, 0x78, 0x74, 0x65,
+	0x6e, 0x64, 0x65, 0x64, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x34, 0x0a,
+	0x16, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x69, 0x6e, 0x67,
+	0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x14, 0x6d,
+	0x61, 0x73, 0x74, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72,
+	0x69, 0x6e, 0x74, 0x12, 0x39, 0x0a, 0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74,
+	0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c,
+	0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70,
+	0x65, 0x52, 0x0b, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17,
+	0x0a, 0x07, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52,
+	0x06, 0x64, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x22, 0xaf, 0x01, 0x0a, 0x15, 0x49, 0x6d, 0x70, 0x6f,
+	0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
+	0x65, 0x12, 0x2c, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x0b, 0x32, 0x12, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41,
+	0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12,
+	0x33, 0x0a, 0x16, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72,
+	0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52,
+	0x13, 0x64, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41,
+	0x64, 0x64, 0x72, 0x73, 0x12, 0x33, 0x0a, 0x16, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x5f,
+	0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x03,
+	0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x64, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x49, 0x6e, 0x74, 0x65,
+	0x72, 0x6e, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x73, 0x22, 0x72, 0x0a, 0x16, 0x49, 0x6d, 0x70,
+	0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75,
+	0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65,
+	0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b,
+	0x65, 0x79, 0x12, 0x39, 0x0a, 0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79,
+	0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65,
+	0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65,
+	0x52, 0x0b, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x22, 0x19, 0x0a,
+	0x17, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79,
+	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3a, 0x0a, 0x0b, 0x54, 0x72, 0x61, 0x6e,
+	0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x15, 0x0a, 0x06, 0x74, 0x78, 0x5f, 0x68, 0x65,
+	0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x74, 0x78, 0x48, 0x65, 0x78, 0x12, 0x14,
+	0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c,
+	0x61, 0x62, 0x65, 0x6c, 0x22, 0x36, 0x0a, 0x0f, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52,
+	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x75, 0x62, 0x6c, 0x69,
+	0x73, 0x68, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c,
+	0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x22, 0xbc, 0x01, 0x0a,
+	0x12, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75,
+	0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x0a, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6b,
 	0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x4b,
-	0x77, 0x22, 0xfc, 0x03, 0x0a, 0x0c, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65,
-	0x65, 0x70, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01,
-	0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74,
-	0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12,
-	0x39, 0x0a, 0x0c, 0x77, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18,
-	0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70,
-	0x63, 0x2e, 0x57, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x77,
-	0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x6d,
-	0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09,
-	0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x61, 0x74, 0x12, 0x24, 0x0a, 0x0c, 0x73, 0x61, 0x74,
-	0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x42,
-	0x02, 0x18, 0x01, 0x52, 0x0a, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12,
-	0x2d, 0x0a, 0x12, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x5f, 0x61, 0x74, 0x74,
-	0x65, 0x6d, 0x70, 0x74, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x62, 0x72, 0x6f,
-	0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73, 0x12, 0x32,
-	0x0a, 0x15, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74,
-	0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x13, 0x6e,
-	0x65, 0x78, 0x74, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x48, 0x65, 0x69, 0x67,
-	0x68, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f,
-	0x63, 0x6f, 0x6e, 0x66, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28,
-	0x0d, 0x52, 0x13, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x66,
-	0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x37, 0x0a, 0x16, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73,
-	0x74, 0x65, 0x64, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65,
-	0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x13, 0x72, 0x65, 0x71, 0x75,
-	0x65, 0x73, 0x74, 0x65, 0x64, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12,
-	0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65,
-	0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62,
-	0x79, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x17, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64,
-	0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x0b,
-	0x20, 0x01, 0x28, 0x04, 0x52, 0x14, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x53,
-	0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f,
-	0x72, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65,
-	0x22, 0x16, 0x0a, 0x14, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70,
-	0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x57, 0x0a, 0x15, 0x50, 0x65, 0x6e, 0x64,
-	0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
-	0x65, 0x12, 0x3e, 0x0a, 0x0e, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x77, 0x65,
-	0x65, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x77, 0x61, 0x6c, 0x6c,
-	0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65,
-	0x65, 0x70, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70,
-	0x73, 0x22, 0xbe, 0x01, 0x0a, 0x0e, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71,
-	0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74,
-	0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f,
-	0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e,
-	0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66,
-	0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f,
-	0x6e, 0x66, 0x12, 0x24, 0x0a, 0x0c, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79,
-	0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x73, 0x61,
-	0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63,
-	0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x22,
-	0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18,
-	0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79,
-	0x74, 0x65, 0x22, 0x11, 0x0a, 0x0f, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73,
-	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2d, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x65,
-	0x65, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65,
-	0x72, 0x62, 0x6f, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x76, 0x65, 0x72,
-	0x62, 0x6f, 0x73, 0x65, 0x22, 0x80, 0x02, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x65,
-	0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x13, 0x74,
-	0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69,
-	0x6c, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 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, 0x48, 0x00, 0x52, 0x12, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69,
-	0x6f, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x57, 0x0a, 0x0f, 0x74, 0x72, 0x61,
-	0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x01,
-	0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c,
-	0x69, 0x73, 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
-	0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x73,
-	0x48, 0x00, 0x52, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49,
-	0x64, 0x73, 0x1a, 0x39, 0x0a, 0x0e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f,
-	0x6e, 0x49, 0x44, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74,
-	0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x74,
-	0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, 0x42, 0x08, 0x0a,
-	0x06, 0x73, 0x77, 0x65, 0x65, 0x70, 0x73, 0x22, 0x61, 0x0a, 0x17, 0x4c, 0x61, 0x62, 0x65, 0x6c,
-	0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65,
-	0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,
-	0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18,
-	0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x1c, 0x0a, 0x09,
-	0x6f, 0x76, 0x65, 0x72, 0x77, 0x72, 0x69, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52,
-	0x09, 0x6f, 0x76, 0x65, 0x72, 0x77, 0x72, 0x69, 0x74, 0x65, 0x22, 0x1a, 0x0a, 0x18, 0x4c, 0x61,
-	0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65,
-	0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x93, 0x02, 0x0a, 0x0f, 0x46, 0x75, 0x6e, 0x64, 0x50,
-	0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x04, 0x70, 0x73,
-	0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x04, 0x70, 0x73, 0x62, 0x74,
-	0x12, 0x29, 0x0a, 0x03, 0x72, 0x61, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e,
-	0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x78, 0x54, 0x65, 0x6d, 0x70,
-	0x6c, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x03, 0x72, 0x61, 0x77, 0x12, 0x21, 0x0a, 0x0b, 0x74,
-	0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d,
-	0x48, 0x01, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x24,
-	0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18,
-	0x04, 0x20, 0x01, 0x28, 0x04, 0x48, 0x01, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56,
-	0x62, 0x79, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18,
-	0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1b,
-	0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28,
-	0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x73,
-	0x70, 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64,
-	0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63,
-	0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x42, 0x0a, 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70,
-	0x6c, 0x61, 0x74, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x66, 0x65, 0x65, 0x73, 0x22, 0x9c, 0x01, 0x0a,
-	0x10, 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
-	0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74,
-	0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x73,
-	0x62, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x6f, 0x75, 0x74,
-	0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52,
-	0x11, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64,
-	0x65, 0x78, 0x12, 0x37, 0x0a, 0x0c, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x75, 0x74, 0x78,
-	0x6f, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65,
-	0x74, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x52, 0x0b,
-	0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x22, 0xaf, 0x01, 0x0a, 0x0a,
-	0x54, 0x78, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x27, 0x0a, 0x06, 0x69, 0x6e,
-	0x70, 0x75, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72,
-	0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x06, 0x69, 0x6e, 0x70,
-	0x75, 0x74, 0x73, 0x12, 0x3c, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x18, 0x02,
-	0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63,
-	0x2e, 0x54, 0x78, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x2e, 0x4f, 0x75, 0x74, 0x70,
-	0x75, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74,
-	0x73, 0x1a, 0x3a, 0x0a, 0x0c, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 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, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01,
-	0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x68, 0x0a,
-	0x09, 0x55, 0x74, 0x78, 0x6f, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
-	0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75,
-	0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c,
-	0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f,
-	0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72,
-	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x65, 0x78, 0x70,
-	0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x32, 0x0a, 0x0f, 0x53, 0x69, 0x67, 0x6e, 0x50,
-	0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x75,
-	0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52,
-	0x0a, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x22, 0x33, 0x0a, 0x10, 0x53,
-	0x69, 0x67, 0x6e, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
-	0x1f, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01,
-	0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74,
-	0x22, 0x50, 0x0a, 0x13, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74,
-	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x75, 0x6e, 0x64, 0x65,
-	0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x75,
-	0x6e, 0x64, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f,
+	0x77, 0x12, 0x28, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03,
+	0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x78, 0x4f,
+	0x75, 0x74, 0x52, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6c,
+	0x61, 0x62, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65,
+	0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x04,
+	0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x2b,
+	0x0a, 0x11, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72,
+	0x6d, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64,
+	0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x22, 0x2c, 0x0a, 0x13, 0x53,
+	0x65, 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
+	0x73, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x61, 0x77, 0x5f, 0x74, 0x78, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x0c, 0x52, 0x05, 0x72, 0x61, 0x77, 0x54, 0x78, 0x22, 0x35, 0x0a, 0x12, 0x45, 0x73, 0x74,
+	0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
+	0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x66, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74,
+	0x22, 0x33, 0x0a, 0x13, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52,
+	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x0a, 0x73, 0x61, 0x74, 0x5f, 0x70,
+	0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x73, 0x61, 0x74,
+	0x50, 0x65, 0x72, 0x4b, 0x77, 0x22, 0xfc, 0x03, 0x0a, 0x0c, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e,
+	0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69,
+	0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
+	0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f,
+	0x69, 0x6e, 0x74, 0x12, 0x39, 0x0a, 0x0c, 0x77, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x5f, 0x74,
+	0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c,
+	0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70,
+	0x65, 0x52, 0x0b, 0x77, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d,
+	0x0a, 0x0a, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01,
+	0x28, 0x0d, 0x52, 0x09, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x61, 0x74, 0x12, 0x24, 0x0a,
+	0x0c, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x04, 0x20,
+	0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42,
+	0x79, 0x74, 0x65, 0x12, 0x2d, 0x0a, 0x12, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74,
+	0x5f, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52,
+	0x11, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70,
+	0x74, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x62, 0x72, 0x6f, 0x61, 0x64,
+	0x63, 0x61, 0x73, 0x74, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28,
+	0x0d, 0x52, 0x13, 0x6e, 0x65, 0x78, 0x74, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74,
+	0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73,
+	0x74, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18,
+	0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x13, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64,
+	0x43, 0x6f, 0x6e, 0x66, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x37, 0x0a, 0x16, 0x72, 0x65,
+	0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f,
+	0x62, 0x79, 0x74, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x13,
+	0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42,
+	0x79, 0x74, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76,
+	0x62, 0x79, 0x74, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50,
+	0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x17, 0x72, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79,
+	0x74, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x14, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73,
+	0x74, 0x65, 0x64, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x14,
+	0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66,
+	0x6f, 0x72, 0x63, 0x65, 0x22, 0x16, 0x0a, 0x14, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53,
+	0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x57, 0x0a, 0x15,
+	0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73,
+	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x0e, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67,
+	0x5f, 0x73, 0x77, 0x65, 0x65, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e,
+	0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e,
+	0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53,
+	0x77, 0x65, 0x65, 0x70, 0x73, 0x22, 0xbe, 0x01, 0x0a, 0x0e, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65,
+	0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70,
+	0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72,
+	0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74,
+	0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f,
+	0x63, 0x6f, 0x6e, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67,
+	0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x24, 0x0a, 0x0c, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65,
+	0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01,
+	0x52, 0x0a, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05,
+	0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72,
+	0x63, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62,
+	0x79, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65,
+	0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x22, 0x11, 0x0a, 0x0f, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65,
+	0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2d, 0x0a, 0x11, 0x4c, 0x69, 0x73,
+	0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18,
+	0x0a, 0x07, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52,
+	0x07, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x22, 0x80, 0x02, 0x0a, 0x12, 0x4c, 0x69, 0x73,
+	0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
+	0x4c, 0x0a, 0x13, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64,
+	0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 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, 0x48, 0x00, 0x52, 0x12, 0x74, 0x72, 0x61, 0x6e, 0x73,
+	0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x57, 0x0a,
+	0x0f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72,
+	0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73,
+	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f,
+	0x6e, 0x49, 0x44, 0x73, 0x48, 0x00, 0x52, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74,
+	0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, 0x1a, 0x39, 0x0a, 0x0e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61,
+	0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x72, 0x61, 0x6e,
+	0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,
+	0x09, 0x52, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64,
+	0x73, 0x42, 0x08, 0x0a, 0x06, 0x73, 0x77, 0x65, 0x65, 0x70, 0x73, 0x22, 0x61, 0x0a, 0x17, 0x4c,
+	0x61, 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52,
+	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61,
+	0x62, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c,
+	0x12, 0x1c, 0x0a, 0x09, 0x6f, 0x76, 0x65, 0x72, 0x77, 0x72, 0x69, 0x74, 0x65, 0x18, 0x03, 0x20,
+	0x01, 0x28, 0x08, 0x52, 0x09, 0x6f, 0x76, 0x65, 0x72, 0x77, 0x72, 0x69, 0x74, 0x65, 0x22, 0x1a,
+	0x0a, 0x18, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69,
+	0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x93, 0x02, 0x0a, 0x0f, 0x46,
+	0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14,
+	0x0a, 0x04, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x04,
+	0x70, 0x73, 0x62, 0x74, 0x12, 0x29, 0x0a, 0x03, 0x72, 0x61, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x15, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x78,
+	0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x03, 0x72, 0x61, 0x77, 0x12,
+	0x21, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x03,
+	0x20, 0x01, 0x28, 0x0d, 0x48, 0x01, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f,
+	0x6e, 0x66, 0x12, 0x24, 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62,
+	0x79, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x48, 0x01, 0x52, 0x0b, 0x73, 0x61, 0x74,
+	0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f,
 	0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75,
-	0x6e, 0x74, 0x22, 0x59, 0x0a, 0x14, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73,
-	0x62, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x69,
-	0x67, 0x6e, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52,
-	0x0a, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x20, 0x0a, 0x0c, 0x72,
-	0x61, 0x77, 0x5f, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x74, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28,
-	0x0c, 0x52, 0x0a, 0x72, 0x61, 0x77, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x54, 0x78, 0x22, 0x13, 0x0a,
-	0x11, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
-	0x73, 0x74, 0x22, 0x4d, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73,
-	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0c, 0x6c, 0x6f, 0x63, 0x6b,
-	0x65, 0x64, 0x5f, 0x75, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14,
-	0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x4c,
-	0x65, 0x61, 0x73, 0x65, 0x52, 0x0b, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x55, 0x74, 0x78, 0x6f,
-	0x73, 0x2a, 0x7a, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65,
-	0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x17, 0x0a,
-	0x13, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f,
-	0x48, 0x41, 0x53, 0x48, 0x10, 0x01, 0x12, 0x1e, 0x0a, 0x1a, 0x4e, 0x45, 0x53, 0x54, 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, 0x25, 0x0a, 0x21, 0x48, 0x59, 0x42, 0x52, 0x49, 0x44,
-	0x5f, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f,
-	0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x03, 0x2a, 0x99, 0x03,
-	0x0a, 0x0b, 0x57, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x13, 0x0a,
-	0x0f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53,
-	0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54,
-	0x5f, 0x54, 0x49, 0x4d, 0x45, 0x5f, 0x4c, 0x4f, 0x43, 0x4b, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13,
-	0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x4f, 0x5f, 0x44, 0x45,
-	0x4c, 0x41, 0x59, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d,
-	0x45, 0x4e, 0x54, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x03, 0x12, 0x17, 0x0a, 0x13,
-	0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x56,
-	0x4f, 0x4b, 0x45, 0x10, 0x04, 0x12, 0x18, 0x0a, 0x14, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43,
-	0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x05, 0x12,
-	0x25, 0x0a, 0x21, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f,
-	0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c,
-	0x45, 0x56, 0x45, 0x4c, 0x10, 0x06, 0x12, 0x26, 0x0a, 0x22, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41,
-	0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x5f,
-	0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x10, 0x07, 0x12, 0x1f,
-	0x0a, 0x1b, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, 0x52,
-	0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x08, 0x12,
-	0x20, 0x0a, 0x1c, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44,
-	0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10,
-	0x09, 0x12, 0x1c, 0x0a, 0x18, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44,
-	0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x0a, 0x12,
-	0x14, 0x0a, 0x10, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x48,
-	0x41, 0x53, 0x48, 0x10, 0x0b, 0x12, 0x1b, 0x0a, 0x17, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f,
-	0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48,
-	0x10, 0x0c, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54,
-	0x5f, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x10, 0x0d, 0x32, 0xf7, 0x0b, 0x0a, 0x09, 0x57, 0x61,
-	0x6c, 0x6c, 0x65, 0x74, 0x4b, 0x69, 0x74, 0x12, 0x4c, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x55,
-	0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72,
-	0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65,
-	0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 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, 0x0b, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75,
-	0x74, 0x70, 0x75, 0x74, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63,
-	0x2e, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75,
-	0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e,
-	0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f,
-	0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75,
-	0x74, 0x70, 0x75, 0x74, 0x12, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63,
-	0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65,
-	0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70,
-	0x63, 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52,
-	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x4c,
-	0x65, 0x61, 0x73, 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70,
-	0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75,
-	0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e,
-	0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
-	0x73, 0x65, 0x12, 0x3a, 0x0a, 0x0d, 0x44, 0x65, 0x72, 0x69, 0x76, 0x65, 0x4e, 0x65, 0x78, 0x74,
-	0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e,
-	0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x16, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63,
-	0x2e, 0x4b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x38,
-	0x0a, 0x09, 0x44, 0x65, 0x72, 0x69, 0x76, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x13, 0x2e, 0x73, 0x69,
-	0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72,
-	0x1a, 0x16, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x44, 0x65,
-	0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x3b, 0x0a, 0x08, 0x4e, 0x65, 0x78, 0x74,
-	0x41, 0x64, 0x64, 0x72, 0x12, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63,
-	0x2e, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x77,
-	0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x73,
-	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63,
-	0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70,
-	0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65,
-	0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70,
-	0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65,
-	0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74,
-	0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74,
-	0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e,
-	0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65,
-	0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75,
-	0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x0f, 0x49, 0x6d,
-	0x70, 0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x21, 0x2e,
-	0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74,
-	0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
-	0x1a, 0x22, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70,
-	0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70,
-	0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a, 0x12, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x54,
-	0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x2e, 0x77, 0x61, 0x6c,
-	0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69,
-	0x6f, 0x6e, 0x1a, 0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50,
-	0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c,
-	0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x12, 0x1d, 0x2e,
-	0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x75,
-	0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77,
-	0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x75, 0x74,
-	0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x0b,
-	0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x12, 0x1d, 0x2e, 0x77, 0x61,
-	0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65,
-	0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x6c,
-	0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46,
-	0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x50, 0x65,
-	0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x12, 0x1f, 0x2e, 0x77, 0x61,
-	0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53,
-	0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x77,
-	0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67,
-	0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40,
-	0x0a, 0x07, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, 0x65, 0x12, 0x19, 0x2e, 0x77, 0x61, 0x6c, 0x6c,
-	0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71,
-	0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63,
-	0x2e, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
-	0x12, 0x49, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x12, 0x1c,
-	0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53,
-	0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x77,
-	0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x65,
-	0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, 0x10, 0x4c,
-	0x61, 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12,
-	0x22, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x61, 0x62, 0x65,
-	0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75,
-	0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e,
+	0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18,
+	0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12,
+	0x2b, 0x0a, 0x11, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69,
+	0x72, 0x6d, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e,
+	0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x42, 0x0a, 0x0a, 0x08,
+	0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x66, 0x65, 0x65, 0x73,
+	0x22, 0x9c, 0x01, 0x0a, 0x10, 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73,
+	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x5f,
+	0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x75, 0x6e, 0x64,
+	0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65,
+	0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20,
+	0x01, 0x28, 0x05, 0x52, 0x11, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75,
+	0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x37, 0x0a, 0x0c, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64,
+	0x5f, 0x75, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x77,
+	0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x4c, 0x65, 0x61,
+	0x73, 0x65, 0x52, 0x0b, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x22,
+	0xaf, 0x01, 0x0a, 0x0a, 0x54, 0x78, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x27,
+	0x0a, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f,
+	0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52,
+	0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x12, 0x3c, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75,
+	0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65,
+	0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x78, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x2e,
+	0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x6f, 0x75,
+	0x74, 0x70, 0x75, 0x74, 0x73, 0x1a, 0x3a, 0x0a, 0x0c, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 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, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38,
+	0x01, 0x22, 0x68, 0x0a, 0x09, 0x55, 0x74, 0x78, 0x6f, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x12, 0x0e,
+	0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x2b,
+	0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e,
+	0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x65,
+	0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52,
+	0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x32, 0x0a, 0x0f, 0x53,
+	0x69, 0x67, 0x6e, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f,
+	0x0a, 0x0b, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x22,
+	0x33, 0x0a, 0x10, 0x53, 0x69, 0x67, 0x6e, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f,
+	0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x70, 0x73,
+	0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64,
+	0x50, 0x73, 0x62, 0x74, 0x22, 0x50, 0x0a, 0x13, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65,
+	0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x66,
+	0x75, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,
+	0x52, 0x0a, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x18, 0x0a, 0x07,
+	0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61,
+	0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x59, 0x0a, 0x14, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69,
+	0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f,
+	0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12,
+	0x20, 0x0a, 0x0c, 0x72, 0x61, 0x77, 0x5f, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x74, 0x78, 0x18,
+	0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x72, 0x61, 0x77, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x54,
+	0x78, 0x22, 0x13, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x52,
+	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x4d, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65,
+	0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0c,
+	0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x75, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x03,
+	0x28, 0x0b, 0x32, 0x14, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x55,
+	0x74, 0x78, 0x6f, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x52, 0x0b, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64,
+	0x55, 0x74, 0x78, 0x6f, 0x73, 0x2a, 0x7a, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
+	0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10,
+	0x00, 0x12, 0x17, 0x0a, 0x13, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, 0x42,
+	0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x01, 0x12, 0x1e, 0x0a, 0x1a, 0x4e, 0x45,
+	0x53, 0x54, 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, 0x25, 0x0a, 0x21, 0x48, 0x59,
+	0x42, 0x52, 0x49, 0x44, 0x5f, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e,
+	0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10,
+	0x03, 0x2a, 0x99, 0x03, 0x0a, 0x0b, 0x57, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70,
+	0x65, 0x12, 0x13, 0x0a, 0x0f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x57, 0x49, 0x54,
+	0x4e, 0x45, 0x53, 0x53, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54,
+	0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x5f, 0x4c, 0x4f, 0x43, 0x4b, 0x10, 0x01,
+	0x12, 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x4e,
+	0x4f, 0x5f, 0x44, 0x45, 0x4c, 0x41, 0x59, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4d,
+	0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x03,
+	0x12, 0x17, 0x0a, 0x13, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44,
+	0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x04, 0x12, 0x18, 0x0a, 0x14, 0x48, 0x54, 0x4c,
+	0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b,
+	0x45, 0x10, 0x05, 0x12, 0x25, 0x0a, 0x21, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45,
+	0x52, 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x5f, 0x53, 0x45, 0x43, 0x4f,
+	0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x10, 0x06, 0x12, 0x26, 0x0a, 0x22, 0x48, 0x54,
+	0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x53, 0x55, 0x43, 0x43,
+	0x45, 0x53, 0x53, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c,
+	0x10, 0x07, 0x12, 0x1f, 0x0a, 0x1b, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52,
+	0x45, 0x44, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55,
+	0x54, 0x10, 0x08, 0x12, 0x20, 0x0a, 0x1c, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45,
+	0x50, 0x54, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x53, 0x55, 0x43, 0x43,
+	0x45, 0x53, 0x53, 0x10, 0x09, 0x12, 0x1c, 0x0a, 0x18, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x53, 0x45,
+	0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b,
+	0x45, 0x10, 0x0a, 0x12, 0x14, 0x0a, 0x10, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x4b,
+	0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x0b, 0x12, 0x1b, 0x0a, 0x17, 0x4e, 0x45, 0x53,
+	0x54, 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x4b, 0x45, 0x59, 0x5f,
+	0x48, 0x41, 0x53, 0x48, 0x10, 0x0c, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54,
+	0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x10, 0x0d, 0x32, 0xf7, 0x0b,
+	0x0a, 0x09, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x4b, 0x69, 0x74, 0x12, 0x4c, 0x0a, 0x0b, 0x4c,
+	0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c,
+	0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65,
+	0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c,
+	0x65, 0x74, 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, 0x0b, 0x4c, 0x65, 0x61,
+	0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65,
+	0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74,
+	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74,
+	0x72, 0x70, 0x63, 0x2e, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52,
+	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x52, 0x65, 0x6c, 0x65, 0x61,
+	0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65,
+	0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70,
+	0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x77, 0x61, 0x6c, 0x6c,
+	0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74,
+	0x70, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0a, 0x4c,
+	0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x77, 0x61, 0x6c, 0x6c,
+	0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73,
+	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74,
+	0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65,
+	0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x0d, 0x44, 0x65, 0x72, 0x69, 0x76, 0x65,
+	0x4e, 0x65, 0x78, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74,
+	0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x16, 0x2e, 0x73, 0x69, 0x67,
+	0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
+	0x6f, 0x72, 0x12, 0x38, 0x0a, 0x09, 0x44, 0x65, 0x72, 0x69, 0x76, 0x65, 0x4b, 0x65, 0x79, 0x12,
+	0x13, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63,
+	0x61, 0x74, 0x6f, 0x72, 0x1a, 0x16, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b,
+	0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x3b, 0x0a, 0x08,
+	0x4e, 0x65, 0x78, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65,
+	0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+	0x1a, 0x17, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64,
+	0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x0c, 0x4c, 0x69, 0x73,
+	0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c,
+	0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e,
+	0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c,
+	0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e,
+	0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x49, 0x6d,
+	0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x2e, 0x77, 0x61,
+	0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63,
+	0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x77,
+	0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41,
+	0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58,
+	0x0a, 0x0f, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65,
+	0x79, 0x12, 0x21, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d,
+	0x70, 0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71,
+	0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63,
+	0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79,
+	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a, 0x12, 0x50, 0x75, 0x62, 0x6c,
+	0x69, 0x73, 0x68, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16,
+	0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73,
+	0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72,
+	0x70, 0x63, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
+	0x73, 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74,
+	0x73, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65,
+	0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+	0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e,
+	0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
+	0x12, 0x4c, 0x0a, 0x0b, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x12,
+	0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69,
+	0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e,
+	0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d,
+	0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52,
+	0x0a, 0x0d, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x12,
+	0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64,
+	0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+	0x1a, 0x20, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e,
+	0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
+	0x73, 0x65, 0x12, 0x40, 0x0a, 0x07, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, 0x65, 0x12, 0x19, 0x2e,
+	0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65,
+	0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65,
+	0x74, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70,
+	0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x65, 0x65,
+	0x70, 0x73, 0x12, 0x1c, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c,
+	0x69, 0x73, 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+	0x1a, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73,
+	0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
+	0x5b, 0x0a, 0x10, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74,
+	0x69, 0x6f, 0x6e, 0x12, 0x22, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e,
 	0x4c, 0x61, 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e,
-	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x08, 0x46, 0x75, 0x6e, 0x64,
-	0x50, 0x73, 0x62, 0x74, 0x12, 0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63,
-	0x2e, 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
-	0x1a, 0x1b, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e,
-	0x64, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a,
-	0x08, 0x53, 0x69, 0x67, 0x6e, 0x50, 0x73, 0x62, 0x74, 0x12, 0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c,
+	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74,
+	0x72, 0x70, 0x63, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63,
+	0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x08,
+	0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65,
+	0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71,
+	0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63,
+	0x2e, 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
+	0x65, 0x12, 0x43, 0x0a, 0x08, 0x53, 0x69, 0x67, 0x6e, 0x50, 0x73, 0x62, 0x74, 0x12, 0x1a, 0x2e,
+	0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x50, 0x73,
+	0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x77, 0x61, 0x6c, 0x6c,
 	0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65,
-	0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70,
-	0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
-	0x73, 0x65, 0x12, 0x4f, 0x0a, 0x0c, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73,
-	0x62, 0x74, 0x12, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46,
-	0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
-	0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46,
-	0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f,
-	0x6e, 0x73, 0x65, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
-	0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f,
-	0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x77, 0x61, 0x6c,
-	0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x0c, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69,
+	0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x12, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72,
+	0x70, 0x63, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x52,
+	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72,
+	0x70, 0x63, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x52,
+	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75,
+	0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e,
+	0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63,
+	0x2f, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x33,
 }
 
 var (
diff --git a/lnrpc/walletrpc/walletkit.proto b/lnrpc/walletrpc/walletkit.proto
index ccabbacf3..84ff7a688 100644
--- a/lnrpc/walletrpc/walletkit.proto
+++ b/lnrpc/walletrpc/walletkit.proto
@@ -12,9 +12,7 @@ option go_package = "github.com/lightningnetwork/lnd/lnrpc/walletrpc";
 service WalletKit {
     /*
     ListUnspent returns a list of all utxos spendable by the wallet with a
-    number of confirmations between the specified minimum and maximum. By
-    default, all utxos are listed. To list only the unconfirmed utxos, set
-    the unconfirmed_only to true.
+    number of confirmations between the specified minimum and maximum.
     */
     rpc ListUnspent (ListUnspentRequest) returns (ListUnspentResponse);
 
@@ -242,14 +240,6 @@ message ListUnspentRequest {
 
     // An optional filter to only include outputs belonging to an account.
     string account = 3;
-
-    /*
-    When min_confs and max_confs are zero, setting false implicitly
-    overrides max_confs to be MaxInt32, otherwise max_confs remains
-    zero. An error is returned if the value is true and both min_confs
-    and max_confs are non-zero. (default: false)
-    */
-    bool unconfirmed_only = 4;
 }
 
 message ListUnspentResponse {
diff --git a/lnrpc/walletrpc/walletkit.swagger.json b/lnrpc/walletrpc/walletkit.swagger.json
index b4f3a00ea..c40c66691 100644
--- a/lnrpc/walletrpc/walletkit.swagger.json
+++ b/lnrpc/walletrpc/walletkit.swagger.json
@@ -555,7 +555,7 @@
     },
     "/v2/wallet/utxos": {
       "post": {
-        "summary": "ListUnspent returns a list of all utxos spendable by the wallet with a\nnumber of confirmations between the specified minimum and maximum. By\ndefault, all utxos are listed. To list only the unconfirmed utxos, set\nthe unconfirmed_only to true.",
+        "summary": "ListUnspent returns a list of all utxos spendable by the wallet with a\nnumber of confirmations between the specified minimum and maximum.",
         "operationId": "WalletKit_ListUnspent",
         "responses": {
           "200": {
@@ -1276,10 +1276,6 @@
         "account": {
           "type": "string",
           "description": "An optional filter to only include outputs belonging to an account."
-        },
-        "unconfirmed_only": {
-          "type": "boolean",
-          "title": "When min_confs and max_confs are zero, setting false implicitly\noverrides max_confs to be MaxInt32, otherwise max_confs remains\nzero. An error is returned if the value is true and both min_confs\nand max_confs are non-zero. (default: false)"
         }
       }
     },
diff --git a/lnrpc/walletrpc/walletkit_grpc.pb.go b/lnrpc/walletrpc/walletkit_grpc.pb.go
index b037c83e0..b61229945 100644
--- a/lnrpc/walletrpc/walletkit_grpc.pb.go
+++ b/lnrpc/walletrpc/walletkit_grpc.pb.go
@@ -21,9 +21,7 @@ const _ = grpc.SupportPackageIsVersion7
 type WalletKitClient interface {
 	//
 	//ListUnspent returns a list of all utxos spendable by the wallet with a
-	//number of confirmations between the specified minimum and maximum. By
-	//default, all utxos are listed. To list only the unconfirmed utxos, set
-	//the unconfirmed_only to true.
+	//number of confirmations between the specified minimum and maximum.
 	ListUnspent(ctx context.Context, in *ListUnspentRequest, opts ...grpc.CallOption) (*ListUnspentResponse, error)
 	//
 	//LeaseOutput locks an output to the given ID, preventing it from being
@@ -395,9 +393,7 @@ func (c *walletKitClient) FinalizePsbt(ctx context.Context, in *FinalizePsbtRequ
 type WalletKitServer interface {
 	//
 	//ListUnspent returns a list of all utxos spendable by the wallet with a
-	//number of confirmations between the specified minimum and maximum. By
-	//default, all utxos are listed. To list only the unconfirmed utxos, set
-	//the unconfirmed_only to true.
+	//number of confirmations between the specified minimum and maximum.
 	ListUnspent(context.Context, *ListUnspentRequest) (*ListUnspentResponse, error)
 	//
 	//LeaseOutput locks an output to the given ID, preventing it from being
diff --git a/lnrpc/walletrpc/walletkit_server.go b/lnrpc/walletrpc/walletkit_server.go
index 971ddac98..15f2bd798 100644
--- a/lnrpc/walletrpc/walletkit_server.go
+++ b/lnrpc/walletrpc/walletkit_server.go
@@ -10,7 +10,6 @@ import (
 	"errors"
 	"fmt"
 	"io/ioutil"
-	"math"
 	"os"
 	"path/filepath"
 	"time"
@@ -323,29 +322,16 @@ func (w *WalletKit) internalScope() waddrmgr.KeyScope {
 	}
 }
 
-// ListUnspent returns useful information about each unspent output owned by
-// the wallet, as reported by the underlying `ListUnspentWitness`; the
-// information returned is: outpoint, amount in satoshis, address, address
-// type, scriptPubKey in hex and number of confirmations. The result is
-// filtered to contain outputs whose number of confirmations is between a
-// minimum and maximum number of confirmations specified by the user.
+// ListUnspent returns useful information about each unspent output owned by the
+// wallet, as reported by the underlying `ListUnspentWitness`; the information
+// returned is: outpoint, amount in satoshis, address, address type,
+// scriptPubKey in hex and number of confirmations.  The result is filtered to
+// contain outputs whose number of confirmations is between a
+// minimum and maximum number of confirmations specified by the user, with 0
+// meaning unconfirmed.
 func (w *WalletKit) ListUnspent(ctx context.Context,
 	req *ListUnspentRequest) (*ListUnspentResponse, error) {
 
-	// Force min_confs and max_confs to be zero if unconfirmed_only is
-	// true.
-	if req.UnconfirmedOnly && (req.MinConfs != 0 || req.MaxConfs != 0) {
-		return nil, fmt.Errorf("min_confs and max_confs must be zero if " +
-			"unconfirmed_only is true")
-	}
-
-	// When unconfirmed_only is inactive and max_confs is zero (default
-	// values), we will override max_confs to be a MaxInt32, in order
-	// to return all confirmed and unconfirmed utxos as a default response.
-	if req.MaxConfs == 0 && !req.UnconfirmedOnly {
-		req.MaxConfs = math.MaxInt32
-	}
-
 	// Validate the confirmation arguments.
 	minConfs, maxConfs, err := lnrpc.ParseConfs(req.MinConfs, req.MaxConfs)
 	if err != nil {
diff --git a/rpcserver.go b/rpcserver.go
index a1ec2b847..30972ca38 100644
--- a/rpcserver.go
+++ b/rpcserver.go
@@ -1078,13 +1078,13 @@ func (r *rpcServer) sendCoinsOnChain(paymentMap map[string]int64,
 	return &txHash, nil
 }
 
-// ListUnspent returns useful information about each unspent output owned by
-// the wallet, as reported by the underlying `ListUnspentWitness`; the
-// information returned is: outpoint, amount in satoshis, address, address
-// type, scriptPubKey in hex and number of confirmations.  The result is
-// filtered to contain outputs whose number of confirmations is between a
-// minimum and maximum number of confirmations specified by the user, with
-// 0 meaning unconfirmed.
+// ListUnspent returns useful information about each unspent output owned by the
+// wallet, as reported by the underlying `ListUnspentWitness`; the information
+// returned is: outpoint, amount in satoshis, address, address type,
+// scriptPubKey in hex and number of confirmations.  The result is filtered to
+// contain outputs whose number of confirmations is between a minimum and
+// maximum number of confirmations specified by the user, with 0 meaning
+// unconfirmed.
 func (r *rpcServer) ListUnspent(ctx context.Context,
 	in *lnrpc.ListUnspentRequest) (*lnrpc.ListUnspentResponse, error) {
 
diff --git a/sample-lnd.conf b/sample-lnd.conf
index 714aaea7d..029547e06 100644
--- a/sample-lnd.conf
+++ b/sample-lnd.conf
@@ -347,6 +347,9 @@
 ; used as a hop.
 ; rejecthtlc=true
 
+; If true, all HTLCs will be held until they are handled by an interceptor
+; requireinterceptor=true
+
 ; If true, will apply a randomized staggering between 0s and 30s when
 ; reconnecting to persistent peers on startup. The first 10 reconnections will be
 ; attempted instantly, regardless of the flag's value
diff --git a/server.go b/server.go
index 632620526..28ac51807 100644
--- a/server.go
+++ b/server.go
@@ -654,7 +654,9 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
 	if err != nil {
 		return nil, err
 	}
-	s.interceptableSwitch = htlcswitch.NewInterceptableSwitch(s.htlcSwitch)
+	s.interceptableSwitch = htlcswitch.NewInterceptableSwitch(
+		s.htlcSwitch, s.cfg.RequireInterceptor,
+	)
 
 	chanStatusMgrCfg := &netann.ChanStatusConfig{
 		ChanStatusSampleInterval: cfg.ChanStatusSampleInterval,
@@ -1786,6 +1788,12 @@ func (s *server) Start() error {
 		}
 		cleanup = cleanup.add(s.htlcSwitch.Stop)
 
+		if err := s.interceptableSwitch.Start(); err != nil {
+			startErr = err
+			return
+		}
+		cleanup = cleanup.add(s.interceptableSwitch.Stop)
+
 		if err := s.chainArb.Start(); err != nil {
 			startErr = err
 			return


Test patch:
diff --git a/htlcswitch/link_test.go b/htlcswitch/link_test.go
index d2cfdbcea..5320336ec 100644
--- a/htlcswitch/link_test.go
+++ b/htlcswitch/link_test.go
@@ -1940,12 +1940,14 @@ func newSingleLinkTestHarness(chanAmt, chanReserve btcutil.Amount) (
 	// the firing via force feeding.
 	bticker := ticker.NewForce(time.Hour)
 	aliceCfg := ChannelLinkConfig{
-		FwrdingPolicy:      globalPolicy,
-		Peer:               alicePeer,
-		Switch:             aliceSwitch,
-		BestHeight:         aliceSwitch.BestHeight,
-		Circuits:           aliceSwitch.CircuitModifier(),
-		ForwardPackets:     aliceSwitch.ForwardPackets,
+		FwrdingPolicy: globalPolicy,
+		Peer:          alicePeer,
+		Switch:        aliceSwitch,
+		BestHeight:    aliceSwitch.BestHeight,
+		Circuits:      aliceSwitch.CircuitModifier(),
+		ForwardPackets: func(linkQuit chan struct{}, _ bool, packets ...*htlcPacket) error {
+			return aliceSwitch.ForwardPackets(linkQuit, packets...)
+		},
 		DecodeHopIterators: decoder.DecodeHopIterators,
 		ExtractErrorEncrypter: func(*btcec.PublicKey) (
 			hop.ErrorEncrypter, lnwire.FailCode) {
@@ -4491,12 +4493,14 @@ func (h *persistentLinkHarness) restartLink(
 	// the firing via force feeding.
 	bticker := ticker.NewForce(time.Hour)
 	aliceCfg := ChannelLinkConfig{
-		FwrdingPolicy:      globalPolicy,
-		Peer:               alicePeer,
-		Switch:             aliceSwitch,
-		BestHeight:         aliceSwitch.BestHeight,
-		Circuits:           aliceSwitch.CircuitModifier(),
-		ForwardPackets:     aliceSwitch.ForwardPackets,
+		FwrdingPolicy: globalPolicy,
+		Peer:          alicePeer,
+		Switch:        aliceSwitch,
+		BestHeight:    aliceSwitch.BestHeight,
+		Circuits:      aliceSwitch.CircuitModifier(),
+		ForwardPackets: func(linkQuit chan struct{}, _ bool, packets ...*htlcPacket) error {
+			return aliceSwitch.ForwardPackets(linkQuit, packets...)
+		},
 		DecodeHopIterators: decoder.DecodeHopIterators,
 		ExtractErrorEncrypter: func(*btcec.PublicKey) (
 			hop.ErrorEncrypter, lnwire.FailCode) {
@@ -6694,7 +6698,7 @@ func TestPipelineSettle(t *testing.T) {
 	// erroneously forwarded. If the forwardChan is closed before the last
 	// step, then the test will fail.
 	forwardChan := make(chan struct{})
-	fwdPkts := func(c chan struct{}, hp ...*htlcPacket) error {
+	fwdPkts := func(c chan struct{}, _ bool, hp ...*htlcPacket) error {
 		close(forwardChan)
 		return nil
 	}
diff --git a/htlcswitch/switch_test.go b/htlcswitch/switch_test.go
index ffbca4907..148a19539 100644
--- a/htlcswitch/switch_test.go
+++ b/htlcswitch/switch_test.go
@@ -3140,32 +3140,29 @@ func getThreeHopEvents(channels *clusterChannels, htlcID uint64,
 }
 
 type mockForwardInterceptor struct {
-	intercepted InterceptedForward
+	t *testing.T
+
+	interceptedChan chan InterceptedPacket
 }
 
 func (m *mockForwardInterceptor) InterceptForwardHtlc(
-	intercepted InterceptedForward) bool {
+	intercepted InterceptedPacket) error {
 
-	m.intercepted = intercepted
-	return true
-}
+	m.interceptedChan <- intercepted
 
-func (m *mockForwardInterceptor) settle(preimage lntypes.Preimage) error {
-	return m.intercepted.Settle(preimage)
-}
-
-func (m *mockForwardInterceptor) fail(reason []byte) error {
-	return m.intercepted.Fail(reason)
+	return nil
 }
 
-func (m *mockForwardInterceptor) failWithCode(
-	code lnwire.FailCode) error {
+func (m *mockForwardInterceptor) getIntercepted() InterceptedPacket {
+	select {
+	case p := <-m.interceptedChan:
+		return p
 
-	return m.intercepted.FailWithCode(code)
-}
+	case <-time.After(time.Second):
+		require.Fail(m.t, "timeout")
 
-func (m *mockForwardInterceptor) resume() error {
-	return m.intercepted.Resume()
+		return InterceptedPacket{}
+	}
 }
 
 func assertNumCircuits(t *testing.T, s *Switch, pending, opened int) {
@@ -3272,22 +3269,28 @@ func TestSwitchHoldForward(t *testing.T) {
 		},
 	}
 
-	forwardInterceptor := &mockForwardInterceptor{}
-	switchForwardInterceptor := NewInterceptableSwitch(s)
+	forwardInterceptor := &mockForwardInterceptor{
+		t:               t,
+		interceptedChan: make(chan InterceptedPacket),
+	}
+	switchForwardInterceptor := NewInterceptableSwitch(s, false)
+	require.NoError(t, switchForwardInterceptor.Start())
+
 	switchForwardInterceptor.SetInterceptor(forwardInterceptor.InterceptForwardHtlc)
 	linkQuit := make(chan struct{})
 
-	// Test resume a hold forward
+	// Test resume a hold forward.
 	assertNumCircuits(t, s, 0, 0)
-	if err := switchForwardInterceptor.ForwardPackets(linkQuit, ogPacket); err != nil {
-		t.Fatalf("can't forward htlc packet: %v", err)
-	}
+	err = switchForwardInterceptor.ForwardPackets(linkQuit, false, ogPacket)
+	require.NoError(t, err)
+
 	assertNumCircuits(t, s, 0, 0)
 	assertOutgoingLinkReceive(t, bobChannelLink, false)
 
-	if err := forwardInterceptor.resume(); err != nil {
-		t.Fatalf("failed to resume forward")
-	}
+	require.NoError(t, switchForwardInterceptor.resolve(&FwdResolution{
+		Action: FwdActionResume,
+		Key:    forwardInterceptor.getIntercepted().IncomingCircuit,
+	}))
 	assertOutgoingLinkReceive(t, bobChannelLink, true)
 	assertNumCircuits(t, s, 1, 1)
 
@@ -3300,35 +3303,72 @@ func TestSwitchHoldForward(t *testing.T) {
 			PaymentPreimage: preimage,
 		},
 	}
-	if err := switchForwardInterceptor.ForwardPackets(linkQuit, settle); err != nil {
-		t.Fatalf("can't forward htlc packet: %v", err)
-	}
+	err = switchForwardInterceptor.ForwardPackets(linkQuit, false, settle)
+	require.NoError(t, err)
+
+	assertOutgoingLinkReceive(t, aliceChannelLink, true)
+	assertNumCircuits(t, s, 0, 0)
+
+	// Test resume a hold forward after disconnection.
+	require.NoError(t, switchForwardInterceptor.ForwardPackets(
+		linkQuit, false, ogPacket,
+	))
+
+	// Wait until the packet is offered to the interceptor.
+	_ = forwardInterceptor.getIntercepted()
+
+	// No forward expected yet.
+	assertNumCircuits(t, s, 0, 0)
+	assertOutgoingLinkReceive(t, bobChannelLink, false)
+
+	// Disconnect should resume the forwarding.
+	switchForwardInterceptor.SetInterceptor(nil)
+
+	assertOutgoingLinkReceive(t, bobChannelLink, true)
+	assertNumCircuits(t, s, 1, 1)
+
+	// Settle the htlc to close the circuit.
+	settle.outgoingHTLCID = 1
+	require.NoError(t, switchForwardInterceptor.ForwardPackets(
+		linkQuit, false, settle,
+	))
+
 	assertOutgoingLinkReceive(t, aliceChannelLink, true)
 	assertNumCircuits(t, s, 0, 0)
 
 	// Test failing a hold forward
-	if err := switchForwardInterceptor.ForwardPackets(linkQuit, ogPacket); err != nil {
-		t.Fatalf("can't forward htlc packet: %v", err)
-	}
+	switchForwardInterceptor.SetInterceptor(
+		forwardInterceptor.InterceptForwardHtlc,
+	)
+
+	require.NoError(t, switchForwardInterceptor.ForwardPackets(
+		linkQuit, false, ogPacket,
+	))
 	assertNumCircuits(t, s, 0, 0)
 	assertOutgoingLinkReceive(t, bobChannelLink, false)
 
-	if err := forwardInterceptor.fail(nil); err != nil {
-		t.Fatalf("failed to cancel forward %v", err)
-	}
+	require.NoError(t, switchForwardInterceptor.resolve(&FwdResolution{
+		Action:      FwdActionFail,
+		Key:         forwardInterceptor.getIntercepted().IncomingCircuit,
+		FailureCode: lnwire.CodeTemporaryChannelFailure,
+	}))
 	assertOutgoingLinkReceive(t, bobChannelLink, false)
 	assertOutgoingLinkReceive(t, aliceChannelLink, true)
 	assertNumCircuits(t, s, 0, 0)
 
 	// Test failing a hold forward with a failure message.
 	require.NoError(t,
-		switchForwardInterceptor.ForwardPackets(linkQuit, ogPacket),
+		switchForwardInterceptor.ForwardPackets(linkQuit, false, ogPacket),
 	)
 	assertNumCircuits(t, s, 0, 0)
 	assertOutgoingLinkReceive(t, bobChannelLink, false)
 
 	reason := lnwire.OpaqueReason([]byte{1, 2, 3})
-	require.NoError(t, forwardInterceptor.fail(reason))
+	require.NoError(t, switchForwardInterceptor.resolve(&FwdResolution{
+		Action:         FwdActionFail,
+		Key:            forwardInterceptor.getIntercepted().IncomingCircuit,
+		FailureMessage: reason,
+	}))
 
 	assertOutgoingLinkReceive(t, bobChannelLink, false)
 	packet := assertOutgoingLinkReceive(t, aliceChannelLink, true)
@@ -3338,14 +3378,18 @@ func TestSwitchHoldForward(t *testing.T) {
 	assertNumCircuits(t, s, 0, 0)
 
 	// Test failing a hold forward with a malformed htlc failure.
-	err = switchForwardInterceptor.ForwardPackets(linkQuit, ogPacket)
+	err = switchForwardInterceptor.ForwardPackets(linkQuit, false, ogPacket)
 	require.NoError(t, err)
 
 	assertNumCircuits(t, s, 0, 0)
 	assertOutgoingLinkReceive(t, bobChannelLink, false)
 
 	code := lnwire.CodeInvalidOnionKey
-	require.NoError(t, forwardInterceptor.failWithCode(code))
+	require.NoError(t, switchForwardInterceptor.resolve(&FwdResolution{
+		Action:      FwdActionFail,
+		Key:         forwardInterceptor.getIntercepted().IncomingCircuit,
+		FailureCode: code,
+	}))
 
 	assertOutgoingLinkReceive(t, bobChannelLink, false)
 	packet = assertOutgoingLinkReceive(t, aliceChannelLink, true)
@@ -3363,18 +3407,89 @@ func TestSwitchHoldForward(t *testing.T) {
 	assertNumCircuits(t, s, 0, 0)
 
 	// Test settling a hold forward
-	if err := switchForwardInterceptor.ForwardPackets(linkQuit, ogPacket); err != nil {
-		t.Fatalf("can't forward htlc packet: %v", err)
-	}
+	require.NoError(t, switchForwardInterceptor.ForwardPackets(
+		linkQuit, false, ogPacket,
+	))
 	assertNumCircuits(t, s, 0, 0)
 	assertOutgoingLinkReceive(t, bobChannelLink, false)
 
-	if err := forwardInterceptor.settle(preimage); err != nil {
-		t.Fatal("failed to cancel forward")
-	}
+	require.NoError(t, switchForwardInterceptor.resolve(&FwdResolution{
+		Key:      forwardInterceptor.getIntercepted().IncomingCircuit,
+		Action:   FwdActionSettle,
+		Preimage: preimage,
+	}))
+	assertOutgoingLinkReceive(t, bobChannelLink, false)
+	assertOutgoingLinkReceive(t, aliceChannelLink, true)
+	assertNumCircuits(t, s, 0, 0)
+
+	require.NoError(t, switchForwardInterceptor.Stop())
+
+	// Test always-on interception.
+	switchForwardInterceptor = NewInterceptableSwitch(s, true)
+	require.NoError(t, switchForwardInterceptor.Start())
+
+	// Forward a fresh packet. It is expected to be failed immediately,
+	// because there is no interceptor registered.
+	require.NoError(t, switchForwardInterceptor.ForwardPackets(
+		linkQuit, false, ogPacket,
+	))
+
+	assertOutgoingLinkReceive(t, bobChannelLink, false)
+	assertOutgoingLinkReceive(t, aliceChannelLink, true)
+	assertNumCircuits(t, s, 0, 0)
+
+	// Forward a replayed packet. It is expected to be held until the
+	// interceptor connects. To continue the test, it needs to be ran in a
+	// goroutine.
+	errChan := make(chan error)
+	go func() {
+		errChan <- switchForwardInterceptor.ForwardPackets(
+			linkQuit, true, ogPacket,
+		)
+	}()
+
+	// Assert that nothing is forward to the switch.
+	assertOutgoingLinkReceive(t, bobChannelLink, false)
+	assertNumCircuits(t, s, 0, 0)
+
+	// Register an interceptor.
+	switchForwardInterceptor.SetInterceptor(
+		forwardInterceptor.InterceptForwardHtlc,
+	)
+
+	// Expect the ForwardPackets call to unblock.
+	require.NoError(t, <-errChan)
+
+	// Now expect the queued packet to come through.
+	forwardInterceptor.getIntercepted()
+
+	// Disconnect and reconnect interceptor.
+	switchForwardInterceptor.SetInterceptor(nil)
+	switchForwardInterceptor.SetInterceptor(
+		forwardInterceptor.InterceptForwardHtlc,
+	)
+
+	// A replay of the held packet is expected.
+	intercepted := forwardInterceptor.getIntercepted()
+
+	// Settle the packet.
+	require.NoError(t, switchForwardInterceptor.resolve(&FwdResolution{
+		Key:      intercepted.IncomingCircuit,
+		Action:   FwdActionSettle,
+		Preimage: preimage,
+	}))
 	assertOutgoingLinkReceive(t, bobChannelLink, false)
 	assertOutgoingLinkReceive(t, aliceChannelLink, true)
 	assertNumCircuits(t, s, 0, 0)
+
+	require.NoError(t, switchForwardInterceptor.Stop())
+
+	select {
+	case <-forwardInterceptor.interceptedChan:
+		require.Fail(t, "unexpected interception")
+
+	default:
+	}
 }
 
 // TestSwitchDustForwarding tests that the switch properly fails HTLC's which
diff --git a/htlcswitch/test_utils.go b/htlcswitch/test_utils.go
index 014adfc52..7dfd1599c 100644
--- a/htlcswitch/test_utils.go
+++ b/htlcswitch/test_utils.go
@@ -1135,12 +1135,14 @@ func (h *hopNetwork) createChannelLink(server, peer *mockServer,
 
 	link := NewChannelLink(
 		ChannelLinkConfig{
-			Switch:             server.htlcSwitch,
-			BestHeight:         server.htlcSwitch.BestHeight,
-			FwrdingPolicy:      h.globalPolicy,
-			Peer:               peer,
-			Circuits:           server.htlcSwitch.CircuitModifier(),
-			ForwardPackets:     server.htlcSwitch.ForwardPackets,
+			Switch:        server.htlcSwitch,
+			BestHeight:    server.htlcSwitch.BestHeight,
+			FwrdingPolicy: h.globalPolicy,
+			Peer:          peer,
+			Circuits:      server.htlcSwitch.CircuitModifier(),
+			ForwardPackets: func(linkQuit chan struct{}, _ bool, packets ...*htlcPacket) error {
+				return server.htlcSwitch.ForwardPackets(linkQuit, packets...)
+			},
 			DecodeHopIterators: decoder.DecodeHopIterators,
 			ExtractErrorEncrypter: func(*btcec.PublicKey) (
 				hop.ErrorEncrypter, lnwire.FailCode) {
diff --git a/peer/test_utils.go b/peer/test_utils.go
index 75cc19f91..40f329563 100644
--- a/peer/test_utils.go
+++ b/peer/test_utils.go
@@ -367,7 +367,9 @@ func createTestPeer(notifier chainntnfs.ChainNotifier,
 		Switch:      mockSwitch,
 
 		ChanActiveTimeout: chanActiveTimeout,
-		InterceptSwitch:   htlcswitch.NewInterceptableSwitch(nil),
+		InterceptSwitch: htlcswitch.NewInterceptableSwitch(
+			nil, false,
+		),
 
 		ChannelDB:      dbAlice.ChannelStateDB(),
 		FeeEstimator:   estimator,