diff --git a/config.go b/config.go index 8589cec57..167743ed9 100644 --- a/config.go +++ b/config.go @@ -248,7 +248,7 @@ type config struct { Color string `long:"color" description:"The color of the node in hex format (i.e. '#3399FF'). Used to customize node appearance in intelligence services"` MinChanSize int64 `long:"minchansize" description:"The smallest channel size (in satoshis) that we should accept. Incoming channels smaller than this will be rejected"` - NoChanUpdates bool `long:"nochanupdates" description:"If specified, lnd will not request real-time channel updates from connected peers. This option should be used by routing nodes to save bandwidth."` + NumGraphSyncPeers int `long:"numgraphsyncpeers" description:"The number of peers that we should receive new graph updates from. This option can be tuned to save bandwidth for light clients or routing nodes."` RejectPush bool `long:"rejectpush" description:"If true, lnd will not accept channel opening requests with non-zero push amounts. This should prevent accidental pushes to merchant nodes."` @@ -335,6 +335,7 @@ func loadConfig() (*config, error) { Alias: defaultAlias, Color: defaultColor, MinChanSize: int64(minChanFundingSize), + NumGraphSyncPeers: defaultMinPeers, Tor: &torConfig{ SOCKS: defaultTorSOCKS, DNS: defaultTorDNS, diff --git a/discovery/gossiper.go b/discovery/gossiper.go index fb6cd7d29..7e124c02b 100644 --- a/discovery/gossiper.go +++ b/discovery/gossiper.go @@ -20,6 +20,7 @@ import ( "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/multimutex" "github.com/lightningnetwork/lnd/routing" + "github.com/lightningnetwork/lnd/ticker" ) var ( @@ -75,7 +76,7 @@ type Config struct { Router routing.ChannelGraphSource // ChanSeries is an interfaces that provides access to a time series - // view of the current known channel graph. Each gossipSyncer enabled + // view of the current known channel graph. Each GossipSyncer enabled // peer will utilize this in order to create and respond to channel // graph time series queries. ChanSeries ChannelGraphTimeSeries @@ -143,6 +144,28 @@ type Config struct { // TODO(roasbeef): extract ann crafting + sign from fundingMgr into // here? AnnSigner lnwallet.MessageSigner + + // NumActiveSyncers is the number of peers for which we should have + // active syncers with. After reaching NumActiveSyncers, any future + // gossip syncers will be passive. + NumActiveSyncers int + + // RotateTicker is a ticker responsible for notifying the SyncManager + // when it should rotate its active syncers. A single active syncer with + // a chansSynced state will be exchanged for a passive syncer in order + // to ensure we don't keep syncing with the same peers. + RotateTicker ticker.Ticker + + // HistoricalSyncTicker is a ticker responsible for notifying the + // syncManager when it should attempt a historical sync with a gossip + // sync peer. + HistoricalSyncTicker ticker.Ticker + + // ActiveSyncerTimeoutTicker is a ticker responsible for notifying the + // syncManager when it should attempt to start the next pending + // activeSyncer due to the current one not completing its state machine + // within the timeout. + ActiveSyncerTimeoutTicker ticker.Ticker } // AuthenticatedGossiper is a subsystem which is responsible for receiving @@ -212,13 +235,14 @@ type AuthenticatedGossiper struct { rejectMtx sync.RWMutex recentRejects map[uint64]struct{} - // peerSyncers keeps track of all the gossip syncers we're maintain for - // peers that understand this mode of operation. When we go to send out - // new updates, for all peers in the map, we'll send the messages - // directly to their gossiper, rather than broadcasting them. With this - // change, we ensure we filter out all updates properly. - syncerMtx sync.RWMutex - peerSyncers map[routing.Vertex]*gossipSyncer + // syncMgr is a subsystem responsible for managing the gossip syncers + // for peers currently connected. When a new peer is connected, the + // manager will create its accompanying gossip syncer and determine + // whether it should have an activeSync or passiveSync sync type based + // on how many other gossip syncers are currently active. Any activeSync + // gossip syncers are started in a round-robin manner to ensure we're + // not syncing with multiple peers at the same time. + syncMgr *SyncManager // reliableSender is a subsystem responsible for handling reliable // message send requests to peers. This should only be used for channels @@ -243,7 +267,14 @@ func New(cfg Config, selfKey *btcec.PublicKey) *AuthenticatedGossiper { prematureChannelUpdates: make(map[uint64][]*networkMsg), channelMtx: multimutex.NewMutex(), recentRejects: make(map[uint64]struct{}), - peerSyncers: make(map[routing.Vertex]*gossipSyncer), + syncMgr: newSyncManager(&SyncManagerCfg{ + ChainHash: cfg.ChainHash, + ChanSeries: cfg.ChanSeries, + RotateTicker: cfg.RotateTicker, + HistoricalSyncTicker: cfg.HistoricalSyncTicker, + ActiveSyncerTimeoutTicker: cfg.ActiveSyncerTimeoutTicker, + NumActiveSyncers: cfg.NumActiveSyncers, + }), } gossiper.reliableSender = newReliableSender(&reliableSenderCfg{ @@ -419,6 +450,8 @@ func (d *AuthenticatedGossiper) Start() error { return err } + d.syncMgr.Start() + d.wg.Add(1) go d.networkHandler() @@ -435,11 +468,7 @@ func (d *AuthenticatedGossiper) Stop() { d.blockEpochs.Cancel() - d.syncerMtx.RLock() - for _, syncer := range d.peerSyncers { - syncer.Stop() - } - d.syncerMtx.RUnlock() + d.syncMgr.Stop() close(d.quit) d.wg.Wait() @@ -463,7 +492,7 @@ func (d *AuthenticatedGossiper) ProcessRemoteAnnouncement(msg lnwire.Message, errChan := make(chan error, 1) // For messages in the known set of channel series queries, we'll - // dispatch the message directly to the gossipSyncer, and skip the main + // dispatch the message directly to the GossipSyncer, and skip the main // processing loop. switch m := msg.(type) { case *lnwire.QueryShortChanIDs, @@ -471,12 +500,12 @@ func (d *AuthenticatedGossiper) ProcessRemoteAnnouncement(msg lnwire.Message, *lnwire.ReplyChannelRange, *lnwire.ReplyShortChanIDsEnd: - syncer, err := d.findGossipSyncer(peer.IdentityKey()) - if err != nil { - log.Warnf("Unable to find gossip syncer for "+ - "peer=%x: %v", peer.PubKey(), err) + syncer, ok := d.syncMgr.GossipSyncer(peer.PubKey()) + if !ok { + log.Warnf("Gossip syncer for peer=%x not found", + peer.PubKey()) - errChan <- err + errChan <- ErrGossipSyncerNotFound return errChan } @@ -488,24 +517,22 @@ func (d *AuthenticatedGossiper) ProcessRemoteAnnouncement(msg lnwire.Message, return errChan // If a peer is updating its current update horizon, then we'll dispatch - // that directly to the proper gossipSyncer. + // that directly to the proper GossipSyncer. case *lnwire.GossipTimestampRange: - syncer, err := d.findGossipSyncer(peer.IdentityKey()) - if err != nil { - log.Warnf("Unable to find gossip syncer for "+ - "peer=%x: %v", peer.PubKey(), err) + syncer, ok := d.syncMgr.GossipSyncer(peer.PubKey()) + if !ok { + log.Warnf("Gossip syncer for peer=%x not found", + peer.PubKey()) - errChan <- err + errChan <- ErrGossipSyncerNotFound return errChan } // If we've found the message target, then we'll dispatch the // message directly to it. - err = syncer.ApplyGossipFilter(m) - if err != nil { - log.Warnf("unable to apply gossip "+ - "filter for peer=%x: %v", - peer.PubKey(), err) + if err := syncer.ApplyGossipFilter(m); err != nil { + log.Warnf("Unable to apply gossip filter for peer=%x: "+ + "%v", peer.PubKey(), err) errChan <- err return errChan @@ -590,10 +617,10 @@ type msgWithSenders struct { } // mergeSyncerMap is used to merge the set of senders of a particular message -// with peers that we have an active gossipSyncer with. We do this to ensure +// with peers that we have an active GossipSyncer with. We do this to ensure // that we don't broadcast messages to any peers that we have active gossip // syncers for. -func (m *msgWithSenders) mergeSyncerMap(syncers map[routing.Vertex]*gossipSyncer) { +func (m *msgWithSenders) mergeSyncerMap(syncers map[routing.Vertex]*GossipSyncer) { for peerPub := range syncers { m.senders[peerPub] = struct{}{} } @@ -812,28 +839,6 @@ func (d *deDupedAnnouncements) Emit() []msgWithSenders { return msgs } -// findGossipSyncer is a utility method used by the gossiper to locate the -// gossip syncer for an inbound message so we can properly dispatch the -// incoming message. If a gossip syncer isn't found, then one will be created -// for the target peer. -func (d *AuthenticatedGossiper) findGossipSyncer(pub *btcec.PublicKey) ( - *gossipSyncer, error) { - - target := routing.NewVertex(pub) - - // First, we'll try to find an existing gossiper for this peer. - d.syncerMtx.RLock() - syncer, ok := d.peerSyncers[target] - d.syncerMtx.RUnlock() - - // If one exists, then we'll return it directly. - if ok { - return syncer, nil - } - - return nil, ErrGossipSyncerNotFound -} - // networkHandler is the primary goroutine that drives this service. The roles // of this goroutine includes answering queries related to the state of the // network, syncing up newly connected peers, and also periodically @@ -1028,12 +1033,7 @@ func (d *AuthenticatedGossiper) networkHandler() { // For the set of peers that have an active gossip // syncers, we'll collect their pubkeys so we can avoid // sending them the full message blast below. - d.syncerMtx.RLock() - syncerPeers := make(map[routing.Vertex]*gossipSyncer) - for peerPub, syncer := range d.peerSyncers { - syncerPeers[peerPub] = syncer - } - d.syncerMtx.RUnlock() + syncerPeers := d.syncMgr.GossipSyncers() log.Infof("Broadcasting batch of %v new announcements", len(announcementBatch)) @@ -1088,62 +1088,16 @@ func (d *AuthenticatedGossiper) networkHandler() { // InitSyncState is called by outside sub-systems when a connection is // established to a new peer that understands how to perform channel range // queries. We'll allocate a new gossip syncer for it, and start any goroutines -// needed to handle new queries. The recvUpdates bool indicates if we should -// continue to receive real-time updates from the remote peer once we've synced -// channel state. -func (d *AuthenticatedGossiper) InitSyncState(syncPeer lnpeer.Peer, - recvUpdates bool) { - - d.syncerMtx.Lock() - defer d.syncerMtx.Unlock() - - // If we already have a syncer, then we'll exit early as we don't want - // to override it. - nodeID := routing.Vertex(syncPeer.PubKey()) - if _, ok := d.peerSyncers[nodeID]; ok { - return - } - - log.Infof("Creating new gossipSyncer for peer=%x", nodeID[:]) - - encoding := lnwire.EncodingSortedPlain - syncer := newGossiperSyncer(gossipSyncerCfg{ - chainHash: d.cfg.ChainHash, - syncChanUpdates: recvUpdates, - channelSeries: d.cfg.ChanSeries, - encodingType: encoding, - chunkSize: encodingTypeToChunkSize[encoding], - sendToPeer: func(msgs ...lnwire.Message) error { - return syncPeer.SendMessageLazy(false, msgs...) - }, - }) - copy(syncer.peerPub[:], nodeID[:]) - d.peerSyncers[nodeID] = syncer - - syncer.Start() +// needed to handle new queries. +func (d *AuthenticatedGossiper) InitSyncState(syncPeer lnpeer.Peer) { + d.syncMgr.InitSyncState(syncPeer) } // PruneSyncState is called by outside sub-systems once a peer that we were // previously connected to has been disconnected. In this case we can stop the -// existing gossipSyncer assigned to the peer and free up resources. -func (d *AuthenticatedGossiper) PruneSyncState(peer *btcec.PublicKey) { - d.syncerMtx.Lock() - defer d.syncerMtx.Unlock() - - log.Infof("Removing gossipSyncer for peer=%x", - peer.SerializeCompressed()) - - vertex := routing.NewVertex(peer) - syncer, ok := d.peerSyncers[vertex] - if !ok { - return - } - - syncer.Stop() - - delete(d.peerSyncers, vertex) - - return +// existing GossipSyncer assigned to the peer and free up resources. +func (d *AuthenticatedGossiper) PruneSyncState(peer routing.Vertex) { + d.syncMgr.PruneSyncState(peer) } // isRecentlyRejectedMsg returns true if we recently rejected a message, and @@ -2514,3 +2468,8 @@ func (d *AuthenticatedGossiper) updateChannel(info *channeldb.ChannelEdgeInfo, return chanAnn, chanUpdate, err } + +// SyncManager returns the gossiper's SyncManager instance. +func (d *AuthenticatedGossiper) SyncManager() *SyncManager { + return d.syncMgr +} diff --git a/discovery/gossiper_test.go b/discovery/gossiper_test.go index 2e19f9fe2..066d76e2e 100644 --- a/discovery/gossiper_test.go +++ b/discovery/gossiper_test.go @@ -27,6 +27,7 @@ import ( "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing" + "github.com/lightningnetwork/lnd/ticker" ) var ( @@ -713,12 +714,16 @@ func createTestCtx(startHeight uint32) (*testCtx, func(), error) { c := make(chan struct{}) return c }, - Router: router, - TrickleDelay: trickleDelay, - RetransmitDelay: retransmitDelay, - ProofMatureDelta: proofMatureDelta, - WaitingProofStore: waitingProofStore, - MessageStore: newMockMessageStore(), + Router: router, + TrickleDelay: trickleDelay, + RetransmitDelay: retransmitDelay, + ProofMatureDelta: proofMatureDelta, + WaitingProofStore: waitingProofStore, + MessageStore: newMockMessageStore(), + RotateTicker: ticker.NewForce(DefaultSyncerRotationInterval), + HistoricalSyncTicker: ticker.NewForce(DefaultHistoricalSyncInterval), + ActiveSyncerTimeoutTicker: ticker.NewForce(DefaultActiveSyncerTimeout), + NumActiveSyncers: 3, }, nodeKeyPub1) if err := gossiper.Start(); err != nil { @@ -1447,16 +1452,20 @@ func TestSignatureAnnouncementRetryAtStartup(t *testing.T) { // the message to the peer. ctx.gossiper.Stop() gossiper := New(Config{ - Notifier: ctx.gossiper.cfg.Notifier, - Broadcast: ctx.gossiper.cfg.Broadcast, - NotifyWhenOnline: ctx.gossiper.reliableSender.cfg.NotifyWhenOnline, - NotifyWhenOffline: ctx.gossiper.reliableSender.cfg.NotifyWhenOffline, - Router: ctx.gossiper.cfg.Router, - TrickleDelay: trickleDelay, - RetransmitDelay: retransmitDelay, - ProofMatureDelta: proofMatureDelta, - WaitingProofStore: ctx.gossiper.cfg.WaitingProofStore, - MessageStore: ctx.gossiper.cfg.MessageStore, + Notifier: ctx.gossiper.cfg.Notifier, + Broadcast: ctx.gossiper.cfg.Broadcast, + NotifyWhenOnline: ctx.gossiper.reliableSender.cfg.NotifyWhenOnline, + NotifyWhenOffline: ctx.gossiper.reliableSender.cfg.NotifyWhenOffline, + Router: ctx.gossiper.cfg.Router, + TrickleDelay: trickleDelay, + RetransmitDelay: retransmitDelay, + ProofMatureDelta: proofMatureDelta, + WaitingProofStore: ctx.gossiper.cfg.WaitingProofStore, + MessageStore: ctx.gossiper.cfg.MessageStore, + RotateTicker: ticker.NewForce(DefaultSyncerRotationInterval), + HistoricalSyncTicker: ticker.NewForce(DefaultHistoricalSyncInterval), + ActiveSyncerTimeoutTicker: ticker.NewForce(DefaultActiveSyncerTimeout), + NumActiveSyncers: 3, }, ctx.gossiper.selfKey) if err != nil { t.Fatalf("unable to recreate gossiper: %v", err) diff --git a/discovery/reliable_sender_test.go b/discovery/reliable_sender_test.go index 8633b7d40..5890dfa31 100644 --- a/discovery/reliable_sender_test.go +++ b/discovery/reliable_sender_test.go @@ -8,6 +8,7 @@ import ( "github.com/btcsuite/btcd/btcec" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/lnpeer" + "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lnwire" ) @@ -59,29 +60,6 @@ func assertMsgsSent(t *testing.T, msgChan chan lnwire.Message, } } -// waitPredicate is a helper test function that will wait for a timeout period -// of time until the passed predicate returns true. -func waitPredicate(t *testing.T, timeout time.Duration, pred func() bool) { - t.Helper() - - const pollInterval = 20 * time.Millisecond - exitTimer := time.After(timeout) - - for { - <-time.After(pollInterval) - - select { - case <-exitTimer: - t.Fatalf("predicate not satisfied after timeout") - default: - } - - if pred() { - return - } - } -} - // TestReliableSenderFlow ensures that the flow for sending messages reliably to // a peer while taking into account its connection lifecycle works as expected. func TestReliableSenderFlow(t *testing.T) { @@ -262,27 +240,23 @@ func TestReliableSenderStaleMessages(t *testing.T) { // message store since it is seen as stale and has been sent at least // once. Once the message is removed, the peerHandler should be torn // down as there are no longer any pending messages within the store. - var predErr error - waitPredicate(t, time.Second, func() bool { + err := lntest.WaitNoError(func() error { msgs, err := reliableSender.cfg.MessageStore.MessagesForPeer( peerPubKey, ) if err != nil { - predErr = fmt.Errorf("unable to retrieve messages for "+ + return fmt.Errorf("unable to retrieve messages for "+ "peer: %v", err) - return false } if len(msgs) != 0 { - predErr = fmt.Errorf("expected to not find any "+ + return fmt.Errorf("expected to not find any "+ "messages for peer, found %d", len(msgs)) - return false } - predErr = nil - return true - }) - if predErr != nil { - t.Fatal(predErr) + return nil + }, time.Second) + if err != nil { + t.Fatal(err) } // Override IsMsgStale to no longer mark messages as stale. diff --git a/discovery/sync_manager.go b/discovery/sync_manager.go new file mode 100644 index 000000000..12e3e4a25 --- /dev/null +++ b/discovery/sync_manager.go @@ -0,0 +1,735 @@ +package discovery + +import ( + "container/list" + "errors" + "sync" + "time" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/lightningnetwork/lnd/lnpeer" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing" + "github.com/lightningnetwork/lnd/ticker" +) + +const ( + // DefaultSyncerRotationInterval is the default interval in which we'll + // rotate a single active syncer. + DefaultSyncerRotationInterval = 20 * time.Minute + + // DefaultHistoricalSyncInterval is the default interval in which we'll + // force a historical sync to ensure we have as much of the public + // network as possible. + DefaultHistoricalSyncInterval = 20 * time.Minute + + // DefaultActiveSyncerTimeout is the default timeout interval in which + // we'll wait until an active syncer has completed its state machine and + // reached its final chansSynced state. + DefaultActiveSyncerTimeout = 5 * time.Minute +) + +var ( + // ErrSyncManagerExiting is an error returned when we attempt to + // start/stop a gossip syncer for a connected/disconnected peer, but the + // SyncManager has already been stopped. + ErrSyncManagerExiting = errors.New("sync manager exiting") +) + +// staleActiveSyncer is an internal message the SyncManager will use in order to +// handle a peer corresponding to an active syncer being disconnected. +type staleActiveSyncer struct { + // syncer is the active syncer to be removed. + syncer *GossipSyncer + + // transitioned, if true, signals that the active GossipSyncer is stale + // due to being transitioned to a PassiveSync state. + transitioned bool + + // done serves as a signal to the caller that the SyncManager's internal + // state correctly reflects the stale active syncer. This is needed to + // ensure we always create a new syncer for a flappy peer after they + // disconnect if they happened to be an active syncer. + done chan struct{} +} + +// SyncManagerCfg contains all of the dependencies required for the SyncManager +// to carry out its duties. +type SyncManagerCfg struct { + // ChainHash is a hash that indicates the specific network of the active + // chain. + ChainHash chainhash.Hash + + // ChanSeries is an interface that provides access to a time series view + // of the current known channel graph. Each GossipSyncer enabled peer + // will utilize this in order to create and respond to channel graph + // time series queries. + ChanSeries ChannelGraphTimeSeries + + // NumActiveSyncers is the number of peers for which we should have + // active syncers with. After reaching NumActiveSyncers, any future + // gossip syncers will be passive. + NumActiveSyncers int + + // RotateTicker is a ticker responsible for notifying the SyncManager + // when it should rotate its active syncers. A single active syncer with + // a chansSynced state will be exchanged for a passive syncer in order + // to ensure we don't keep syncing with the same peers. + RotateTicker ticker.Ticker + + // HistoricalSyncTicker is a ticker responsible for notifying the + // SyncManager when it should attempt a historical sync with a gossip + // sync peer. + HistoricalSyncTicker ticker.Ticker + + // ActiveSyncerTimeoutTicker is a ticker responsible for notifying the + // SyncManager when it should attempt to start the next pending + // activeSyncer due to the current one not completing its state machine + // within the timeout. + ActiveSyncerTimeoutTicker ticker.Ticker +} + +// SyncManager is a subsystem of the gossiper that manages the gossip syncers +// for peers currently connected. When a new peer is connected, the manager will +// create its accompanying gossip syncer and determine whether it should have an +// ActiveSync or PassiveSync sync type based on how many other gossip syncers +// are currently active. Any ActiveSync gossip syncers are started in a +// round-robin manner to ensure we're not syncing with multiple peers at the +// same time. The first GossipSyncer registered with the SyncManager will +// attempt a historical sync to ensure we have as much of the public channel +// graph as possible. +type SyncManager struct { + start sync.Once + stop sync.Once + + cfg SyncManagerCfg + + // historicalSync allows us to perform an initial historical sync only + // _once_ with a peer during the SyncManager's startup. + historicalSync sync.Once + + // activeSyncers is the set of all syncers for which we are currently + // receiving graph updates from. The number of possible active syncers + // is bounded by NumActiveSyncers. + activeSyncers map[routing.Vertex]*GossipSyncer + + // inactiveSyncers is the set of all syncers for which we are not + // currently receiving new graph updates from. + inactiveSyncers map[routing.Vertex]*GossipSyncer + + // pendingActiveSyncers is a map that tracks our set of pending active + // syncers. This map will be queried when choosing the next pending + // active syncer in the queue to ensure it is not stale. + pendingActiveSyncers map[routing.Vertex]*GossipSyncer + + // pendingActiveSyncerQueue is the list of active syncers which are + // pending to be started. Syncers will be added to this list through the + // newActiveSyncers and staleActiveSyncers channels. + pendingActiveSyncerQueue *list.List + + // newActiveSyncers is a channel that will serve as a signal to the + // roundRobinHandler to allow it to transition the next pending active + // syncer in the queue. + newActiveSyncers chan struct{} + + // staleActiveSyncers is a channel through which we'll send any stale + // active syncers that should be removed from the round-robin. + staleActiveSyncers chan *staleActiveSyncer + + sync.Mutex + wg sync.WaitGroup + quit chan struct{} +} + +// newSyncManager constructs a new SyncManager backed by the given config. +func newSyncManager(cfg *SyncManagerCfg) *SyncManager { + return &SyncManager{ + cfg: *cfg, + activeSyncers: make( + map[routing.Vertex]*GossipSyncer, cfg.NumActiveSyncers, + ), + inactiveSyncers: make(map[routing.Vertex]*GossipSyncer), + pendingActiveSyncers: make(map[routing.Vertex]*GossipSyncer), + pendingActiveSyncerQueue: list.New(), + newActiveSyncers: make(chan struct{}), + staleActiveSyncers: make(chan *staleActiveSyncer), + quit: make(chan struct{}), + } +} + +// Start starts the SyncManager in order to properly carry out its duties. +func (m *SyncManager) Start() { + m.start.Do(func() { + m.wg.Add(2) + go m.syncerHandler() + go m.roundRobinHandler() + }) +} + +// Stop stops the SyncManager from performing its duties. +func (m *SyncManager) Stop() { + m.stop.Do(func() { + close(m.quit) + m.wg.Wait() + + m.Lock() + defer m.Unlock() + + for _, syncer := range m.inactiveSyncers { + syncer.Stop() + } + for _, syncer := range m.pendingActiveSyncers { + syncer.Stop() + } + for _, syncer := range m.activeSyncers { + syncer.Stop() + } + }) +} + +// syncerHandler is the SyncManager's main event loop responsible for: +// +// 1. Finding new peers to receive graph updates from to ensure we don't only +// receive them from the same set of peers. +// +// 2. Finding new peers to force a historical sync with to ensure we have as +// much of the public network as possible. +// +// NOTE: This must be run as a goroutine. +func (m *SyncManager) syncerHandler() { + defer m.wg.Done() + + m.cfg.RotateTicker.Resume() + defer m.cfg.RotateTicker.Stop() + + m.cfg.HistoricalSyncTicker.Resume() + defer m.cfg.HistoricalSyncTicker.Stop() + + for { + select { + // Our RotateTicker has ticked, so we'll attempt to rotate a + // single active syncer with a passive one. + case <-m.cfg.RotateTicker.Ticks(): + m.rotateActiveSyncerCandidate() + + // Our HistoricalSyncTicker has ticked, so we'll randomly select + // a peer and force a historical sync with them. + case <-m.cfg.HistoricalSyncTicker.Ticks(): + m.forceHistoricalSync() + + case <-m.quit: + return + } + } +} + +// signalNewActiveSyncer sends a signal to the roundRobinHandler to ensure it +// transitions any pending active syncers. +func (m *SyncManager) signalNewActiveSyncer() { + select { + case m.newActiveSyncers <- struct{}{}: + case <-m.quit: + } +} + +// signalStaleActiveSyncer removes the syncer for the given peer from the +// round-robin queue. +func (m *SyncManager) signalStaleActiveSyncer(s *GossipSyncer, transitioned bool) { + done := make(chan struct{}) + + select { + case m.staleActiveSyncers <- &staleActiveSyncer{ + syncer: s, + transitioned: transitioned, + done: done, + }: + case <-m.quit: + } + + // Before returning to the caller, we'll wait for the roundRobinHandler + // to signal us that the SyncManager has correctly updated its internal + // state after handling the stale active syncer. + select { + case <-done: + case <-m.quit: + } +} + +// roundRobinHandler is the SyncManager's event loop responsible for managing +// the round-robin queue of our active syncers to ensure they don't overlap and +// request the same set of channels, which significantly reduces bandwidth +// usage. +// +// NOTE: This must be run as a goroutine. +func (m *SyncManager) roundRobinHandler() { + defer m.wg.Done() + + defer m.cfg.ActiveSyncerTimeoutTicker.Stop() + + var ( + // current will hold the current active syncer we're waiting for + // to complete its state machine. + current *GossipSyncer + + // transitionNext will be responsible for containing the signal + // of when the current active syncer has completed its state + // machine. This signal allows us to transition the next pending + // active syncer, if any. + transitionNext chan struct{} + ) + + // transitionNextSyncer is a helper closure that we'll use to transition + // the next syncer queued up. If there aren't any, this will act as a + // NOP. + transitionNextSyncer := func() { + m.Lock() + current = m.nextPendingActiveSyncer() + m.Unlock() + for current != nil { + // We'll avoid performing the transition with the lock + // as it can potentially stall the SyncManager due to + // the syncTransitionTimeout. + err := m.transitionPassiveSyncer(current) + // If we timed out attempting to transition the syncer, + // we'll re-queue it to retry at a later time and move + // on to the next. + if err == ErrSyncTransitionTimeout { + log.Debugf("Timed out attempting to "+ + "transition pending active "+ + "GossipSyncer(%x)", current.cfg.peerPub) + + m.Lock() + m.queueActiveSyncer(current) + current = m.nextPendingActiveSyncer() + m.Unlock() + continue + } + if err != nil { + log.Errorf("Unable to transition pending "+ + "active GossipSyncer(%x): %v", + current.cfg.peerPub, err) + + m.Lock() + current = m.nextPendingActiveSyncer() + m.Unlock() + continue + } + + // The transition succeeded, so we'll set our signal to + // know when we should attempt to transition the next + // pending active syncer in our queue. + transitionNext = current.ResetSyncedSignal() + m.cfg.ActiveSyncerTimeoutTicker.Resume() + return + } + + transitionNext = nil + m.cfg.ActiveSyncerTimeoutTicker.Pause() + } + + for { + select { + // A new active syncer signal has been received, which indicates + // a new pending active syncer has been added to our queue. + // We'll only attempt to transition it now if we're not already + // in the middle of transitioning another one. We do this to + // ensure we don't overlap when requesting channels from + // different peers. + case <-m.newActiveSyncers: + if current == nil { + transitionNextSyncer() + } + + // A stale active syncer has been received, so we'll need to + // remove them from our queue. If we are currently waiting for + // its state machine to complete, we'll move on to the next + // active syncer in the queue. + case staleActiveSyncer := <-m.staleActiveSyncers: + s := staleActiveSyncer.syncer + + m.Lock() + // If the syncer has transitioned from an ActiveSync + // type, rather than disconnecting, we'll include it in + // the set of inactive syncers. + if staleActiveSyncer.transitioned { + m.inactiveSyncers[s.cfg.peerPub] = s + } + + // Remove the internal active syncer references for this + // peer. + delete(m.pendingActiveSyncers, s.cfg.peerPub) + delete(m.activeSyncers, s.cfg.peerPub) + + // We'll then attempt to find a passive syncer that can + // replace the stale active syncer. + newActiveSyncer := m.chooseRandomSyncer(nil, false) + if newActiveSyncer != nil { + m.queueActiveSyncer(newActiveSyncer) + } + m.Unlock() + + // Signal to the caller that they can now proceed since + // the SyncManager's state correctly reflects the + // stale active syncer. + close(staleActiveSyncer.done) + + // If we're not currently waiting for an active syncer + // to reach its terminal state, or if we are but we are + // currently waiting for the peer being + // disconnected/transitioned, then we'll move on to the + // next active syncer in our queue. + if current == nil || (current != nil && + current.cfg.peerPub == s.cfg.peerPub) { + transitionNextSyncer() + } + + // Our current active syncer has reached its terminal + // chansSynced state, so we'll proceed to transitioning the next + // pending active syncer if there is one. + case <-transitionNext: + transitionNextSyncer() + + // We've timed out waiting for the current active syncer to + // reach its terminal chansSynced state, so we'll just + // move on to the next and avoid retrying as its already been + // transitioned. + case <-m.cfg.ActiveSyncerTimeoutTicker.Ticks(): + log.Warnf("Timed out waiting for GossipSyncer(%x) to "+ + "be fully synced", current.cfg.peerPub) + transitionNextSyncer() + + case <-m.quit: + return + } + } +} + +// queueActiveSyncer queues the given pending active gossip syncer to the end of +// the round-robin queue. +func (m *SyncManager) queueActiveSyncer(s *GossipSyncer) { + log.Debugf("Queueing next pending active GossipSyncer(%x)", + s.cfg.peerPub) + + delete(m.inactiveSyncers, s.cfg.peerPub) + m.pendingActiveSyncers[s.cfg.peerPub] = s + m.pendingActiveSyncerQueue.PushBack(s) +} + +// nextPendingActiveSyncer returns the next active syncer pending to be +// transitioned. If there aren't any, then `nil` is returned. +func (m *SyncManager) nextPendingActiveSyncer() *GossipSyncer { + next := m.pendingActiveSyncerQueue.Front() + for next != nil { + s := m.pendingActiveSyncerQueue.Remove(next).(*GossipSyncer) + + // If the next pending active syncer is no longer in our lookup + // map, then the corresponding peer has disconnected, so we'll + // skip them. + if _, ok := m.pendingActiveSyncers[s.cfg.peerPub]; !ok { + next = m.pendingActiveSyncerQueue.Front() + continue + } + + return s + } + + return nil +} + +// rotateActiveSyncerCandidate rotates a single active syncer. In order to +// achieve this, the active syncer must be in a chansSynced state in order to +// process the sync transition. +func (m *SyncManager) rotateActiveSyncerCandidate() { + // If we don't have a candidate to rotate with, we can return early. + m.Lock() + candidate := m.chooseRandomSyncer(nil, false) + if candidate == nil { + m.Unlock() + log.Debug("No eligible candidate to rotate active syncer") + return + } + + // We'll choose an active syncer at random that's within a chansSynced + // state to rotate. + var activeSyncer *GossipSyncer + for _, s := range m.activeSyncers { + // The active syncer must be in a chansSynced state in order to + // process sync transitions. + if s.syncState() != chansSynced { + continue + } + + activeSyncer = s + break + } + m.Unlock() + + // If we couldn't find an eligible one, we can return early. + if activeSyncer == nil { + log.Debug("No eligible active syncer to rotate") + return + } + + // Otherwise, we'll attempt to transition each syncer to their + // respective new sync type. We'll avoid performing the transition with + // the lock as it can potentially stall the SyncManager due to the + // syncTransitionTimeout. + if err := m.transitionActiveSyncer(activeSyncer); err != nil { + log.Errorf("Unable to transition active "+ + "GossipSyncer(%x): %v", activeSyncer.cfg.peerPub, err) + return + } + + m.Lock() + m.queueActiveSyncer(candidate) + m.Unlock() + + m.signalNewActiveSyncer() +} + +// transitionActiveSyncer transitions an active syncer to a passive one. +func (m *SyncManager) transitionActiveSyncer(s *GossipSyncer) error { + log.Debugf("Transitioning active GossipSyncer(%x) to passive", + s.cfg.peerPub) + + if err := s.ProcessSyncTransition(PassiveSync); err != nil { + return err + } + + m.signalStaleActiveSyncer(s, true) + + return nil +} + +// transitionPassiveSyncer transitions a passive syncer to an active one. +func (m *SyncManager) transitionPassiveSyncer(s *GossipSyncer) error { + log.Debugf("Transitioning passive GossipSyncer(%x) to active", + s.cfg.peerPub) + + if err := s.ProcessSyncTransition(ActiveSync); err != nil { + return err + } + + m.Lock() + m.activeSyncers[s.cfg.peerPub] = s + delete(m.pendingActiveSyncers, s.cfg.peerPub) + m.Unlock() + + return nil +} + +// forceHistoricalSync chooses a syncer with a remote peer at random and forces +// a historical sync with it. +func (m *SyncManager) forceHistoricalSync() { + m.Lock() + defer m.Unlock() + + // We'll choose a random peer with whom we can perform a historical sync + // with. We'll set useActive to true to make sure we can still do one if + // we don't happen to have any non-active syncers. + candidatesChosen := make(map[routing.Vertex]struct{}) + s := m.chooseRandomSyncer(candidatesChosen, true) + for s != nil { + // Blacklist the candidate to ensure it's not chosen again. + candidatesChosen[s.cfg.peerPub] = struct{}{} + + err := s.historicalSync() + if err == nil { + return + } + + log.Errorf("Unable to perform historical sync with "+ + "GossipSyncer(%x): %v", s.cfg.peerPub, err) + + s = m.chooseRandomSyncer(candidatesChosen, true) + } +} + +// chooseRandomSyncer returns a random non-active syncer that's eligible for a +// sync transition. A blacklist can be used to skip any previously chosen +// candidates. The useActive boolean can be used to also filter active syncers. +// +// NOTE: It's possible for a nil value to be returned if there are no eligible +// candidate syncers. +// +// NOTE: This method must be called with the syncersMtx lock held. +func (m *SyncManager) chooseRandomSyncer(blacklist map[routing.Vertex]struct{}, + useActive bool) *GossipSyncer { + + eligible := func(s *GossipSyncer) bool { + // Skip any syncers that exist within the blacklist. + if blacklist != nil { + if _, ok := blacklist[s.cfg.peerPub]; ok { + return false + } + } + + // Only syncers in a chansSynced state are viable for sync + // transitions, so skip any that aren't. + return s.syncState() == chansSynced + } + + for _, s := range m.inactiveSyncers { + if !eligible(s) { + continue + } + return s + } + + if useActive { + for _, s := range m.activeSyncers { + if !eligible(s) { + continue + } + return s + } + } + + return nil +} + +// InitSyncState is called by outside sub-systems when a connection is +// established to a new peer that understands how to perform channel range +// queries. We'll allocate a new GossipSyncer for it, and start any goroutines +// needed to handle new queries. The first GossipSyncer registered with the +// SyncManager will attempt a historical sync to ensure we have as much of the +// public channel graph as possible. +// +// TODO(wilmer): Only mark as ActiveSync if this isn't a channel peer. +func (m *SyncManager) InitSyncState(peer lnpeer.Peer) { + // If we already have a syncer, then we'll exit early as we don't want + // to override it. + nodeID := routing.Vertex(peer.PubKey()) + if _, ok := m.GossipSyncer(nodeID); ok { + return + } + + log.Infof("Creating new GossipSyncer for peer=%x", nodeID[:]) + + encoding := lnwire.EncodingSortedPlain + s := newGossipSyncer(gossipSyncerCfg{ + chainHash: m.cfg.ChainHash, + peerPub: nodeID, + channelSeries: m.cfg.ChanSeries, + encodingType: encoding, + chunkSize: encodingTypeToChunkSize[encoding], + sendToPeer: func(msgs ...lnwire.Message) error { + return peer.SendMessage(false, msgs...) + }, + }) + + // Gossip syncers are initialized by default as passive and in a + // chansSynced state so that they can reply to any peer queries or + // handle any sync transitions. + s.setSyncType(PassiveSync) + s.setSyncState(chansSynced) + s.Start() + + m.Lock() + m.inactiveSyncers[nodeID] = s + + // We'll force a historical sync with the first peer we connect to + // ensure we get as much of the graph as possible. + var err error + m.historicalSync.Do(func() { + log.Infof("Attempting historical sync with GossipSyncer(%x)", + s.cfg.peerPub) + + err = s.historicalSync() + }) + if err != nil { + log.Errorf("Unable to perform historical sync with "+ + "GossipSyncer(%x): %v", s.cfg.peerPub, err) + + // Reset historicalSync to ensure it is tried again with a + // different peer. + m.historicalSync = sync.Once{} + } + + // If we've yet to reach our desired number of active syncers, then + // we'll use this one. + numActiveSyncers := len(m.activeSyncers) + len(m.pendingActiveSyncers) + if numActiveSyncers < m.cfg.NumActiveSyncers { + m.queueActiveSyncer(s) + m.Unlock() + m.signalNewActiveSyncer() + return + } + m.Unlock() +} + +// PruneSyncState is called by outside sub-systems once a peer that we were +// previously connected to has been disconnected. In this case we can stop the +// existing GossipSyncer assigned to the peer and free up resources. +func (m *SyncManager) PruneSyncState(peer routing.Vertex) { + s, ok := m.GossipSyncer(peer) + if !ok { + return + } + + log.Infof("Removing GossipSyncer for peer=%v", peer) + + // We'll start by stopping the GossipSyncer for the disconnected peer. + s.Stop() + + // If it's a non-active syncer, then we can just exit now. + m.Lock() + if _, ok := m.inactiveSyncers[s.cfg.peerPub]; ok { + delete(m.inactiveSyncers, s.cfg.peerPub) + m.Unlock() + return + } + m.Unlock() + + // Otherwise, we'll need to dequeue it from our pending active syncers + // queue and find a new one to replace it, if any. + m.signalStaleActiveSyncer(s, false) +} + +// GossipSyncer returns the associated gossip syncer of a peer. The boolean +// returned signals whether there exists a gossip syncer for the peer. +func (m *SyncManager) GossipSyncer(peer routing.Vertex) (*GossipSyncer, bool) { + m.Lock() + defer m.Unlock() + return m.gossipSyncer(peer) +} + +// gossipSyncer returns the associated gossip syncer of a peer. The boolean +// returned signals whether there exists a gossip syncer for the peer. +func (m *SyncManager) gossipSyncer(peer routing.Vertex) (*GossipSyncer, bool) { + syncer, ok := m.inactiveSyncers[peer] + if ok { + return syncer, true + } + syncer, ok = m.pendingActiveSyncers[peer] + if ok { + return syncer, true + } + syncer, ok = m.activeSyncers[peer] + if ok { + return syncer, true + } + return nil, false +} + +// GossipSyncers returns all of the currently initialized gossip syncers. +func (m *SyncManager) GossipSyncers() map[routing.Vertex]*GossipSyncer { + m.Lock() + defer m.Unlock() + + numSyncers := len(m.inactiveSyncers) + len(m.activeSyncers) + + len(m.inactiveSyncers) + syncers := make(map[routing.Vertex]*GossipSyncer, numSyncers) + + for _, syncer := range m.inactiveSyncers { + syncers[syncer.cfg.peerPub] = syncer + } + for _, syncer := range m.pendingActiveSyncers { + syncers[syncer.cfg.peerPub] = syncer + } + for _, syncer := range m.activeSyncers { + syncers[syncer.cfg.peerPub] = syncer + } + + return syncers +} diff --git a/discovery/sync_manager_test.go b/discovery/sync_manager_test.go new file mode 100644 index 000000000..7fee2b3ef --- /dev/null +++ b/discovery/sync_manager_test.go @@ -0,0 +1,549 @@ +package discovery + +import ( + "fmt" + "math" + "reflect" + "sync/atomic" + "testing" + "time" + + "github.com/davecgh/go-spew/spew" + "github.com/lightningnetwork/lnd/lntest" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/ticker" +) + +// randPeer creates a random peer. +func randPeer(t *testing.T, quit chan struct{}) *mockPeer { + t.Helper() + + return &mockPeer{ + pk: randPubKey(t), + sentMsgs: make(chan lnwire.Message), + quit: quit, + } +} + +// newTestSyncManager creates a new test SyncManager using mock implementations +// of its dependencies. +func newTestSyncManager(numActiveSyncers int) *SyncManager { + hID := lnwire.ShortChannelID{BlockHeight: latestKnownHeight} + return newSyncManager(&SyncManagerCfg{ + ChanSeries: newMockChannelGraphTimeSeries(hID), + RotateTicker: ticker.NewForce(DefaultSyncerRotationInterval), + HistoricalSyncTicker: ticker.NewForce(DefaultHistoricalSyncInterval), + ActiveSyncerTimeoutTicker: ticker.NewForce(DefaultActiveSyncerTimeout), + NumActiveSyncers: numActiveSyncers, + }) +} + +// TestSyncManagerNumActiveSyncers ensures that we are unable to have more than +// NumActiveSyncers active syncers. +func TestSyncManagerNumActiveSyncers(t *testing.T) { + t.Parallel() + + // We'll start by creating our test sync manager which will hold up to + // 3 active syncers. + const numActiveSyncers = 3 + const numSyncers = numActiveSyncers + 1 + + syncMgr := newTestSyncManager(numActiveSyncers) + syncMgr.Start() + defer syncMgr.Stop() + + // We'll go ahead and create our syncers. We'll gather the ones which + // should be active and passive to check them later on. + for i := 0; i < numActiveSyncers; i++ { + peer := randPeer(t, syncMgr.quit) + syncMgr.InitSyncState(peer) + + // The first syncer registered always attempts a historical + // sync. + if i == 0 { + assertTransitionToChansSynced(t, syncMgr, peer, true) + } + + assertPassiveSyncerTransition(t, syncMgr, peer) + assertSyncerStatus(t, syncMgr, peer, chansSynced, ActiveSync) + } + + for i := 0; i < numSyncers-numActiveSyncers; i++ { + peer := randPeer(t, syncMgr.quit) + syncMgr.InitSyncState(peer) + assertSyncerStatus(t, syncMgr, peer, chansSynced, PassiveSync) + } +} + +// TestSyncManagerNewActiveSyncerAfterDisconnect ensures that we can regain an +// active syncer after losing one due to the peer disconnecting. +func TestSyncManagerNewActiveSyncerAfterDisconnect(t *testing.T) { + t.Parallel() + + // We'll create our test sync manager to only have one active syncer. + syncMgr := newTestSyncManager(1) + syncMgr.Start() + defer syncMgr.Stop() + + // peer1 will represent an active syncer that performs a historical + // sync since it is the first registered peer with the SyncManager. + peer1 := randPeer(t, syncMgr.quit) + syncMgr.InitSyncState(peer1) + assertTransitionToChansSynced(t, syncMgr, peer1, true) + assertPassiveSyncerTransition(t, syncMgr, peer1) + + // It will then be torn down to simulate a disconnection. Since there + // are no other candidate syncers available, the active syncer won't be + // replaced. + syncMgr.PruneSyncState(peer1.PubKey()) + + // Then, we'll start our active syncer again, but this time we'll also + // have a passive syncer available to replace the active syncer after + // the peer disconnects. + syncMgr.InitSyncState(peer1) + assertPassiveSyncerTransition(t, syncMgr, peer1) + + // Create our second peer, which should be initialized as a passive + // syncer. + peer2 := randPeer(t, syncMgr.quit) + syncMgr.InitSyncState(peer2) + assertSyncerStatus(t, syncMgr, peer2, chansSynced, PassiveSync) + + // Disconnect our active syncer, which should trigger the SyncManager to + // replace it with our passive syncer. + syncMgr.PruneSyncState(peer1.PubKey()) + assertPassiveSyncerTransition(t, syncMgr, peer2) +} + +// TestSyncManagerRotateActiveSyncerCandidate tests that we can successfully +// rotate our active syncers after a certain interval. +func TestSyncManagerRotateActiveSyncerCandidate(t *testing.T) { + t.Parallel() + + // We'll create our sync manager with three active syncers. + syncMgr := newTestSyncManager(1) + syncMgr.Start() + defer syncMgr.Stop() + + // The first syncer registered always performs a historical sync. + activeSyncPeer := randPeer(t, syncMgr.quit) + syncMgr.InitSyncState(activeSyncPeer) + assertTransitionToChansSynced(t, syncMgr, activeSyncPeer, true) + assertPassiveSyncerTransition(t, syncMgr, activeSyncPeer) + + // We'll send a tick to force a rotation. Since there aren't any + // candidates, none of the active syncers will be rotated. + syncMgr.cfg.RotateTicker.(*ticker.Force).Force <- time.Time{} + assertNoMsgSent(t, activeSyncPeer) + assertSyncerStatus(t, syncMgr, activeSyncPeer, chansSynced, ActiveSync) + + // We'll then go ahead and add a passive syncer. + passiveSyncPeer := randPeer(t, syncMgr.quit) + syncMgr.InitSyncState(passiveSyncPeer) + assertSyncerStatus(t, syncMgr, passiveSyncPeer, chansSynced, PassiveSync) + + // We'll force another rotation - this time, since we have a passive + // syncer available, they should be rotated. + syncMgr.cfg.RotateTicker.(*ticker.Force).Force <- time.Time{} + + // The transition from an active syncer to a passive syncer causes the + // peer to send out a new GossipTimestampRange in the past so that they + // don't receive new graph updates. + assertActiveSyncerTransition(t, syncMgr, activeSyncPeer) + + // The transition from a passive syncer to an active syncer causes the + // peer to send a new GossipTimestampRange with the current timestamp to + // signal that they would like to receive new graph updates from their + // peers. This will also cause the gossip syncer to redo its state + // machine, starting from its initial syncingChans state. We'll then + // need to transition it to its final chansSynced state to ensure the + // next syncer is properly started in the round-robin. + assertPassiveSyncerTransition(t, syncMgr, passiveSyncPeer) +} + +// TestSyncManagerHistoricalSync ensures that we only attempt a single +// historical sync during the SyncManager's startup, and that we can routinely +// force historical syncs whenever the HistoricalSyncTicker fires. +func TestSyncManagerHistoricalSync(t *testing.T) { + t.Parallel() + + syncMgr := newTestSyncManager(0) + syncMgr.Start() + defer syncMgr.Stop() + + // We should expect to see a QueryChannelRange message with a + // FirstBlockHeight of the genesis block, signaling that a historical + // sync is being attempted. + peer := randPeer(t, syncMgr.quit) + syncMgr.InitSyncState(peer) + assertMsgSent(t, peer, &lnwire.QueryChannelRange{ + FirstBlockHeight: 0, + NumBlocks: math.MaxUint32, + }) + + // If an additional peer connects, then a historical sync should not be + // attempted again. + extraPeer := randPeer(t, syncMgr.quit) + syncMgr.InitSyncState(extraPeer) + assertNoMsgSent(t, extraPeer) + + // Then, we'll send a tick to force a historical sync. This should + // trigger the extra peer to also perform a historical sync since the + // first peer is not eligible due to not being in a chansSynced state. + syncMgr.cfg.HistoricalSyncTicker.(*ticker.Force).Force <- time.Time{} + assertMsgSent(t, extraPeer, &lnwire.QueryChannelRange{ + FirstBlockHeight: 0, + NumBlocks: math.MaxUint32, + }) +} + +// TestSyncManagerRoundRobinQueue ensures that any subsequent active syncers can +// only be started after the previous one has completed its state machine. +func TestSyncManagerRoundRobinQueue(t *testing.T) { + t.Parallel() + + const numActiveSyncers = 3 + + // We'll start by creating our sync manager with support for three + // active syncers. + syncMgr := newTestSyncManager(numActiveSyncers) + syncMgr.Start() + defer syncMgr.Stop() + + peers := make([]*mockPeer, 0, numActiveSyncers) + + // The first syncer registered always attempts a historical sync. + firstPeer := randPeer(t, syncMgr.quit) + syncMgr.InitSyncState(firstPeer) + peers = append(peers, firstPeer) + assertTransitionToChansSynced(t, syncMgr, firstPeer, true) + + // After completing the historical sync, a sync transition to ActiveSync + // should happen. It should transition immediately since it has no + // dependents. + assertActiveGossipTimestampRange(t, firstPeer) + + // We'll create the remaining numActiveSyncers. These will be queued in + // the round robin since the first syncer has yet to reach chansSynced. + queuedPeers := make([]*mockPeer, 0, numActiveSyncers-1) + for i := 0; i < numActiveSyncers-1; i++ { + peer := randPeer(t, syncMgr.quit) + syncMgr.InitSyncState(peer) + peers = append(peers, peer) + queuedPeers = append(queuedPeers, peer) + } + + // Ensure they cannot transition without sending a GossipTimestampRange + // message first. + for _, peer := range queuedPeers { + assertNoMsgSent(t, peer) + } + + // Transition the first syncer to chansSynced, which should allow the + // second to transition next. + assertTransitionToChansSynced(t, syncMgr, firstPeer, false) + + // assertSyncerTransitioned ensures the target peer's syncer is the only + // that has transitioned. + assertSyncerTransitioned := func(target *mockPeer) { + t.Helper() + + for _, peer := range peers { + if peer.PubKey() != target.PubKey() { + assertNoMsgSent(t, peer) + continue + } + + assertActiveGossipTimestampRange(t, target) + } + } + + // For each queued syncer, we'll ensure they have transitioned to an + // ActiveSync type and reached their final chansSynced state to allow + // the next one to transition. + for _, peer := range queuedPeers { + assertSyncerTransitioned(peer) + assertTransitionToChansSynced(t, syncMgr, peer, false) + } +} + +// TestSyncManagerRoundRobinTimeout ensures that if we timeout while waiting for +// an active syncer to reach its final chansSynced state, then we will go on to +// start the next. +func TestSyncManagerRoundRobinTimeout(t *testing.T) { + t.Parallel() + + // Create our sync manager with support for two active syncers. + syncMgr := newTestSyncManager(2) + syncMgr.Start() + defer syncMgr.Stop() + + // peer1 will be the first peer we start, which will time out and cause + // peer2 to start. + peer1 := randPeer(t, syncMgr.quit) + peer2 := randPeer(t, syncMgr.quit) + + // The first syncer registered always attempts a historical sync. + syncMgr.InitSyncState(peer1) + assertTransitionToChansSynced(t, syncMgr, peer1, true) + + // We assume the syncer for peer1 has transitioned once we see it send a + // lnwire.GossipTimestampRange message. + assertActiveGossipTimestampRange(t, peer1) + + // We'll then create the syncer for peer2. This should cause it to be + // queued so that it starts once the syncer for peer1 is done. + syncMgr.InitSyncState(peer2) + assertNoMsgSent(t, peer2) + + // Send a force tick to pretend the sync manager has timed out waiting + // for peer1's syncer to reach chansSynced. + syncMgr.cfg.ActiveSyncerTimeoutTicker.(*ticker.Force).Force <- time.Time{} + + // Finally, ensure that the syncer for peer2 has transitioned. + assertActiveGossipTimestampRange(t, peer2) +} + +// TestSyncManagerRoundRobinStaleSyncer ensures that any stale active syncers we +// are currently waiting for or are queued up to start are properly removed and +// stopped. +func TestSyncManagerRoundRobinStaleSyncer(t *testing.T) { + t.Parallel() + + const numActiveSyncers = 4 + + // We'll create and start our sync manager with some active syncers. + syncMgr := newTestSyncManager(numActiveSyncers) + syncMgr.Start() + defer syncMgr.Stop() + + peers := make([]*mockPeer, 0, numActiveSyncers) + + // The first syncer registered always attempts a historical sync. + firstPeer := randPeer(t, syncMgr.quit) + syncMgr.InitSyncState(firstPeer) + peers = append(peers, firstPeer) + assertTransitionToChansSynced(t, syncMgr, firstPeer, true) + + // After completing the historical sync, a sync transition to ActiveSync + // should happen. It should transition immediately since it has no + // dependents. + assertActiveGossipTimestampRange(t, firstPeer) + assertMsgSent(t, firstPeer, &lnwire.QueryChannelRange{ + FirstBlockHeight: startHeight, + NumBlocks: math.MaxUint32 - startHeight, + }) + + // We'll create the remaining numActiveSyncers. These will be queued in + // the round robin since the first syncer has yet to reach chansSynced. + queuedPeers := make([]*mockPeer, 0, numActiveSyncers-1) + for i := 0; i < numActiveSyncers-1; i++ { + peer := randPeer(t, syncMgr.quit) + syncMgr.InitSyncState(peer) + peers = append(peers, peer) + queuedPeers = append(queuedPeers, peer) + } + + // Ensure they cannot transition without sending a GossipTimestampRange + // message first. + for _, peer := range queuedPeers { + assertNoMsgSent(t, peer) + } + + // assertSyncerTransitioned ensures the target peer's syncer is the only + // that has transitioned. + assertSyncerTransitioned := func(target *mockPeer) { + t.Helper() + + for _, peer := range peers { + if peer.PubKey() != target.PubKey() { + assertNoMsgSent(t, peer) + continue + } + + assertPassiveSyncerTransition(t, syncMgr, target) + } + } + + // We'll then remove the syncers in the middle to cover the case where + // they are queued up in the sync manager's pending list. + for i, peer := range peers { + if i == 0 || i == len(peers)-1 { + continue + } + + syncMgr.PruneSyncState(peer.PubKey()) + } + + // We'll then remove the syncer we are currently waiting for. This + // should prompt the last syncer to start since it is the only one left + // pending. We'll do this in a goroutine since the peer behind the new + // active syncer will need to send out its new GossipTimestampRange. + go syncMgr.PruneSyncState(peers[0].PubKey()) + assertSyncerTransitioned(peers[len(peers)-1]) +} + +// assertNoMsgSent is a helper function that ensures a peer hasn't sent any +// messages. +func assertNoMsgSent(t *testing.T, peer *mockPeer) { + t.Helper() + + select { + case msg := <-peer.sentMsgs: + t.Fatalf("peer %x sent unexpected message %v", peer.PubKey(), + spew.Sdump(msg)) + case <-time.After(time.Second): + } +} + +// assertMsgSent asserts that the peer has sent the given message. +func assertMsgSent(t *testing.T, peer *mockPeer, msg lnwire.Message) { + t.Helper() + + var msgSent lnwire.Message + select { + case msgSent = <-peer.sentMsgs: + case <-time.After(time.Second): + t.Fatalf("expected peer %x to send %T message", peer.PubKey(), + msg) + } + + if !reflect.DeepEqual(msgSent, msg) { + t.Fatalf("expected peer %x to send message: %v\ngot: %v", + peer.PubKey(), spew.Sdump(msg), spew.Sdump(msgSent)) + } +} + +// assertActiveGossipTimestampRange is a helper function that ensures a peer has +// sent a lnwire.GossipTimestampRange message indicating that it would like to +// receive new graph updates. +func assertActiveGossipTimestampRange(t *testing.T, peer *mockPeer) { + t.Helper() + + var msgSent lnwire.Message + select { + case msgSent = <-peer.sentMsgs: + case <-time.After(time.Second): + t.Fatalf("expected peer %x to send lnwire.GossipTimestampRange "+ + "message", peer.PubKey()) + } + + msg, ok := msgSent.(*lnwire.GossipTimestampRange) + if !ok { + t.Fatalf("expected peer %x to send %T message", peer.PubKey(), + msg) + } + if msg.FirstTimestamp == 0 { + t.Fatalf("expected *lnwire.GossipTimestampRange message with " + + "non-zero FirstTimestamp") + } + if msg.TimestampRange == 0 { + t.Fatalf("expected *lnwire.GossipTimestampRange message with " + + "non-zero TimestampRange") + } +} + +// assertSyncerStatus asserts that the gossip syncer for the given peer matches +// the expected sync state and type. +func assertSyncerStatus(t *testing.T, syncMgr *SyncManager, peer *mockPeer, + syncState syncerState, syncType SyncerType) { + + t.Helper() + + s, ok := syncMgr.GossipSyncer(peer.PubKey()) + if !ok { + t.Fatalf("gossip syncer for peer %x not found", peer.PubKey()) + } + + // We'll check the status of our syncer within a WaitPredicate as some + // sync transitions might cause this to be racy. + err := lntest.WaitNoError(func() error { + state := s.syncState() + if s.syncState() != syncState { + return fmt.Errorf("expected syncState %v for peer "+ + "%x, got %v", syncState, peer.PubKey(), state) + } + + typ := s.SyncType() + if s.SyncType() != syncType { + return fmt.Errorf("expected syncType %v for peer "+ + "%x, got %v", syncType, peer.PubKey(), typ) + } + + return nil + }, time.Second) + if err != nil { + t.Fatal(err) + } +} + +// assertTransitionToChansSynced asserts the transition of an ActiveSync +// GossipSyncer to its final chansSynced state. +func assertTransitionToChansSynced(t *testing.T, syncMgr *SyncManager, + peer *mockPeer, historicalSync bool) { + + t.Helper() + + s, ok := syncMgr.GossipSyncer(peer.PubKey()) + if !ok { + t.Fatalf("gossip syncer for peer %x not found", peer.PubKey()) + } + + firstBlockHeight := uint32(startHeight) + if historicalSync { + firstBlockHeight = 0 + } + assertMsgSent(t, peer, &lnwire.QueryChannelRange{ + FirstBlockHeight: firstBlockHeight, + NumBlocks: math.MaxUint32 - firstBlockHeight, + }) + + s.ProcessQueryMsg(&lnwire.ReplyChannelRange{Complete: 1}, nil) + + chanSeries := syncMgr.cfg.ChanSeries.(*mockChannelGraphTimeSeries) + + select { + case <-chanSeries.filterReq: + chanSeries.filterResp <- nil + case <-time.After(2 * time.Second): + t.Fatal("expected to receive FilterKnownChanIDs request") + } + + err := lntest.WaitNoError(func() error { + state := syncerState(atomic.LoadUint32(&s.state)) + if state != chansSynced { + return fmt.Errorf("expected syncerState %v, got %v", + chansSynced, state) + } + + return nil + }, time.Second) + if err != nil { + t.Fatal(err) + } +} + +// assertPassiveSyncerTransition asserts that a gossip syncer goes through all +// of its expected steps when transitioning from passive to active. +func assertPassiveSyncerTransition(t *testing.T, syncMgr *SyncManager, + peer *mockPeer) { + + t.Helper() + + assertActiveGossipTimestampRange(t, peer) + assertTransitionToChansSynced(t, syncMgr, peer, false) +} + +// assertActiveSyncerTransition asserts that a gossip syncer goes through all of +// its expected steps when transitioning from active to passive. +func assertActiveSyncerTransition(t *testing.T, syncMgr *SyncManager, + peer *mockPeer) { + + t.Helper() + + assertMsgSent(t, peer, &lnwire.GossipTimestampRange{ + FirstTimestamp: uint32(zeroTimestamp.Unix()), + TimestampRange: 0, + }) + assertSyncerStatus(t, syncMgr, peer, chansSynced, PassiveSync) +} diff --git a/discovery/syncer.go b/discovery/syncer.go index f82c372e7..791e31c97 100644 --- a/discovery/syncer.go +++ b/discovery/syncer.go @@ -13,18 +13,51 @@ import ( "golang.org/x/time/rate" ) -// syncerState is an enum that represents the current state of the -// gossipSyncer. As the syncer is a state machine, we'll gate our actions -// based off of the current state and the next incoming message. +// SyncerType encapsulates the different types of syncing mechanisms for a +// gossip syncer. +type SyncerType uint8 + +const ( + // ActiveSync denotes that a gossip syncer should exercise its default + // behavior. This includes reconciling the set of missing graph updates + // with the remote peer _and_ receiving new updates from them. + ActiveSync SyncerType = iota + + // PassiveSync denotes that a gossip syncer: + // + // 1. Should not attempt to query the remote peer for graph updates. + // 2. Should respond to queries from the remote peer. + // 3. Should not receive new updates from the remote peer. + // + // They are started in a chansSynced state in order to accomplish their + // responsibilities above. + PassiveSync +) + +// String returns a human readable string describing the target SyncerType. +func (t SyncerType) String() string { + switch t { + case ActiveSync: + return "ActiveSync" + case PassiveSync: + return "PassiveSync" + default: + return fmt.Sprintf("unknown sync type %d", t) + } +} + +// syncerState is an enum that represents the current state of the GossipSyncer. +// As the syncer is a state machine, we'll gate our actions based off of the +// current state and the next incoming message. type syncerState uint32 const ( - // syncingChans is the default state of the gossipSyncer. We start in + // syncingChans is the default state of the GossipSyncer. We start in // this state when a new peer first connects and we don't yet know if // we're fully synchronized. syncingChans syncerState = iota - // waitingQueryRangeReply is the second main phase of the gossipSyncer. + // waitingQueryRangeReply is the second main phase of the GossipSyncer. // We enter this state after we send out our first QueryChannelRange // reply. We'll stay in this state until the remote party sends us a // ReplyShortChanIDsEnd message that indicates they've responded to our @@ -33,19 +66,19 @@ const ( // chan ID's to us. waitingQueryRangeReply - // queryNewChannels is the third main phase of the gossipSyncer. In + // queryNewChannels is the third main phase of the GossipSyncer. In // this phase we'll send out all of our QueryShortChanIDs messages in // response to the new channels that we don't yet know about. queryNewChannels - // waitingQueryChanReply is the fourth main phase of the gossipSyncer. + // waitingQueryChanReply is the fourth main phase of the GossipSyncer. // We enter this phase once we've sent off a query chink to the remote // peer. We'll stay in this phase until we receive a // ReplyShortChanIDsEnd message which indicates that the remote party // has responded to all of our requests. waitingQueryChanReply - // chansSynced is the terminal stage of the gossipSyncer. Once we enter + // chansSynced is the terminal stage of the GossipSyncer. Once we enter // this phase, we'll send out our update horizon, which filters out the // set of channel updates that we're interested in. In this state, // we'll be able to accept any outgoing messages from the @@ -54,17 +87,6 @@ const ( chansSynced ) -const ( - // DefaultMaxUndelayedQueryReplies specifies how many gossip queries we - // will respond to immediately before starting to delay responses. - DefaultMaxUndelayedQueryReplies = 10 - - // DefaultDelayedQueryReplyInterval is the length of time we will wait - // before responding to gossip queries after replying to - // maxUndelayedQueryReplies queries. - DefaultDelayedQueryReplyInterval = 5 * time.Second -) - // String returns a human readable string describing the target syncerState. func (s syncerState) String() string { switch s { @@ -88,6 +110,26 @@ func (s syncerState) String() string { } } +const ( + // DefaultMaxUndelayedQueryReplies specifies how many gossip queries we + // will respond to immediately before starting to delay responses. + DefaultMaxUndelayedQueryReplies = 10 + + // DefaultDelayedQueryReplyInterval is the length of time we will wait + // before responding to gossip queries after replying to + // maxUndelayedQueryReplies queries. + DefaultDelayedQueryReplyInterval = 5 * time.Second + + // chanRangeQueryBuffer is the number of blocks back that we'll go when + // asking the remote peer for their any channels they know of beyond + // our highest known channel ID. + chanRangeQueryBuffer = 144 + + // syncTransitionTimeout is the default timeout in which we'll wait up + // to when attempting to perform a sync transition. + syncTransitionTimeout = 5 * time.Second +) + var ( // encodingTypeToChunkSize maps an encoding type, to the max number of // short chan ID's using the encoding type that we can fit into a @@ -98,21 +140,33 @@ var ( // ErrGossipSyncerExiting signals that the syncer has been killed. ErrGossipSyncerExiting = errors.New("gossip syncer exiting") + + // ErrSyncTransitionTimeout is an error returned when we've timed out + // attempting to perform a sync transition. + ErrSyncTransitionTimeout = errors.New("timed out attempting to " + + "transition sync type") + + // zeroTimestamp is the timestamp we'll use when we want to indicate to + // peers that we do not want to receive any new graph updates. + zeroTimestamp time.Time ) -const ( - // chanRangeQueryBuffer is the number of blocks back that we'll go when - // asking the remote peer for their any channels they know of beyond - // our highest known channel ID. - chanRangeQueryBuffer = 144 -) +// syncTransitionReq encapsulates a request for a gossip syncer sync transition. +type syncTransitionReq struct { + newSyncType SyncerType + errChan chan error +} -// gossipSyncerCfg is a struct that packages all the information a gossipSyncer +// gossipSyncerCfg is a struct that packages all the information a GossipSyncer // needs to carry out its duties. type gossipSyncerCfg struct { // chainHash is the chain that this syncer is responsible for. chainHash chainhash.Hash + // peerPub is the public key of the peer we're syncing with, serialized + // in compressed format. + peerPub [33]byte + // syncChanUpdates is a bool that indicates if we should request a // continual channel update stream or not. syncChanUpdates bool @@ -144,18 +198,27 @@ type gossipSyncerCfg struct { delayedQueryReplyInterval time.Duration } -// gossipSyncer is a struct that handles synchronizing the channel graph state -// with a remote peer. The gossipSyncer implements a state machine that will +// GossipSyncer is a struct that handles synchronizing the channel graph state +// with a remote peer. The GossipSyncer implements a state machine that will // progressively ensure we're synchronized with the channel state of the remote // node. Once both nodes have been synchronized, we'll use an update filter to // filter out which messages should be sent to a remote peer based on their // update horizon. If the update horizon isn't specified, then we won't send // them any channel updates at all. -// -// TODO(roasbeef): modify to only sync from one peer at a time? -type gossipSyncer struct { - started uint32 - stopped uint32 +type GossipSyncer struct { + started sync.Once + stopped sync.Once + + // state is the current state of the GossipSyncer. + // + // NOTE: This variable MUST be used atomically. + state uint32 + + // syncType denotes the SyncerType the gossip syncer is currently + // exercising. + // + // NOTE: This variable MUST be used atomically. + syncType uint32 // remoteUpdateHorizon is the update horizon of the remote peer. We'll // use this to properly filter out any messages. @@ -165,10 +228,25 @@ type gossipSyncer struct { // determine if we've already sent out our update. localUpdateHorizon *lnwire.GossipTimestampRange - // state is the current state of the gossipSyncer. - // - // NOTE: This variable MUST be used atomically. - state uint32 + // syncTransitions is a channel through which new sync type transition + // requests will be sent through. These requests should only be handled + // when the gossip syncer is in a chansSynced state to ensure its state + // machine behaves as expected. + syncTransitionReqs chan *syncTransitionReq + + // historicalSyncReqs is a channel that serves as a signal for the + // gossip syncer to perform a historical sync. Theese can only be done + // once the gossip syncer is in a chansSynced state to ensure its state + // machine behaves as expected. + historicalSyncReqs chan struct{} + + // genHistoricalChanRangeQuery when true signals to the gossip syncer + // that it should request the remote peer for all of its known channel + // IDs starting from the genesis block of the chain. This can only + // happen if the gossip syncer receives a request to attempt a + // historical sync. It can be unset if the syncer ever transitions from + // PassiveSync to ActiveSync. + genHistoricalChanRangeQuery bool // gossipMsgs is a channel that all messages from the target peer will // be sent over. @@ -183,10 +261,6 @@ type gossipSyncer struct { // state. newChansToQuery []lnwire.ShortChannelID - // peerPub is the public key of the peer we're syncing with, serialized - // in compressed format. - peerPub [33]byte - cfg gossipSyncerCfg // rateLimiter dictates the frequency with which we will reply to gossip @@ -195,15 +269,19 @@ type gossipSyncer struct { // number of queries. rateLimiter *rate.Limiter + // syncedSignal is a channel that, if set, will be closed when the + // GossipSyncer reaches its terminal chansSynced state. + syncedSignal chan struct{} + sync.Mutex quit chan struct{} wg sync.WaitGroup } -// newGossiperSyncer returns a new instance of the gossipSyncer populated using +// newGossipSyncer returns a new instance of the GossipSyncer populated using // the passed config. -func newGossiperSyncer(cfg gossipSyncerCfg) *gossipSyncer { +func newGossipSyncer(cfg gossipSyncerCfg) *GossipSyncer { // If no parameter was specified for max undelayed query replies, set it // to the default of 5 queries. if cfg.maxUndelayedQueryReplies <= 0 { @@ -225,57 +303,48 @@ func newGossiperSyncer(cfg gossipSyncerCfg) *gossipSyncer { interval, cfg.maxUndelayedQueryReplies, ) - return &gossipSyncer{ - cfg: cfg, - rateLimiter: rateLimiter, - gossipMsgs: make(chan lnwire.Message, 100), - quit: make(chan struct{}), + return &GossipSyncer{ + cfg: cfg, + rateLimiter: rateLimiter, + syncTransitionReqs: make(chan *syncTransitionReq), + historicalSyncReqs: make(chan struct{}), + gossipMsgs: make(chan lnwire.Message, 100), + quit: make(chan struct{}), } } -// Start starts the gossipSyncer and any goroutines that it needs to carry out +// Start starts the GossipSyncer and any goroutines that it needs to carry out // its duties. -func (g *gossipSyncer) Start() error { - if !atomic.CompareAndSwapUint32(&g.started, 0, 1) { - return nil - } +func (g *GossipSyncer) Start() { + g.started.Do(func() { + log.Debugf("Starting GossipSyncer(%x)", g.cfg.peerPub[:]) - log.Debugf("Starting gossipSyncer(%x)", g.peerPub[:]) - - g.wg.Add(1) - go g.channelGraphSyncer() - - return nil + g.wg.Add(1) + go g.channelGraphSyncer() + }) } -// Stop signals the gossipSyncer for a graceful exit, then waits until it has +// Stop signals the GossipSyncer for a graceful exit, then waits until it has // exited. -func (g *gossipSyncer) Stop() error { - if !atomic.CompareAndSwapUint32(&g.stopped, 0, 1) { - return nil - } - - close(g.quit) - - g.wg.Wait() - - return nil +func (g *GossipSyncer) Stop() { + g.stopped.Do(func() { + close(g.quit) + g.wg.Wait() + }) } // channelGraphSyncer is the main goroutine responsible for ensuring that we // properly channel graph state with the remote peer, and also that we only // send them messages which actually pass their defined update horizon. -func (g *gossipSyncer) channelGraphSyncer() { +func (g *GossipSyncer) channelGraphSyncer() { defer g.wg.Done() - // TODO(roasbeef): also add ability to force transition back to syncing - // chans - // * needed if we want to sync chan state very few blocks? - for { - state := atomic.LoadUint32(&g.state) - log.Debugf("gossipSyncer(%x): state=%v", g.peerPub[:], - syncerState(state)) + state := g.syncState() + syncType := g.SyncType() + + log.Debugf("GossipSyncer(%x): state=%v, type=%v", + g.cfg.peerPub[:], state, syncType) switch syncerState(state) { // When we're in this state, we're trying to synchronize our @@ -286,7 +355,9 @@ func (g *gossipSyncer) channelGraphSyncer() { case syncingChans: // If we're in this state, then we'll send the remote // peer our opening QueryChannelRange message. - queryRangeMsg, err := g.genChanRangeQuery() + queryRangeMsg, err := g.genChanRangeQuery( + g.genHistoricalChanRangeQuery, + ) if err != nil { log.Errorf("unable to gen chan range "+ "query: %v", err) @@ -302,7 +373,7 @@ func (g *gossipSyncer) channelGraphSyncer() { // With the message sent successfully, we'll transition // into the next state where we wait for their reply. - atomic.StoreUint32(&g.state, uint32(waitingQueryRangeReply)) + g.setSyncState(waitingQueryRangeReply) // In this state, we've sent out our initial channel range // query and are waiting for the final response from the remote @@ -359,13 +430,13 @@ func (g *gossipSyncer) channelGraphSyncer() { // If this wasn't our last query, then we'll need to // transition to our waiting state. if !done { - atomic.StoreUint32(&g.state, uint32(waitingQueryChanReply)) + g.setSyncState(waitingQueryChanReply) continue } // If we're fully synchronized, then we can transition // to our terminal state. - atomic.StoreUint32(&g.state, uint32(chansSynced)) + g.setSyncState(chansSynced) // In this state, we've just sent off a new query for channels // that we don't yet know of. We'll remain in this state until @@ -382,7 +453,7 @@ func (g *gossipSyncer) channelGraphSyncer() { // state to send of the remaining query chunks. _, ok := msg.(*lnwire.ReplyShortChanIDsEnd) if ok { - atomic.StoreUint32(&g.state, uint32(queryNewChannels)) + g.setSyncState(queryNewChannels) continue } @@ -401,35 +472,30 @@ func (g *gossipSyncer) channelGraphSyncer() { // This is our final terminal state where we'll only reply to // any further queries by the remote peer. case chansSynced: + g.Lock() + if g.syncedSignal != nil { + close(g.syncedSignal) + g.syncedSignal = nil + } + g.Unlock() + // If we haven't yet sent out our update horizon, and // we want to receive real-time channel updates, we'll // do so now. - if g.localUpdateHorizon == nil && g.cfg.syncChanUpdates { - // TODO(roasbeef): query DB for most recent - // update? - - // We'll give an hours room in our update - // horizon to ensure we don't miss any newer - // items. - updateHorizon := time.Now().Add(-time.Hour * 1) - log.Infof("gossipSyncer(%x): applying "+ - "gossipFilter(start=%v)", g.peerPub[:], - updateHorizon) - - g.localUpdateHorizon = &lnwire.GossipTimestampRange{ - ChainHash: g.cfg.chainHash, - FirstTimestamp: uint32(updateHorizon.Unix()), - TimestampRange: math.MaxUint32, - } - err := g.cfg.sendToPeer(g.localUpdateHorizon) + if g.localUpdateHorizon == nil && syncType == ActiveSync { + err := g.sendGossipTimestampRange( + time.Now(), math.MaxUint32, + ) if err != nil { - log.Errorf("unable to send update "+ - "horizon: %v", err) + log.Errorf("Unable to send update "+ + "horizon to %x: %v", + g.cfg.peerPub, err) } } // With our horizon set, we'll simply reply to any new - // message and exit if needed. + // messages or process any state transitions and exit if + // needed. select { case msg := <-g.gossipMsgs: err := g.replyPeerQueries(msg) @@ -438,6 +504,12 @@ func (g *gossipSyncer) channelGraphSyncer() { "query: %v", err) } + case req := <-g.syncTransitionReqs: + req.errChan <- g.handleSyncTransition(req) + + case <-g.historicalSyncReqs: + g.handleHistoricalSync() + case <-g.quit: return } @@ -445,19 +517,50 @@ func (g *gossipSyncer) channelGraphSyncer() { } } +// sendGossipTimestampRange constructs and sets a GossipTimestampRange for the +// syncer and sends it to the remote peer. +func (g *GossipSyncer) sendGossipTimestampRange(firstTimestamp time.Time, + timestampRange uint32) error { + + endTimestamp := firstTimestamp.Add( + time.Duration(timestampRange) * time.Second, + ) + + log.Infof("GossipSyncer(%x): applying gossipFilter(start=%v, end=%v)", + g.cfg.peerPub[:], firstTimestamp, endTimestamp) + + localUpdateHorizon := &lnwire.GossipTimestampRange{ + ChainHash: g.cfg.chainHash, + FirstTimestamp: uint32(firstTimestamp.Unix()), + TimestampRange: timestampRange, + } + + if err := g.cfg.sendToPeer(localUpdateHorizon); err != nil { + return err + } + + if firstTimestamp == zeroTimestamp && timestampRange == 0 { + g.localUpdateHorizon = nil + } else { + g.localUpdateHorizon = localUpdateHorizon + } + + return nil +} + // synchronizeChanIDs is called by the channelGraphSyncer when we need to query // the remote peer for its known set of channel IDs within a particular block // range. This method will be called continually until the entire range has // been queried for with a response received. We'll chunk our requests as // required to ensure they fit into a single message. We may re-renter this // state in the case that chunking is required. -func (g *gossipSyncer) synchronizeChanIDs() (bool, error) { +func (g *GossipSyncer) synchronizeChanIDs() (bool, error) { // If we're in this state yet there are no more new channels to query // for, then we'll transition to our final synced state and return true // to signal that we're fully synchronized. if len(g.newChansToQuery) == 0 { - log.Infof("gossipSyncer(%x): no more chans to query", - g.peerPub[:]) + log.Infof("GossipSyncer(%x): no more chans to query", + g.cfg.peerPub[:]) return true, nil } @@ -479,8 +582,8 @@ func (g *gossipSyncer) synchronizeChanIDs() (bool, error) { g.newChansToQuery = g.newChansToQuery[g.cfg.chunkSize:] } - log.Infof("gossipSyncer(%x): querying for %v new channels", - g.peerPub[:], len(queryChunk)) + log.Infof("GossipSyncer(%x): querying for %v new channels", + g.cfg.peerPub[:], len(queryChunk)) // With our chunk obtained, we'll send over our next query, then return // false indicating that we're net yet fully synced. @@ -493,16 +596,16 @@ func (g *gossipSyncer) synchronizeChanIDs() (bool, error) { return false, err } -// processChanRangeReply is called each time the gossipSyncer receives a new +// processChanRangeReply is called each time the GossipSyncer receives a new // reply to the initial range query to discover new channels that it didn't // previously know of. -func (g *gossipSyncer) processChanRangeReply(msg *lnwire.ReplyChannelRange) error { +func (g *GossipSyncer) processChanRangeReply(msg *lnwire.ReplyChannelRange) error { g.bufferedChanRangeReplies = append( g.bufferedChanRangeReplies, msg.ShortChanIDs..., ) - log.Infof("gossipSyncer(%x): buffering chan range reply of size=%v", - g.peerPub[:], len(msg.ShortChanIDs)) + log.Infof("GossipSyncer(%x): buffering chan range reply of size=%v", + g.cfg.peerPub[:], len(msg.ShortChanIDs)) // If this isn't the last response, then we can exit as we've already // buffered the latest portion of the streaming reply. @@ -510,8 +613,8 @@ func (g *gossipSyncer) processChanRangeReply(msg *lnwire.ReplyChannelRange) erro return nil } - log.Infof("gossipSyncer(%x): filtering through %v chans", g.peerPub[:], - len(g.bufferedChanRangeReplies)) + log.Infof("GossipSyncer(%x): filtering through %v chans", + g.cfg.peerPub[:], len(g.bufferedChanRangeReplies)) // Otherwise, this is the final response, so we'll now check to see // which channels they know of that we don't. @@ -530,28 +633,31 @@ func (g *gossipSyncer) processChanRangeReply(msg *lnwire.ReplyChannelRange) erro // If there aren't any channels that we don't know of, then we can // switch straight to our terminal state. if len(newChans) == 0 { - log.Infof("gossipSyncer(%x): remote peer has no new chans", - g.peerPub[:]) + log.Infof("GossipSyncer(%x): remote peer has no new chans", + g.cfg.peerPub[:]) - atomic.StoreUint32(&g.state, uint32(chansSynced)) + g.setSyncState(chansSynced) return nil } // Otherwise, we'll set the set of channels that we need to query for // the next state, and also transition our state. g.newChansToQuery = newChans - atomic.StoreUint32(&g.state, uint32(queryNewChannels)) + g.setSyncState(queryNewChannels) - log.Infof("gossipSyncer(%x): starting query for %v new chans", - g.peerPub[:], len(newChans)) + log.Infof("GossipSyncer(%x): starting query for %v new chans", + g.cfg.peerPub[:], len(newChans)) return nil } // genChanRangeQuery generates the initial message we'll send to the remote // party when we're kicking off the channel graph synchronization upon -// connection. -func (g *gossipSyncer) genChanRangeQuery() (*lnwire.QueryChannelRange, error) { +// connection. The historicalQuery boolean can be used to generate a query from +// the genesis block of the chain. +func (g *GossipSyncer) genChanRangeQuery( + historicalQuery bool) (*lnwire.QueryChannelRange, error) { + // First, we'll query our channel graph time series for its highest // known channel ID. newestChan, err := g.cfg.channelSeries.HighestChanID(g.cfg.chainHash) @@ -559,23 +665,23 @@ func (g *gossipSyncer) genChanRangeQuery() (*lnwire.QueryChannelRange, error) { return nil, err } - // Once we have the chan ID of the newest, we'll obtain the block - // height of the channel, then subtract our default horizon to ensure - // we don't miss any channels. By default, we go back 1 day from the - // newest channel. + // Once we have the chan ID of the newest, we'll obtain the block height + // of the channel, then subtract our default horizon to ensure we don't + // miss any channels. By default, we go back 1 day from the newest + // channel, unless we're attempting a historical sync, where we'll + // actually start from the genesis block instead. var startHeight uint32 switch { - case newestChan.BlockHeight <= chanRangeQueryBuffer: + case historicalQuery: fallthrough - case newestChan.BlockHeight == 0: + case newestChan.BlockHeight <= chanRangeQueryBuffer: startHeight = 0 - default: startHeight = uint32(newestChan.BlockHeight - chanRangeQueryBuffer) } - log.Infof("gossipSyncer(%x): requesting new chans from height=%v "+ - "and %v blocks after", g.peerPub[:], startHeight, + log.Infof("GossipSyncer(%x): requesting new chans from height=%v "+ + "and %v blocks after", g.cfg.peerPub[:], startHeight, math.MaxUint32-startHeight) // Finally, we'll craft the channel range query, using our starting @@ -590,7 +696,7 @@ func (g *gossipSyncer) genChanRangeQuery() (*lnwire.QueryChannelRange, error) { // replyPeerQueries is called in response to any query by the remote peer. // We'll examine our state and send back our best response. -func (g *gossipSyncer) replyPeerQueries(msg lnwire.Message) error { +func (g *GossipSyncer) replyPeerQueries(msg lnwire.Message) error { reservation := g.rateLimiter.Reserve() delay := reservation.Delay() @@ -598,8 +704,8 @@ func (g *gossipSyncer) replyPeerQueries(msg lnwire.Message) error { // responses back to the remote peer. This can help prevent DOS attacks // where the remote peer spams us endlessly. if delay > 0 { - log.Infof("gossipSyncer(%x): rate limiting gossip replies, "+ - "responding in %s", g.peerPub[:], delay) + log.Infof("GossipSyncer(%x): rate limiting gossip replies, "+ + "responding in %s", g.cfg.peerPub[:], delay) select { case <-time.After(delay): @@ -630,9 +736,9 @@ func (g *gossipSyncer) replyPeerQueries(msg lnwire.Message) error { // meet the channel range, then chunk our responses to the remote node. We also // ensure that our final fragment carries the "complete" bit to indicate the // end of our streaming response. -func (g *gossipSyncer) replyChanRangeQuery(query *lnwire.QueryChannelRange) error { - log.Infof("gossipSyncer(%x): filtering chan range: start_height=%v, "+ - "num_blocks=%v", g.peerPub[:], query.FirstBlockHeight, +func (g *GossipSyncer) replyChanRangeQuery(query *lnwire.QueryChannelRange) error { + log.Infof("GossipSyncer(%x): filtering chan range: start_height=%v, "+ + "num_blocks=%v", g.cfg.peerPub[:], query.FirstBlockHeight, query.NumBlocks) // Next, we'll consult the time series to obtain the set of known @@ -666,16 +772,16 @@ func (g *gossipSyncer) replyChanRangeQuery(query *lnwire.QueryChannelRange) erro if isFinalChunk { channelChunk = channelRange[numChansSent:] - log.Infof("gossipSyncer(%x): sending final chan "+ - "range chunk, size=%v", g.peerPub[:], len(channelChunk)) - + log.Infof("GossipSyncer(%x): sending final chan "+ + "range chunk, size=%v", g.cfg.peerPub[:], + len(channelChunk)) } else { // Otherwise, we'll only send off a fragment exactly // sized to the proper chunk size. channelChunk = channelRange[numChansSent : numChansSent+g.cfg.chunkSize] - log.Infof("gossipSyncer(%x): sending range chunk of "+ - "size=%v", g.peerPub[:], len(channelChunk)) + log.Infof("GossipSyncer(%x): sending range chunk of "+ + "size=%v", g.cfg.peerPub[:], len(channelChunk)) } // With our chunk assembled, we'll now send to the remote peer @@ -707,7 +813,7 @@ func (g *gossipSyncer) replyChanRangeQuery(query *lnwire.QueryChannelRange) erro // node for information concerning a set of short channel ID's. Our response // will be sent in a streaming chunked manner to ensure that we remain below // the current transport level message size. -func (g *gossipSyncer) replyShortChanIDs(query *lnwire.QueryShortChanIDs) error { +func (g *GossipSyncer) replyShortChanIDs(query *lnwire.QueryShortChanIDs) error { // Before responding, we'll check to ensure that the remote peer is // querying for the same chain that we're on. If not, we'll send back a // response with a complete value of zero to indicate we're on a @@ -724,13 +830,13 @@ func (g *gossipSyncer) replyShortChanIDs(query *lnwire.QueryShortChanIDs) error } if len(query.ShortChanIDs) == 0 { - log.Infof("gossipSyncer(%x): ignoring query for blank short chan ID's", - g.peerPub[:]) + log.Infof("GossipSyncer(%x): ignoring query for blank short chan ID's", + g.cfg.peerPub[:]) return nil } - log.Infof("gossipSyncer(%x): fetching chan anns for %v chans", - g.peerPub[:], len(query.ShortChanIDs)) + log.Infof("GossipSyncer(%x): fetching chan anns for %v chans", + g.cfg.peerPub[:], len(query.ShortChanIDs)) // Now that we know we're on the same chain, we'll query the channel // time series for the set of messages that we know of which satisfies @@ -766,7 +872,7 @@ func (g *gossipSyncer) replyShortChanIDs(query *lnwire.QueryShortChanIDs) error // ApplyGossipFilter applies a gossiper filter sent by the remote node to the // state machine. Once applied, we'll ensure that we don't forward any messages // to the peer that aren't within the time range of the filter. -func (g *gossipSyncer) ApplyGossipFilter(filter *lnwire.GossipTimestampRange) error { +func (g *GossipSyncer) ApplyGossipFilter(filter *lnwire.GossipTimestampRange) error { g.Lock() g.remoteUpdateHorizon = filter @@ -787,8 +893,8 @@ func (g *gossipSyncer) ApplyGossipFilter(filter *lnwire.GossipTimestampRange) er return err } - log.Infof("gossipSyncer(%x): applying new update horizon: start=%v, "+ - "end=%v, backlog_size=%v", g.peerPub[:], startTime, endTime, + log.Infof("GossipSyncer(%x): applying new update horizon: start=%v, "+ + "end=%v, backlog_size=%v", g.cfg.peerPub[:], startTime, endTime, len(newUpdatestoSend)) // If we don't have any to send, then we can return early. @@ -813,17 +919,19 @@ func (g *gossipSyncer) ApplyGossipFilter(filter *lnwire.GossipTimestampRange) er // FilterGossipMsgs takes a set of gossip messages, and only send it to a peer // iff the message is within the bounds of their set gossip filter. If the peer // doesn't have a gossip filter set, then no messages will be forwarded. -func (g *gossipSyncer) FilterGossipMsgs(msgs ...msgWithSenders) { +func (g *GossipSyncer) FilterGossipMsgs(msgs ...msgWithSenders) { // If the peer doesn't have an update horizon set, then we won't send // it any new update messages. if g.remoteUpdateHorizon == nil { return } - // If we've been signalled to exit, or are exiting, then we'll stop + // If we've been signaled to exit, or are exiting, then we'll stop // short. - if atomic.LoadUint32(&g.stopped) == 1 { + select { + case <-g.quit: return + default: } // TODO(roasbeef): need to ensure that peer still online...send msg to @@ -858,7 +966,8 @@ func (g *gossipSyncer) FilterGossipMsgs(msgs ...msgWithSenders) { passesFilter := func(timeStamp uint32) bool { t := time.Unix(int64(timeStamp), 0) - return t.After(startTime) && t.Before(endTime) + return t.Equal(startTime) || + (t.After(startTime) && t.Before(endTime)) } msgsToSend := make([]lnwire.Message, 0, len(msgs)) @@ -866,7 +975,7 @@ func (g *gossipSyncer) FilterGossipMsgs(msgs ...msgWithSenders) { // If the target peer is the peer that sent us this message, // then we'll exit early as we don't need to filter this // message. - if _, ok := msg.senders[g.peerPub]; ok { + if _, ok := msg.senders[g.cfg.peerPub]; ok { continue } @@ -920,8 +1029,8 @@ func (g *gossipSyncer) FilterGossipMsgs(msgs ...msgWithSenders) { } } - log.Tracef("gossipSyncer(%x): filtered gossip msgs: set=%v, sent=%v", - g.peerPub[:], len(msgs), len(msgsToSend)) + log.Tracef("GossipSyncer(%x): filtered gossip msgs: set=%v, sent=%v", + g.cfg.peerPub[:], len(msgs), len(msgsToSend)) if len(msgsToSend) == 0 { return @@ -932,7 +1041,7 @@ func (g *gossipSyncer) FilterGossipMsgs(msgs ...msgWithSenders) { // ProcessQueryMsg is used by outside callers to pass new channel time series // queries to the internal processing goroutine. -func (g *gossipSyncer) ProcessQueryMsg(msg lnwire.Message, peerQuit <-chan struct{}) { +func (g *GossipSyncer) ProcessQueryMsg(msg lnwire.Message, peerQuit <-chan struct{}) { select { case g.gossipMsgs <- msg: case <-peerQuit: @@ -940,7 +1049,149 @@ func (g *gossipSyncer) ProcessQueryMsg(msg lnwire.Message, peerQuit <-chan struc } } -// SyncerState returns the current syncerState of the target gossipSyncer. -func (g *gossipSyncer) SyncState() syncerState { +// setSyncState sets the gossip syncer's state to the given state. +func (g *GossipSyncer) setSyncState(state syncerState) { + atomic.StoreUint32(&g.state, uint32(state)) +} + +// syncState returns the current syncerState of the target GossipSyncer. +func (g *GossipSyncer) syncState() syncerState { return syncerState(atomic.LoadUint32(&g.state)) } + +// ResetSyncedSignal returns a channel that will be closed in order to serve as +// a signal for when the GossipSyncer has reached its chansSynced state. +func (g *GossipSyncer) ResetSyncedSignal() chan struct{} { + g.Lock() + defer g.Unlock() + + syncedSignal := make(chan struct{}) + + syncState := syncerState(atomic.LoadUint32(&g.state)) + if syncState == chansSynced { + close(syncedSignal) + return syncedSignal + } + + g.syncedSignal = syncedSignal + return g.syncedSignal +} + +// ProcessSyncTransition sends a request to the gossip syncer to transition its +// sync type to a new one. +// +// NOTE: This can only be done once the gossip syncer has reached its final +// chansSynced state. +func (g *GossipSyncer) ProcessSyncTransition(newSyncType SyncerType) error { + errChan := make(chan error, 1) + select { + case g.syncTransitionReqs <- &syncTransitionReq{ + newSyncType: newSyncType, + errChan: errChan, + }: + case <-time.After(syncTransitionTimeout): + return ErrSyncTransitionTimeout + case <-g.quit: + return ErrGossipSyncerExiting + } + + select { + case err := <-errChan: + return err + case <-g.quit: + return ErrGossipSyncerExiting + } +} + +// handleSyncTransition handles a new sync type transition request. +// +// NOTE: The gossip syncer might have another sync state as a result of this +// transition. +func (g *GossipSyncer) handleSyncTransition(req *syncTransitionReq) error { + // Return early from any NOP sync transitions. + syncType := g.SyncType() + if syncType == req.newSyncType { + return nil + } + + log.Debugf("GossipSyncer(%x): transitioning from %v to %v", + g.cfg.peerPub, syncType, req.newSyncType) + + var ( + firstTimestamp time.Time + timestampRange uint32 + newState syncerState + ) + + switch req.newSyncType { + // If an active sync has been requested, then we should resume receiving + // new graph updates from the remote peer. + case ActiveSync: + firstTimestamp = time.Now() + timestampRange = math.MaxUint32 + newState = syncingChans + + // We'll set genHistoricalChanRangeQuery to false since in order + // to not perform another historical sync if we previously have. + g.genHistoricalChanRangeQuery = false + + // If a PassiveSync transition has been requested, then we should no + // longer receive any new updates from the remote peer. We can do this + // by setting our update horizon to a range in the past ensuring no + // graph updates match the timestamp range. + case PassiveSync: + firstTimestamp = zeroTimestamp + timestampRange = 0 + newState = chansSynced + + default: + return fmt.Errorf("unhandled sync transition %v", + req.newSyncType) + } + + err := g.sendGossipTimestampRange(firstTimestamp, timestampRange) + if err != nil { + return fmt.Errorf("unable to send local update horizon: %v", err) + } + + g.setSyncState(newState) + g.setSyncType(req.newSyncType) + + return nil +} + +// setSyncType sets the gossip syncer's sync type to the given type. +func (g *GossipSyncer) setSyncType(syncType SyncerType) { + atomic.StoreUint32(&g.syncType, uint32(syncType)) +} + +// SyncType returns the current SyncerType of the target GossipSyncer. +func (g *GossipSyncer) SyncType() SyncerType { + return SyncerType(atomic.LoadUint32(&g.syncType)) +} + +// historicalSync sends a request to the gossip syncer to perofmr a historical +// sync. +// +// NOTE: This can only be done once the gossip syncer has reached its final +// chansSynced state. +func (g *GossipSyncer) historicalSync() error { + select { + case g.historicalSyncReqs <- struct{}{}: + return nil + case <-time.After(syncTransitionTimeout): + return ErrSyncTransitionTimeout + case <-g.quit: + return ErrGossiperShuttingDown + } +} + +// handleHistoricalSync handles a request to the gossip syncer to perform a +// historical sync. +func (g *GossipSyncer) handleHistoricalSync() { + // We'll go back to our initial syncingChans state in order to request + // the remote peer to give us all of the channel IDs they know of + // starting from the genesis block. + g.genHistoricalChanRangeQuery = true + g.setSyncState(syncingChans) +} diff --git a/discovery/syncer_test.go b/discovery/syncer_test.go index 1887f0ba8..ee5719d80 100644 --- a/discovery/syncer_test.go +++ b/discovery/syncer_test.go @@ -13,7 +13,9 @@ import ( ) const ( - defaultEncoding = lnwire.EncodingSortedPlain + defaultEncoding = lnwire.EncodingSortedPlain + latestKnownHeight = 1337 + startHeight = latestKnownHeight - chanRangeQueryBuffer ) var ( @@ -116,21 +118,20 @@ var _ ChannelGraphTimeSeries = (*mockChannelGraphTimeSeries)(nil) func newTestSyncer(hID lnwire.ShortChannelID, encodingType lnwire.ShortChanIDEncoding, chunkSize int32, -) (chan []lnwire.Message, *gossipSyncer, *mockChannelGraphTimeSeries) { +) (chan []lnwire.Message, *GossipSyncer, *mockChannelGraphTimeSeries) { msgChan := make(chan []lnwire.Message, 20) cfg := gossipSyncerCfg{ - syncChanUpdates: true, - channelSeries: newMockChannelGraphTimeSeries(hID), - encodingType: encodingType, - chunkSize: chunkSize, + channelSeries: newMockChannelGraphTimeSeries(hID), + encodingType: encodingType, + chunkSize: chunkSize, sendToPeer: func(msgs ...lnwire.Message) error { msgChan <- msgs return nil }, delayedQueryReplyInterval: 2 * time.Second, } - syncer := newGossiperSyncer(cfg) + syncer := newGossipSyncer(cfg) return msgChan, syncer, cfg.channelSeries.(*mockChannelGraphTimeSeries) } @@ -140,7 +141,7 @@ func newTestSyncer(hID lnwire.ShortChannelID, func TestGossipSyncerFilterGossipMsgsNoHorizon(t *testing.T) { t.Parallel() - // First, we'll create a gossipSyncer instance with a canned sendToPeer + // First, we'll create a GossipSyncer instance with a canned sendToPeer // message to allow us to intercept their potential sends. msgChan, syncer, _ := newTestSyncer( lnwire.NewShortChanIDFromInt(10), defaultEncoding, @@ -185,7 +186,7 @@ func unixStamp(a int64) uint32 { func TestGossipSyncerFilterGossipMsgsAllInMemory(t *testing.T) { t.Parallel() - // First, we'll create a gossipSyncer instance with a canned sendToPeer + // First, we'll create a GossipSyncer instance with a canned sendToPeer // message to allow us to intercept their potential sends. msgChan, syncer, chanSeries := newTestSyncer( lnwire.NewShortChanIDFromInt(10), defaultEncoding, @@ -315,7 +316,7 @@ func TestGossipSyncerFilterGossipMsgsAllInMemory(t *testing.T) { func TestGossipSyncerApplyGossipFilter(t *testing.T) { t.Parallel() - // First, we'll create a gossipSyncer instance with a canned sendToPeer + // First, we'll create a GossipSyncer instance with a canned sendToPeer // message to allow us to intercept their potential sends. msgChan, syncer, chanSeries := newTestSyncer( lnwire.NewShortChanIDFromInt(10), defaultEncoding, @@ -413,7 +414,7 @@ func TestGossipSyncerApplyGossipFilter(t *testing.T) { func TestGossipSyncerReplyShortChanIDsWrongChainHash(t *testing.T) { t.Parallel() - // First, we'll create a gossipSyncer instance with a canned sendToPeer + // First, we'll create a GossipSyncer instance with a canned sendToPeer // message to allow us to intercept their potential sends. msgChan, syncer, _ := newTestSyncer( lnwire.NewShortChanIDFromInt(10), defaultEncoding, @@ -464,7 +465,7 @@ func TestGossipSyncerReplyShortChanIDsWrongChainHash(t *testing.T) { func TestGossipSyncerReplyShortChanIDs(t *testing.T) { t.Parallel() - // First, we'll create a gossipSyncer instance with a canned sendToPeer + // First, we'll create a GossipSyncer instance with a canned sendToPeer // message to allow us to intercept their potential sends. msgChan, syncer, chanSeries := newTestSyncer( lnwire.NewShortChanIDFromInt(10), defaultEncoding, @@ -718,7 +719,7 @@ func TestGossipSyncerReplyChanRangeQueryNoNewChans(t *testing.T) { func TestGossipSyncerGenChanRangeQuery(t *testing.T) { t.Parallel() - // First, we'll create a gossipSyncer instance with a canned sendToPeer + // First, we'll create a GossipSyncer instance with a canned sendToPeer // message to allow us to intercept their potential sends. const startingHeight = 200 _, syncer, _ := newTestSyncer( @@ -729,7 +730,7 @@ func TestGossipSyncerGenChanRangeQuery(t *testing.T) { // If we now ask the syncer to generate an initial range query, it // should return a start height that's back chanRangeQueryBuffer // blocks. - rangeQuery, err := syncer.genChanRangeQuery() + rangeQuery, err := syncer.genChanRangeQuery(false) if err != nil { t.Fatalf("unable to resp: %v", err) } @@ -742,7 +743,22 @@ func TestGossipSyncerGenChanRangeQuery(t *testing.T) { } if rangeQuery.NumBlocks != math.MaxUint32-firstHeight { t.Fatalf("wrong num blocks: expected %v, got %v", - rangeQuery.NumBlocks, math.MaxUint32-firstHeight) + math.MaxUint32-firstHeight, rangeQuery.NumBlocks) + } + + // Generating a historical range query should result in a start height + // of 0. + rangeQuery, err = syncer.genChanRangeQuery(true) + if err != nil { + t.Fatalf("unable to resp: %v", err) + } + if rangeQuery.FirstBlockHeight != 0 { + t.Fatalf("incorrect chan range query: expected %v, %v", 0, + rangeQuery.FirstBlockHeight) + } + if rangeQuery.NumBlocks != math.MaxUint32 { + t.Fatalf("wrong num blocks: expected %v, got %v", + math.MaxUint32, rangeQuery.NumBlocks) } } @@ -753,7 +769,7 @@ func TestGossipSyncerGenChanRangeQuery(t *testing.T) { func TestGossipSyncerProcessChanRangeReply(t *testing.T) { t.Parallel() - // First, we'll create a gossipSyncer instance with a canned sendToPeer + // First, we'll create a GossipSyncer instance with a canned sendToPeer // message to allow us to intercept their potential sends. _, syncer, chanSeries := newTestSyncer( lnwire.NewShortChanIDFromInt(10), defaultEncoding, defaultChunkSize, @@ -827,7 +843,7 @@ func TestGossipSyncerProcessChanRangeReply(t *testing.T) { t.Fatalf("unable to process reply: %v", err) } - if syncer.SyncState() != queryNewChannels { + if syncer.syncState() != queryNewChannels { t.Fatalf("wrong state: expected %v instead got %v", queryNewChannels, syncer.state) } @@ -860,7 +876,7 @@ func TestGossipSyncerProcessChanRangeReply(t *testing.T) { t.Fatalf("unable to process reply: %v", err) } - if syncer.SyncState() != chansSynced { + if syncer.syncState() != chansSynced { t.Fatalf("wrong state: expected %v instead got %v", chansSynced, syncer.state) } @@ -878,7 +894,7 @@ func TestGossipSyncerSynchronizeChanIDs(t *testing.T) { // queries: two full chunks, and one lingering chunk. const chunkSize = 2 - // First, we'll create a gossipSyncer instance with a canned sendToPeer + // First, we'll create a GossipSyncer instance with a canned sendToPeer // message to allow us to intercept their potential sends. msgChan, syncer, _ := newTestSyncer( lnwire.NewShortChanIDFromInt(10), defaultEncoding, chunkSize, @@ -997,7 +1013,7 @@ func TestGossipSyncerDelayDOS(t *testing.T) { const numDelayedQueries = 2 const delayTolerance = time.Millisecond * 200 - // First, we'll create two gossipSyncer instances with a canned + // First, we'll create two GossipSyncer instances with a canned // sendToPeer message to allow us to intercept their potential sends. startHeight := lnwire.ShortChannelID{ BlockHeight: 1144, @@ -1390,7 +1406,7 @@ func TestGossipSyncerRoutineSync(t *testing.T) { // queries: two full chunks, and one lingering chunk. const chunkSize = 2 - // First, we'll create two gossipSyncer instances with a canned + // First, we'll create two GossipSyncer instances with a canned // sendToPeer message to allow us to intercept their potential sends. startHeight := lnwire.ShortChannelID{ BlockHeight: 1144, @@ -1730,7 +1746,7 @@ func TestGossipSyncerAlreadySynced(t *testing.T) { // queries: two full chunks, and one lingering chunk. const chunkSize = 2 - // First, we'll create two gossipSyncer instances with a canned + // First, we'll create two GossipSyncer instances with a canned // sendToPeer message to allow us to intercept their potential sends. startHeight := lnwire.ShortChannelID{ BlockHeight: 1144, @@ -1941,3 +1957,236 @@ func TestGossipSyncerAlreadySynced(t *testing.T) { } } } + +// TestGossipSyncerSyncTransitions ensures that the gossip syncer properly +// carries out its duties when accepting a new sync transition request. +func TestGossipSyncerSyncTransitions(t *testing.T) { + t.Parallel() + + assertMsgSent := func(t *testing.T, msgChan chan []lnwire.Message, + msg lnwire.Message) { + + t.Helper() + + var msgSent lnwire.Message + select { + case msgs := <-msgChan: + if len(msgs) != 1 { + t.Fatal("expected to send a single message at "+ + "a time, got %d", len(msgs)) + } + msgSent = msgs[0] + case <-time.After(time.Second): + t.Fatalf("expected to send %T message", msg) + } + + if !reflect.DeepEqual(msgSent, msg) { + t.Fatalf("expected to send message: %v\ngot: %v", + spew.Sdump(msg), spew.Sdump(msgSent)) + } + } + + tests := []struct { + name string + entrySyncType SyncerType + finalSyncType SyncerType + assert func(t *testing.T, msgChan chan []lnwire.Message, + syncer *GossipSyncer) + }{ + { + name: "active to passive", + entrySyncType: ActiveSync, + finalSyncType: PassiveSync, + assert: func(t *testing.T, msgChan chan []lnwire.Message, + g *GossipSyncer) { + + // When transitioning from active to passive, we + // should expect to see a new local update + // horizon sent to the remote peer indicating + // that it would not like to receive any future + // updates. + assertMsgSent(t, msgChan, &lnwire.GossipTimestampRange{ + FirstTimestamp: uint32(zeroTimestamp.Unix()), + TimestampRange: 0, + }) + + syncState := g.syncState() + if syncState != chansSynced { + t.Fatalf("expected syncerState %v, "+ + "got %v", chansSynced, + syncState) + } + }, + }, + { + name: "passive to active", + entrySyncType: PassiveSync, + finalSyncType: ActiveSync, + assert: func(t *testing.T, msgChan chan []lnwire.Message, + g *GossipSyncer) { + + // When transitioning from historical to active, + // we should expect to see a new local update + // horizon sent to the remote peer indicating + // that it would like to receive any future + // updates. + firstTimestamp := uint32(time.Now().Unix()) + assertMsgSent(t, msgChan, &lnwire.GossipTimestampRange{ + FirstTimestamp: firstTimestamp, + TimestampRange: math.MaxUint32, + }) + + // The local update horizon should be followed + // by a QueryChannelRange message sent to the + // remote peer requesting all channels it + // knows of from the highest height the syncer + // knows of. + assertMsgSent(t, msgChan, &lnwire.QueryChannelRange{ + FirstBlockHeight: startHeight, + NumBlocks: math.MaxUint32 - startHeight, + }) + + syncState := g.syncState() + if syncState != waitingQueryRangeReply { + t.Fatalf("expected syncerState %v, "+ + "got %v", waitingQueryRangeReply, + syncState) + } + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + // We'll start each test by creating our syncer. We'll + // initialize it with a state of chansSynced, as that's + // the only time when it can process sync transitions. + msgChan, syncer, _ := newTestSyncer( + lnwire.ShortChannelID{ + BlockHeight: latestKnownHeight, + }, + defaultEncoding, defaultChunkSize, + ) + syncer.setSyncState(chansSynced) + + // We'll set the initial syncType to what the test + // demands. + syncer.setSyncType(test.entrySyncType) + + // We'll then start the syncer in order to process the + // request. + syncer.Start() + defer syncer.Stop() + + syncer.ProcessSyncTransition(test.finalSyncType) + + // The syncer should now have the expected final + // SyncerType that the test expects. + syncType := syncer.SyncType() + if syncType != test.finalSyncType { + t.Fatalf("expected syncType %v, got %v", + test.finalSyncType, syncType) + } + + // Finally, we'll run a set of assertions for each test + // to ensure the syncer performed its expected duties + // after processing its sync transition. + test.assert(t, msgChan, syncer) + }) + } +} + +// TestGossipSyncerHistoricalSync tests that a gossip syncer can perform a +// historical sync with the remote peer. +func TestGossipSyncerHistoricalSync(t *testing.T) { + t.Parallel() + + // We'll create a new gossip syncer and manually override its state to + // chansSynced. This is necessary as the syncer can only process + // historical sync requests in this state. + msgChan, syncer, _ := newTestSyncer( + lnwire.ShortChannelID{BlockHeight: latestKnownHeight}, + defaultEncoding, defaultChunkSize, + ) + syncer.setSyncType(PassiveSync) + syncer.setSyncState(chansSynced) + + syncer.Start() + defer syncer.Stop() + + syncer.historicalSync() + + // We should expect to see a single lnwire.QueryChannelRange message be + // sent to the remote peer with a FirstBlockHeight of 0. + expectedMsg := &lnwire.QueryChannelRange{ + FirstBlockHeight: 0, + NumBlocks: math.MaxUint32, + } + + select { + case msgs := <-msgChan: + if len(msgs) != 1 { + t.Fatalf("expected to send a single "+ + "lnwire.QueryChannelRange message, got %d", + len(msgs)) + } + if !reflect.DeepEqual(msgs[0], expectedMsg) { + t.Fatalf("expected to send message: %v\ngot: %v", + spew.Sdump(expectedMsg), spew.Sdump(msgs[0])) + } + case <-time.After(time.Second): + t.Fatalf("expected to send a lnwire.QueryChannelRange message") + } +} + +// TestGossipSyncerSyncedSignal ensures that we receive a signal when a gossip +// syncer reaches its terminal chansSynced state. +func TestGossipSyncerSyncedSignal(t *testing.T) { + t.Parallel() + + // We'll create a new gossip syncer and manually override its state to + // chansSynced. + _, syncer, _ := newTestSyncer( + lnwire.NewShortChanIDFromInt(10), defaultEncoding, + defaultChunkSize, + ) + syncer.setSyncState(chansSynced) + + // We'll go ahead and request a signal to be notified of when it reaches + // this state. + signalChan := syncer.ResetSyncedSignal() + + // Starting the gossip syncer should cause the signal to be delivered. + syncer.Start() + + select { + case <-signalChan: + case <-time.After(time.Second): + t.Fatal("expected to receive chansSynced signal") + } + + syncer.Stop() + + // We'll try this again, but this time we'll request the signal after + // the syncer is active and has already reached its chansSynced state. + _, syncer, _ = newTestSyncer( + lnwire.NewShortChanIDFromInt(10), defaultEncoding, + defaultChunkSize, + ) + + syncer.setSyncState(chansSynced) + + syncer.Start() + defer syncer.Stop() + + signalChan = syncer.ResetSyncedSignal() + + // The signal should be delivered immediately. + select { + case <-signalChan: + case <-time.After(time.Second): + t.Fatal("expected to receive chansSynced signal") + } +} diff --git a/lnrpc/rpc.pb.go b/lnrpc/rpc.pb.go index 9412758eb..f413f8096 100644 --- a/lnrpc/rpc.pb.go +++ b/lnrpc/rpc.pb.go @@ -55,7 +55,7 @@ func (x AddressType) String() string { return proto.EnumName(AddressType_name, int32(x)) } func (AddressType) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{0} + return fileDescriptor_rpc_854431eb46daab93, []int{0} } type ChannelCloseSummary_ClosureType int32 @@ -90,7 +90,39 @@ func (x ChannelCloseSummary_ClosureType) String() string { return proto.EnumName(ChannelCloseSummary_ClosureType_name, int32(x)) } func (ChannelCloseSummary_ClosureType) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{41, 0} + return fileDescriptor_rpc_854431eb46daab93, []int{41, 0} +} + +type Peer_SyncType int32 + +const ( + // * + // Denotes that we cannot determine the peer's current sync type. + Peer_UNKNOWN_SYNC Peer_SyncType = 0 + // * + // Denotes that we are actively receiving new graph updates from the peer. + Peer_ACTIVE_SYNC Peer_SyncType = 1 + // * + // Denotes that we are not receiving new graph updates from the peer. + Peer_PASSIVE_SYNC Peer_SyncType = 2 +) + +var Peer_SyncType_name = map[int32]string{ + 0: "UNKNOWN_SYNC", + 1: "ACTIVE_SYNC", + 2: "PASSIVE_SYNC", +} +var Peer_SyncType_value = map[string]int32{ + "UNKNOWN_SYNC": 0, + "ACTIVE_SYNC": 1, + "PASSIVE_SYNC": 2, +} + +func (x Peer_SyncType) String() string { + return proto.EnumName(Peer_SyncType_name, int32(x)) +} +func (Peer_SyncType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_rpc_854431eb46daab93, []int{44, 0} } type ChannelEventUpdate_UpdateType int32 @@ -119,7 +151,7 @@ func (x ChannelEventUpdate_UpdateType) String() string { return proto.EnumName(ChannelEventUpdate_UpdateType_name, int32(x)) } func (ChannelEventUpdate_UpdateType) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{62, 0} + return fileDescriptor_rpc_854431eb46daab93, []int{62, 0} } type Invoice_InvoiceState int32 @@ -148,7 +180,7 @@ func (x Invoice_InvoiceState) String() string { return proto.EnumName(Invoice_InvoiceState_name, int32(x)) } func (Invoice_InvoiceState) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{92, 0} + return fileDescriptor_rpc_854431eb46daab93, []int{92, 0} } type GenSeedRequest struct { @@ -169,7 +201,7 @@ func (m *GenSeedRequest) Reset() { *m = GenSeedRequest{} } func (m *GenSeedRequest) String() string { return proto.CompactTextString(m) } func (*GenSeedRequest) ProtoMessage() {} func (*GenSeedRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{0} + return fileDescriptor_rpc_854431eb46daab93, []int{0} } func (m *GenSeedRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_GenSeedRequest.Unmarshal(m, b) @@ -224,7 +256,7 @@ func (m *GenSeedResponse) Reset() { *m = GenSeedResponse{} } func (m *GenSeedResponse) String() string { return proto.CompactTextString(m) } func (*GenSeedResponse) ProtoMessage() {} func (*GenSeedResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{1} + return fileDescriptor_rpc_854431eb46daab93, []int{1} } func (m *GenSeedResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_GenSeedResponse.Unmarshal(m, b) @@ -297,7 +329,7 @@ func (m *InitWalletRequest) Reset() { *m = InitWalletRequest{} } func (m *InitWalletRequest) String() string { return proto.CompactTextString(m) } func (*InitWalletRequest) ProtoMessage() {} func (*InitWalletRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{2} + return fileDescriptor_rpc_854431eb46daab93, []int{2} } func (m *InitWalletRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_InitWalletRequest.Unmarshal(m, b) @@ -362,7 +394,7 @@ func (m *InitWalletResponse) Reset() { *m = InitWalletResponse{} } func (m *InitWalletResponse) String() string { return proto.CompactTextString(m) } func (*InitWalletResponse) ProtoMessage() {} func (*InitWalletResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{3} + return fileDescriptor_rpc_854431eb46daab93, []int{3} } func (m *InitWalletResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_InitWalletResponse.Unmarshal(m, b) @@ -412,7 +444,7 @@ func (m *UnlockWalletRequest) Reset() { *m = UnlockWalletRequest{} } func (m *UnlockWalletRequest) String() string { return proto.CompactTextString(m) } func (*UnlockWalletRequest) ProtoMessage() {} func (*UnlockWalletRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{4} + return fileDescriptor_rpc_854431eb46daab93, []int{4} } func (m *UnlockWalletRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_UnlockWalletRequest.Unmarshal(m, b) @@ -463,7 +495,7 @@ func (m *UnlockWalletResponse) Reset() { *m = UnlockWalletResponse{} } func (m *UnlockWalletResponse) String() string { return proto.CompactTextString(m) } func (*UnlockWalletResponse) ProtoMessage() {} func (*UnlockWalletResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{5} + return fileDescriptor_rpc_854431eb46daab93, []int{5} } func (m *UnlockWalletResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_UnlockWalletResponse.Unmarshal(m, b) @@ -501,7 +533,7 @@ func (m *ChangePasswordRequest) Reset() { *m = ChangePasswordRequest{} } func (m *ChangePasswordRequest) String() string { return proto.CompactTextString(m) } func (*ChangePasswordRequest) ProtoMessage() {} func (*ChangePasswordRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{6} + return fileDescriptor_rpc_854431eb46daab93, []int{6} } func (m *ChangePasswordRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ChangePasswordRequest.Unmarshal(m, b) @@ -545,7 +577,7 @@ func (m *ChangePasswordResponse) Reset() { *m = ChangePasswordResponse{} func (m *ChangePasswordResponse) String() string { return proto.CompactTextString(m) } func (*ChangePasswordResponse) ProtoMessage() {} func (*ChangePasswordResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{7} + return fileDescriptor_rpc_854431eb46daab93, []int{7} } func (m *ChangePasswordResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ChangePasswordResponse.Unmarshal(m, b) @@ -587,7 +619,7 @@ func (m *Utxo) Reset() { *m = Utxo{} } func (m *Utxo) String() string { return proto.CompactTextString(m) } func (*Utxo) ProtoMessage() {} func (*Utxo) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{8} + return fileDescriptor_rpc_854431eb46daab93, []int{8} } func (m *Utxo) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_Utxo.Unmarshal(m, b) @@ -675,7 +707,7 @@ func (m *Transaction) Reset() { *m = Transaction{} } func (m *Transaction) String() string { return proto.CompactTextString(m) } func (*Transaction) ProtoMessage() {} func (*Transaction) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{9} + return fileDescriptor_rpc_854431eb46daab93, []int{9} } func (m *Transaction) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_Transaction.Unmarshal(m, b) @@ -761,7 +793,7 @@ func (m *GetTransactionsRequest) Reset() { *m = GetTransactionsRequest{} func (m *GetTransactionsRequest) String() string { return proto.CompactTextString(m) } func (*GetTransactionsRequest) ProtoMessage() {} func (*GetTransactionsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{10} + return fileDescriptor_rpc_854431eb46daab93, []int{10} } func (m *GetTransactionsRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_GetTransactionsRequest.Unmarshal(m, b) @@ -793,7 +825,7 @@ func (m *TransactionDetails) Reset() { *m = TransactionDetails{} } func (m *TransactionDetails) String() string { return proto.CompactTextString(m) } func (*TransactionDetails) ProtoMessage() {} func (*TransactionDetails) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{11} + return fileDescriptor_rpc_854431eb46daab93, []int{11} } func (m *TransactionDetails) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_TransactionDetails.Unmarshal(m, b) @@ -834,7 +866,7 @@ func (m *FeeLimit) Reset() { *m = FeeLimit{} } func (m *FeeLimit) String() string { return proto.CompactTextString(m) } func (*FeeLimit) ProtoMessage() {} func (*FeeLimit) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{12} + return fileDescriptor_rpc_854431eb46daab93, []int{12} } func (m *FeeLimit) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_FeeLimit.Unmarshal(m, b) @@ -998,7 +1030,7 @@ func (m *SendRequest) Reset() { *m = SendRequest{} } func (m *SendRequest) String() string { return proto.CompactTextString(m) } func (*SendRequest) ProtoMessage() {} func (*SendRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{13} + return fileDescriptor_rpc_854431eb46daab93, []int{13} } func (m *SendRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_SendRequest.Unmarshal(m, b) @@ -1102,7 +1134,7 @@ func (m *SendResponse) Reset() { *m = SendResponse{} } func (m *SendResponse) String() string { return proto.CompactTextString(m) } func (*SendResponse) ProtoMessage() {} func (*SendResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{14} + return fileDescriptor_rpc_854431eb46daab93, []int{14} } func (m *SendResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_SendResponse.Unmarshal(m, b) @@ -1172,7 +1204,7 @@ func (m *SendToRouteRequest) Reset() { *m = SendToRouteRequest{} } func (m *SendToRouteRequest) String() string { return proto.CompactTextString(m) } func (*SendToRouteRequest) ProtoMessage() {} func (*SendToRouteRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{15} + return fileDescriptor_rpc_854431eb46daab93, []int{15} } func (m *SendToRouteRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_SendToRouteRequest.Unmarshal(m, b) @@ -1237,7 +1269,7 @@ func (m *ChannelPoint) Reset() { *m = ChannelPoint{} } func (m *ChannelPoint) String() string { return proto.CompactTextString(m) } func (*ChannelPoint) ProtoMessage() {} func (*ChannelPoint) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{16} + return fileDescriptor_rpc_854431eb46daab93, []int{16} } func (m *ChannelPoint) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ChannelPoint.Unmarshal(m, b) @@ -1383,7 +1415,7 @@ func (m *OutPoint) Reset() { *m = OutPoint{} } func (m *OutPoint) String() string { return proto.CompactTextString(m) } func (*OutPoint) ProtoMessage() {} func (*OutPoint) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{17} + return fileDescriptor_rpc_854431eb46daab93, []int{17} } func (m *OutPoint) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_OutPoint.Unmarshal(m, b) @@ -1438,7 +1470,7 @@ func (m *LightningAddress) Reset() { *m = LightningAddress{} } func (m *LightningAddress) String() string { return proto.CompactTextString(m) } func (*LightningAddress) ProtoMessage() {} func (*LightningAddress) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{18} + return fileDescriptor_rpc_854431eb46daab93, []int{18} } func (m *LightningAddress) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_LightningAddress.Unmarshal(m, b) @@ -1486,7 +1518,7 @@ func (m *EstimateFeeRequest) Reset() { *m = EstimateFeeRequest{} } func (m *EstimateFeeRequest) String() string { return proto.CompactTextString(m) } func (*EstimateFeeRequest) ProtoMessage() {} func (*EstimateFeeRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{19} + return fileDescriptor_rpc_854431eb46daab93, []int{19} } func (m *EstimateFeeRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_EstimateFeeRequest.Unmarshal(m, b) @@ -1534,7 +1566,7 @@ func (m *EstimateFeeResponse) Reset() { *m = EstimateFeeResponse{} } func (m *EstimateFeeResponse) String() string { return proto.CompactTextString(m) } func (*EstimateFeeResponse) ProtoMessage() {} func (*EstimateFeeResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{20} + return fileDescriptor_rpc_854431eb46daab93, []int{20} } func (m *EstimateFeeResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_EstimateFeeResponse.Unmarshal(m, b) @@ -1584,7 +1616,7 @@ func (m *SendManyRequest) Reset() { *m = SendManyRequest{} } func (m *SendManyRequest) String() string { return proto.CompactTextString(m) } func (*SendManyRequest) ProtoMessage() {} func (*SendManyRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{21} + return fileDescriptor_rpc_854431eb46daab93, []int{21} } func (m *SendManyRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_SendManyRequest.Unmarshal(m, b) @@ -1637,7 +1669,7 @@ func (m *SendManyResponse) Reset() { *m = SendManyResponse{} } func (m *SendManyResponse) String() string { return proto.CompactTextString(m) } func (*SendManyResponse) ProtoMessage() {} func (*SendManyResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{22} + return fileDescriptor_rpc_854431eb46daab93, []int{22} } func (m *SendManyResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_SendManyResponse.Unmarshal(m, b) @@ -1687,7 +1719,7 @@ func (m *SendCoinsRequest) Reset() { *m = SendCoinsRequest{} } func (m *SendCoinsRequest) String() string { return proto.CompactTextString(m) } func (*SendCoinsRequest) ProtoMessage() {} func (*SendCoinsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{23} + return fileDescriptor_rpc_854431eb46daab93, []int{23} } func (m *SendCoinsRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_SendCoinsRequest.Unmarshal(m, b) @@ -1754,7 +1786,7 @@ func (m *SendCoinsResponse) Reset() { *m = SendCoinsResponse{} } func (m *SendCoinsResponse) String() string { return proto.CompactTextString(m) } func (*SendCoinsResponse) ProtoMessage() {} func (*SendCoinsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{24} + return fileDescriptor_rpc_854431eb46daab93, []int{24} } func (m *SendCoinsResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_SendCoinsResponse.Unmarshal(m, b) @@ -1795,7 +1827,7 @@ func (m *ListUnspentRequest) Reset() { *m = ListUnspentRequest{} } func (m *ListUnspentRequest) String() string { return proto.CompactTextString(m) } func (*ListUnspentRequest) ProtoMessage() {} func (*ListUnspentRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{25} + return fileDescriptor_rpc_854431eb46daab93, []int{25} } func (m *ListUnspentRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ListUnspentRequest.Unmarshal(m, b) @@ -1841,7 +1873,7 @@ func (m *ListUnspentResponse) Reset() { *m = ListUnspentResponse{} } func (m *ListUnspentResponse) String() string { return proto.CompactTextString(m) } func (*ListUnspentResponse) ProtoMessage() {} func (*ListUnspentResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{26} + return fileDescriptor_rpc_854431eb46daab93, []int{26} } func (m *ListUnspentResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ListUnspentResponse.Unmarshal(m, b) @@ -1880,7 +1912,7 @@ func (m *NewAddressRequest) Reset() { *m = NewAddressRequest{} } func (m *NewAddressRequest) String() string { return proto.CompactTextString(m) } func (*NewAddressRequest) ProtoMessage() {} func (*NewAddressRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{27} + return fileDescriptor_rpc_854431eb46daab93, []int{27} } func (m *NewAddressRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_NewAddressRequest.Unmarshal(m, b) @@ -1919,7 +1951,7 @@ func (m *NewAddressResponse) Reset() { *m = NewAddressResponse{} } func (m *NewAddressResponse) String() string { return proto.CompactTextString(m) } func (*NewAddressResponse) ProtoMessage() {} func (*NewAddressResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{28} + return fileDescriptor_rpc_854431eb46daab93, []int{28} } func (m *NewAddressResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_NewAddressResponse.Unmarshal(m, b) @@ -1958,7 +1990,7 @@ func (m *SignMessageRequest) Reset() { *m = SignMessageRequest{} } func (m *SignMessageRequest) String() string { return proto.CompactTextString(m) } func (*SignMessageRequest) ProtoMessage() {} func (*SignMessageRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{29} + return fileDescriptor_rpc_854431eb46daab93, []int{29} } func (m *SignMessageRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_SignMessageRequest.Unmarshal(m, b) @@ -1997,7 +2029,7 @@ func (m *SignMessageResponse) Reset() { *m = SignMessageResponse{} } func (m *SignMessageResponse) String() string { return proto.CompactTextString(m) } func (*SignMessageResponse) ProtoMessage() {} func (*SignMessageResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{30} + return fileDescriptor_rpc_854431eb46daab93, []int{30} } func (m *SignMessageResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_SignMessageResponse.Unmarshal(m, b) @@ -2038,7 +2070,7 @@ func (m *VerifyMessageRequest) Reset() { *m = VerifyMessageRequest{} } func (m *VerifyMessageRequest) String() string { return proto.CompactTextString(m) } func (*VerifyMessageRequest) ProtoMessage() {} func (*VerifyMessageRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{31} + return fileDescriptor_rpc_854431eb46daab93, []int{31} } func (m *VerifyMessageRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_VerifyMessageRequest.Unmarshal(m, b) @@ -2086,7 +2118,7 @@ func (m *VerifyMessageResponse) Reset() { *m = VerifyMessageResponse{} } func (m *VerifyMessageResponse) String() string { return proto.CompactTextString(m) } func (*VerifyMessageResponse) ProtoMessage() {} func (*VerifyMessageResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{32} + return fileDescriptor_rpc_854431eb46daab93, []int{32} } func (m *VerifyMessageResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_VerifyMessageResponse.Unmarshal(m, b) @@ -2135,7 +2167,7 @@ func (m *ConnectPeerRequest) Reset() { *m = ConnectPeerRequest{} } func (m *ConnectPeerRequest) String() string { return proto.CompactTextString(m) } func (*ConnectPeerRequest) ProtoMessage() {} func (*ConnectPeerRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{33} + return fileDescriptor_rpc_854431eb46daab93, []int{33} } func (m *ConnectPeerRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ConnectPeerRequest.Unmarshal(m, b) @@ -2179,7 +2211,7 @@ func (m *ConnectPeerResponse) Reset() { *m = ConnectPeerResponse{} } func (m *ConnectPeerResponse) String() string { return proto.CompactTextString(m) } func (*ConnectPeerResponse) ProtoMessage() {} func (*ConnectPeerResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{34} + return fileDescriptor_rpc_854431eb46daab93, []int{34} } func (m *ConnectPeerResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ConnectPeerResponse.Unmarshal(m, b) @@ -2211,7 +2243,7 @@ func (m *DisconnectPeerRequest) Reset() { *m = DisconnectPeerRequest{} } func (m *DisconnectPeerRequest) String() string { return proto.CompactTextString(m) } func (*DisconnectPeerRequest) ProtoMessage() {} func (*DisconnectPeerRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{35} + return fileDescriptor_rpc_854431eb46daab93, []int{35} } func (m *DisconnectPeerRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_DisconnectPeerRequest.Unmarshal(m, b) @@ -2248,7 +2280,7 @@ func (m *DisconnectPeerResponse) Reset() { *m = DisconnectPeerResponse{} func (m *DisconnectPeerResponse) String() string { return proto.CompactTextString(m) } func (*DisconnectPeerResponse) ProtoMessage() {} func (*DisconnectPeerResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{36} + return fileDescriptor_rpc_854431eb46daab93, []int{36} } func (m *DisconnectPeerResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_DisconnectPeerResponse.Unmarshal(m, b) @@ -2282,7 +2314,7 @@ func (m *HTLC) Reset() { *m = HTLC{} } func (m *HTLC) String() string { return proto.CompactTextString(m) } func (*HTLC) ProtoMessage() {} func (*HTLC) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{37} + return fileDescriptor_rpc_854431eb46daab93, []int{37} } func (m *HTLC) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_HTLC.Unmarshal(m, b) @@ -2397,7 +2429,7 @@ func (m *Channel) Reset() { *m = Channel{} } func (m *Channel) String() string { return proto.CompactTextString(m) } func (*Channel) ProtoMessage() {} func (*Channel) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{38} + return fileDescriptor_rpc_854431eb46daab93, []int{38} } func (m *Channel) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_Channel.Unmarshal(m, b) @@ -2564,7 +2596,7 @@ func (m *ListChannelsRequest) Reset() { *m = ListChannelsRequest{} } func (m *ListChannelsRequest) String() string { return proto.CompactTextString(m) } func (*ListChannelsRequest) ProtoMessage() {} func (*ListChannelsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{39} + return fileDescriptor_rpc_854431eb46daab93, []int{39} } func (m *ListChannelsRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ListChannelsRequest.Unmarshal(m, b) @@ -2624,7 +2656,7 @@ func (m *ListChannelsResponse) Reset() { *m = ListChannelsResponse{} } func (m *ListChannelsResponse) String() string { return proto.CompactTextString(m) } func (*ListChannelsResponse) ProtoMessage() {} func (*ListChannelsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{40} + return fileDescriptor_rpc_854431eb46daab93, []int{40} } func (m *ListChannelsResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ListChannelsResponse.Unmarshal(m, b) @@ -2681,7 +2713,7 @@ func (m *ChannelCloseSummary) Reset() { *m = ChannelCloseSummary{} } func (m *ChannelCloseSummary) String() string { return proto.CompactTextString(m) } func (*ChannelCloseSummary) ProtoMessage() {} func (*ChannelCloseSummary) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{41} + return fileDescriptor_rpc_854431eb46daab93, []int{41} } func (m *ChannelCloseSummary) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ChannelCloseSummary.Unmarshal(m, b) @@ -2787,7 +2819,7 @@ func (m *ClosedChannelsRequest) Reset() { *m = ClosedChannelsRequest{} } func (m *ClosedChannelsRequest) String() string { return proto.CompactTextString(m) } func (*ClosedChannelsRequest) ProtoMessage() {} func (*ClosedChannelsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{42} + return fileDescriptor_rpc_854431eb46daab93, []int{42} } func (m *ClosedChannelsRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ClosedChannelsRequest.Unmarshal(m, b) @@ -2860,7 +2892,7 @@ func (m *ClosedChannelsResponse) Reset() { *m = ClosedChannelsResponse{} func (m *ClosedChannelsResponse) String() string { return proto.CompactTextString(m) } func (*ClosedChannelsResponse) ProtoMessage() {} func (*ClosedChannelsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{43} + return fileDescriptor_rpc_854431eb46daab93, []int{43} } func (m *ClosedChannelsResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ClosedChannelsResponse.Unmarshal(m, b) @@ -2903,17 +2935,19 @@ type Peer struct { // / A channel is inbound if the counterparty initiated the channel Inbound bool `protobuf:"varint,8,opt,name=inbound,proto3" json:"inbound,omitempty"` // / Ping time to this peer - PingTime int64 `protobuf:"varint,9,opt,name=ping_time,proto3" json:"ping_time,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + PingTime int64 `protobuf:"varint,9,opt,name=ping_time,proto3" json:"ping_time,omitempty"` + // The type of sync we are currently performing with this peer. + SyncType Peer_SyncType `protobuf:"varint,10,opt,name=sync_type,proto3,enum=lnrpc.Peer_SyncType" json:"sync_type,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *Peer) Reset() { *m = Peer{} } func (m *Peer) String() string { return proto.CompactTextString(m) } func (*Peer) ProtoMessage() {} func (*Peer) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{44} + return fileDescriptor_rpc_854431eb46daab93, []int{44} } func (m *Peer) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_Peer.Unmarshal(m, b) @@ -2989,6 +3023,13 @@ func (m *Peer) GetPingTime() int64 { return 0 } +func (m *Peer) GetSyncType() Peer_SyncType { + if m != nil { + return m.SyncType + } + return Peer_UNKNOWN_SYNC +} + type ListPeersRequest struct { XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` @@ -2999,7 +3040,7 @@ func (m *ListPeersRequest) Reset() { *m = ListPeersRequest{} } func (m *ListPeersRequest) String() string { return proto.CompactTextString(m) } func (*ListPeersRequest) ProtoMessage() {} func (*ListPeersRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{45} + return fileDescriptor_rpc_854431eb46daab93, []int{45} } func (m *ListPeersRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ListPeersRequest.Unmarshal(m, b) @@ -3031,7 +3072,7 @@ func (m *ListPeersResponse) Reset() { *m = ListPeersResponse{} } func (m *ListPeersResponse) String() string { return proto.CompactTextString(m) } func (*ListPeersResponse) ProtoMessage() {} func (*ListPeersResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{46} + return fileDescriptor_rpc_854431eb46daab93, []int{46} } func (m *ListPeersResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ListPeersResponse.Unmarshal(m, b) @@ -3068,7 +3109,7 @@ func (m *GetInfoRequest) Reset() { *m = GetInfoRequest{} } func (m *GetInfoRequest) String() string { return proto.CompactTextString(m) } func (*GetInfoRequest) ProtoMessage() {} func (*GetInfoRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{47} + return fileDescriptor_rpc_854431eb46daab93, []int{47} } func (m *GetInfoRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_GetInfoRequest.Unmarshal(m, b) @@ -3128,7 +3169,7 @@ func (m *GetInfoResponse) Reset() { *m = GetInfoResponse{} } func (m *GetInfoResponse) String() string { return proto.CompactTextString(m) } func (*GetInfoResponse) ProtoMessage() {} func (*GetInfoResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{48} + return fileDescriptor_rpc_854431eb46daab93, []int{48} } func (m *GetInfoResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_GetInfoResponse.Unmarshal(m, b) @@ -3261,7 +3302,7 @@ func (m *Chain) Reset() { *m = Chain{} } func (m *Chain) String() string { return proto.CompactTextString(m) } func (*Chain) ProtoMessage() {} func (*Chain) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{49} + return fileDescriptor_rpc_854431eb46daab93, []int{49} } func (m *Chain) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_Chain.Unmarshal(m, b) @@ -3308,7 +3349,7 @@ func (m *ConfirmationUpdate) Reset() { *m = ConfirmationUpdate{} } func (m *ConfirmationUpdate) String() string { return proto.CompactTextString(m) } func (*ConfirmationUpdate) ProtoMessage() {} func (*ConfirmationUpdate) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{50} + return fileDescriptor_rpc_854431eb46daab93, []int{50} } func (m *ConfirmationUpdate) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ConfirmationUpdate.Unmarshal(m, b) @@ -3360,7 +3401,7 @@ func (m *ChannelOpenUpdate) Reset() { *m = ChannelOpenUpdate{} } func (m *ChannelOpenUpdate) String() string { return proto.CompactTextString(m) } func (*ChannelOpenUpdate) ProtoMessage() {} func (*ChannelOpenUpdate) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{51} + return fileDescriptor_rpc_854431eb46daab93, []int{51} } func (m *ChannelOpenUpdate) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ChannelOpenUpdate.Unmarshal(m, b) @@ -3399,7 +3440,7 @@ func (m *ChannelCloseUpdate) Reset() { *m = ChannelCloseUpdate{} } func (m *ChannelCloseUpdate) String() string { return proto.CompactTextString(m) } func (*ChannelCloseUpdate) ProtoMessage() {} func (*ChannelCloseUpdate) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{52} + return fileDescriptor_rpc_854431eb46daab93, []int{52} } func (m *ChannelCloseUpdate) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ChannelCloseUpdate.Unmarshal(m, b) @@ -3454,7 +3495,7 @@ func (m *CloseChannelRequest) Reset() { *m = CloseChannelRequest{} } func (m *CloseChannelRequest) String() string { return proto.CompactTextString(m) } func (*CloseChannelRequest) ProtoMessage() {} func (*CloseChannelRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{53} + return fileDescriptor_rpc_854431eb46daab93, []int{53} } func (m *CloseChannelRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_CloseChannelRequest.Unmarshal(m, b) @@ -3516,7 +3557,7 @@ func (m *CloseStatusUpdate) Reset() { *m = CloseStatusUpdate{} } func (m *CloseStatusUpdate) String() string { return proto.CompactTextString(m) } func (*CloseStatusUpdate) ProtoMessage() {} func (*CloseStatusUpdate) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{54} + return fileDescriptor_rpc_854431eb46daab93, []int{54} } func (m *CloseStatusUpdate) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_CloseStatusUpdate.Unmarshal(m, b) @@ -3659,7 +3700,7 @@ func (m *PendingUpdate) Reset() { *m = PendingUpdate{} } func (m *PendingUpdate) String() string { return proto.CompactTextString(m) } func (*PendingUpdate) ProtoMessage() {} func (*PendingUpdate) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{55} + return fileDescriptor_rpc_854431eb46daab93, []int{55} } func (m *PendingUpdate) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_PendingUpdate.Unmarshal(m, b) @@ -3725,7 +3766,7 @@ func (m *OpenChannelRequest) Reset() { *m = OpenChannelRequest{} } func (m *OpenChannelRequest) String() string { return proto.CompactTextString(m) } func (*OpenChannelRequest) ProtoMessage() {} func (*OpenChannelRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{56} + return fileDescriptor_rpc_854431eb46daab93, []int{56} } func (m *OpenChannelRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_OpenChannelRequest.Unmarshal(m, b) @@ -3836,7 +3877,7 @@ func (m *OpenStatusUpdate) Reset() { *m = OpenStatusUpdate{} } func (m *OpenStatusUpdate) String() string { return proto.CompactTextString(m) } func (*OpenStatusUpdate) ProtoMessage() {} func (*OpenStatusUpdate) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{57} + return fileDescriptor_rpc_854431eb46daab93, []int{57} } func (m *OpenStatusUpdate) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_OpenStatusUpdate.Unmarshal(m, b) @@ -3992,7 +4033,7 @@ func (m *PendingHTLC) Reset() { *m = PendingHTLC{} } func (m *PendingHTLC) String() string { return proto.CompactTextString(m) } func (*PendingHTLC) ProtoMessage() {} func (*PendingHTLC) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{58} + return fileDescriptor_rpc_854431eb46daab93, []int{58} } func (m *PendingHTLC) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_PendingHTLC.Unmarshal(m, b) @@ -4064,7 +4105,7 @@ func (m *PendingChannelsRequest) Reset() { *m = PendingChannelsRequest{} func (m *PendingChannelsRequest) String() string { return proto.CompactTextString(m) } func (*PendingChannelsRequest) ProtoMessage() {} func (*PendingChannelsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{59} + return fileDescriptor_rpc_854431eb46daab93, []int{59} } func (m *PendingChannelsRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_PendingChannelsRequest.Unmarshal(m, b) @@ -4104,7 +4145,7 @@ func (m *PendingChannelsResponse) Reset() { *m = PendingChannelsResponse func (m *PendingChannelsResponse) String() string { return proto.CompactTextString(m) } func (*PendingChannelsResponse) ProtoMessage() {} func (*PendingChannelsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{60} + return fileDescriptor_rpc_854431eb46daab93, []int{60} } func (m *PendingChannelsResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_PendingChannelsResponse.Unmarshal(m, b) @@ -4176,7 +4217,7 @@ func (m *PendingChannelsResponse_PendingChannel) Reset() { func (m *PendingChannelsResponse_PendingChannel) String() string { return proto.CompactTextString(m) } func (*PendingChannelsResponse_PendingChannel) ProtoMessage() {} func (*PendingChannelsResponse_PendingChannel) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{60, 0} + return fileDescriptor_rpc_854431eb46daab93, []int{60, 0} } func (m *PendingChannelsResponse_PendingChannel) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_PendingChannelsResponse_PendingChannel.Unmarshal(m, b) @@ -4263,7 +4304,7 @@ func (m *PendingChannelsResponse_PendingOpenChannel) String() string { } func (*PendingChannelsResponse_PendingOpenChannel) ProtoMessage() {} func (*PendingChannelsResponse_PendingOpenChannel) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{60, 1} + return fileDescriptor_rpc_854431eb46daab93, []int{60, 1} } func (m *PendingChannelsResponse_PendingOpenChannel) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_PendingChannelsResponse_PendingOpenChannel.Unmarshal(m, b) @@ -4336,7 +4377,7 @@ func (m *PendingChannelsResponse_WaitingCloseChannel) String() string { } func (*PendingChannelsResponse_WaitingCloseChannel) ProtoMessage() {} func (*PendingChannelsResponse_WaitingCloseChannel) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{60, 2} + return fileDescriptor_rpc_854431eb46daab93, []int{60, 2} } func (m *PendingChannelsResponse_WaitingCloseChannel) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_PendingChannelsResponse_WaitingCloseChannel.Unmarshal(m, b) @@ -4384,7 +4425,7 @@ func (m *PendingChannelsResponse_ClosedChannel) Reset() { *m = PendingCh func (m *PendingChannelsResponse_ClosedChannel) String() string { return proto.CompactTextString(m) } func (*PendingChannelsResponse_ClosedChannel) ProtoMessage() {} func (*PendingChannelsResponse_ClosedChannel) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{60, 3} + return fileDescriptor_rpc_854431eb46daab93, []int{60, 3} } func (m *PendingChannelsResponse_ClosedChannel) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_PendingChannelsResponse_ClosedChannel.Unmarshal(m, b) @@ -4448,7 +4489,7 @@ func (m *PendingChannelsResponse_ForceClosedChannel) String() string { } func (*PendingChannelsResponse_ForceClosedChannel) ProtoMessage() {} func (*PendingChannelsResponse_ForceClosedChannel) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{60, 4} + return fileDescriptor_rpc_854431eb46daab93, []int{60, 4} } func (m *PendingChannelsResponse_ForceClosedChannel) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_PendingChannelsResponse_ForceClosedChannel.Unmarshal(m, b) @@ -4527,7 +4568,7 @@ func (m *ChannelEventSubscription) Reset() { *m = ChannelEventSubscripti func (m *ChannelEventSubscription) String() string { return proto.CompactTextString(m) } func (*ChannelEventSubscription) ProtoMessage() {} func (*ChannelEventSubscription) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{61} + return fileDescriptor_rpc_854431eb46daab93, []int{61} } func (m *ChannelEventSubscription) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ChannelEventSubscription.Unmarshal(m, b) @@ -4564,7 +4605,7 @@ func (m *ChannelEventUpdate) Reset() { *m = ChannelEventUpdate{} } func (m *ChannelEventUpdate) String() string { return proto.CompactTextString(m) } func (*ChannelEventUpdate) ProtoMessage() {} func (*ChannelEventUpdate) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{62} + return fileDescriptor_rpc_854431eb46daab93, []int{62} } func (m *ChannelEventUpdate) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ChannelEventUpdate.Unmarshal(m, b) @@ -4776,7 +4817,7 @@ func (m *WalletBalanceRequest) Reset() { *m = WalletBalanceRequest{} } func (m *WalletBalanceRequest) String() string { return proto.CompactTextString(m) } func (*WalletBalanceRequest) ProtoMessage() {} func (*WalletBalanceRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{63} + return fileDescriptor_rpc_854431eb46daab93, []int{63} } func (m *WalletBalanceRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_WalletBalanceRequest.Unmarshal(m, b) @@ -4812,7 +4853,7 @@ func (m *WalletBalanceResponse) Reset() { *m = WalletBalanceResponse{} } func (m *WalletBalanceResponse) String() string { return proto.CompactTextString(m) } func (*WalletBalanceResponse) ProtoMessage() {} func (*WalletBalanceResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{64} + return fileDescriptor_rpc_854431eb46daab93, []int{64} } func (m *WalletBalanceResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_WalletBalanceResponse.Unmarshal(m, b) @@ -4863,7 +4904,7 @@ func (m *ChannelBalanceRequest) Reset() { *m = ChannelBalanceRequest{} } func (m *ChannelBalanceRequest) String() string { return proto.CompactTextString(m) } func (*ChannelBalanceRequest) ProtoMessage() {} func (*ChannelBalanceRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{65} + return fileDescriptor_rpc_854431eb46daab93, []int{65} } func (m *ChannelBalanceRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ChannelBalanceRequest.Unmarshal(m, b) @@ -4897,7 +4938,7 @@ func (m *ChannelBalanceResponse) Reset() { *m = ChannelBalanceResponse{} func (m *ChannelBalanceResponse) String() string { return proto.CompactTextString(m) } func (*ChannelBalanceResponse) ProtoMessage() {} func (*ChannelBalanceResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{66} + return fileDescriptor_rpc_854431eb46daab93, []int{66} } func (m *ChannelBalanceResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ChannelBalanceResponse.Unmarshal(m, b) @@ -4967,7 +5008,7 @@ func (m *QueryRoutesRequest) Reset() { *m = QueryRoutesRequest{} } func (m *QueryRoutesRequest) String() string { return proto.CompactTextString(m) } func (*QueryRoutesRequest) ProtoMessage() {} func (*QueryRoutesRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{67} + return fileDescriptor_rpc_854431eb46daab93, []int{67} } func (m *QueryRoutesRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_QueryRoutesRequest.Unmarshal(m, b) @@ -5062,7 +5103,7 @@ func (m *EdgeLocator) Reset() { *m = EdgeLocator{} } func (m *EdgeLocator) String() string { return proto.CompactTextString(m) } func (*EdgeLocator) ProtoMessage() {} func (*EdgeLocator) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{68} + return fileDescriptor_rpc_854431eb46daab93, []int{68} } func (m *EdgeLocator) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_EdgeLocator.Unmarshal(m, b) @@ -5107,7 +5148,7 @@ func (m *QueryRoutesResponse) Reset() { *m = QueryRoutesResponse{} } func (m *QueryRoutesResponse) String() string { return proto.CompactTextString(m) } func (*QueryRoutesResponse) ProtoMessage() {} func (*QueryRoutesResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{69} + return fileDescriptor_rpc_854431eb46daab93, []int{69} } func (m *QueryRoutesResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_QueryRoutesResponse.Unmarshal(m, b) @@ -5159,7 +5200,7 @@ func (m *Hop) Reset() { *m = Hop{} } func (m *Hop) String() string { return proto.CompactTextString(m) } func (*Hop) ProtoMessage() {} func (*Hop) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{70} + return fileDescriptor_rpc_854431eb46daab93, []int{70} } func (m *Hop) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_Hop.Unmarshal(m, b) @@ -5280,7 +5321,7 @@ func (m *Route) Reset() { *m = Route{} } func (m *Route) String() string { return proto.CompactTextString(m) } func (*Route) ProtoMessage() {} func (*Route) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{71} + return fileDescriptor_rpc_854431eb46daab93, []int{71} } func (m *Route) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_Route.Unmarshal(m, b) @@ -5356,7 +5397,7 @@ func (m *NodeInfoRequest) Reset() { *m = NodeInfoRequest{} } func (m *NodeInfoRequest) String() string { return proto.CompactTextString(m) } func (*NodeInfoRequest) ProtoMessage() {} func (*NodeInfoRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{72} + return fileDescriptor_rpc_854431eb46daab93, []int{72} } func (m *NodeInfoRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_NodeInfoRequest.Unmarshal(m, b) @@ -5401,7 +5442,7 @@ func (m *NodeInfo) Reset() { *m = NodeInfo{} } func (m *NodeInfo) String() string { return proto.CompactTextString(m) } func (*NodeInfo) ProtoMessage() {} func (*NodeInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{73} + return fileDescriptor_rpc_854431eb46daab93, []int{73} } func (m *NodeInfo) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_NodeInfo.Unmarshal(m, b) @@ -5462,7 +5503,7 @@ func (m *LightningNode) Reset() { *m = LightningNode{} } func (m *LightningNode) String() string { return proto.CompactTextString(m) } func (*LightningNode) ProtoMessage() {} func (*LightningNode) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{74} + return fileDescriptor_rpc_854431eb46daab93, []int{74} } func (m *LightningNode) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_LightningNode.Unmarshal(m, b) @@ -5529,7 +5570,7 @@ func (m *NodeAddress) Reset() { *m = NodeAddress{} } func (m *NodeAddress) String() string { return proto.CompactTextString(m) } func (*NodeAddress) ProtoMessage() {} func (*NodeAddress) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{75} + return fileDescriptor_rpc_854431eb46daab93, []int{75} } func (m *NodeAddress) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_NodeAddress.Unmarshal(m, b) @@ -5579,7 +5620,7 @@ func (m *RoutingPolicy) Reset() { *m = RoutingPolicy{} } func (m *RoutingPolicy) String() string { return proto.CompactTextString(m) } func (*RoutingPolicy) ProtoMessage() {} func (*RoutingPolicy) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{76} + return fileDescriptor_rpc_854431eb46daab93, []int{76} } func (m *RoutingPolicy) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_RoutingPolicy.Unmarshal(m, b) @@ -5669,7 +5710,7 @@ func (m *ChannelEdge) Reset() { *m = ChannelEdge{} } func (m *ChannelEdge) String() string { return proto.CompactTextString(m) } func (*ChannelEdge) ProtoMessage() {} func (*ChannelEdge) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{77} + return fileDescriptor_rpc_854431eb46daab93, []int{77} } func (m *ChannelEdge) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ChannelEdge.Unmarshal(m, b) @@ -5760,7 +5801,7 @@ func (m *ChannelGraphRequest) Reset() { *m = ChannelGraphRequest{} } func (m *ChannelGraphRequest) String() string { return proto.CompactTextString(m) } func (*ChannelGraphRequest) ProtoMessage() {} func (*ChannelGraphRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{78} + return fileDescriptor_rpc_854431eb46daab93, []int{78} } func (m *ChannelGraphRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ChannelGraphRequest.Unmarshal(m, b) @@ -5802,7 +5843,7 @@ func (m *ChannelGraph) Reset() { *m = ChannelGraph{} } func (m *ChannelGraph) String() string { return proto.CompactTextString(m) } func (*ChannelGraph) ProtoMessage() {} func (*ChannelGraph) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{79} + return fileDescriptor_rpc_854431eb46daab93, []int{79} } func (m *ChannelGraph) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ChannelGraph.Unmarshal(m, b) @@ -5851,7 +5892,7 @@ func (m *ChanInfoRequest) Reset() { *m = ChanInfoRequest{} } func (m *ChanInfoRequest) String() string { return proto.CompactTextString(m) } func (*ChanInfoRequest) ProtoMessage() {} func (*ChanInfoRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{80} + return fileDescriptor_rpc_854431eb46daab93, []int{80} } func (m *ChanInfoRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ChanInfoRequest.Unmarshal(m, b) @@ -5888,7 +5929,7 @@ func (m *NetworkInfoRequest) Reset() { *m = NetworkInfoRequest{} } func (m *NetworkInfoRequest) String() string { return proto.CompactTextString(m) } func (*NetworkInfoRequest) ProtoMessage() {} func (*NetworkInfoRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{81} + return fileDescriptor_rpc_854431eb46daab93, []int{81} } func (m *NetworkInfoRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_NetworkInfoRequest.Unmarshal(m, b) @@ -5928,7 +5969,7 @@ func (m *NetworkInfo) Reset() { *m = NetworkInfo{} } func (m *NetworkInfo) String() string { return proto.CompactTextString(m) } func (*NetworkInfo) ProtoMessage() {} func (*NetworkInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{82} + return fileDescriptor_rpc_854431eb46daab93, []int{82} } func (m *NetworkInfo) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_NetworkInfo.Unmarshal(m, b) @@ -6028,7 +6069,7 @@ func (m *StopRequest) Reset() { *m = StopRequest{} } func (m *StopRequest) String() string { return proto.CompactTextString(m) } func (*StopRequest) ProtoMessage() {} func (*StopRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{83} + return fileDescriptor_rpc_854431eb46daab93, []int{83} } func (m *StopRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_StopRequest.Unmarshal(m, b) @@ -6058,7 +6099,7 @@ func (m *StopResponse) Reset() { *m = StopResponse{} } func (m *StopResponse) String() string { return proto.CompactTextString(m) } func (*StopResponse) ProtoMessage() {} func (*StopResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{84} + return fileDescriptor_rpc_854431eb46daab93, []int{84} } func (m *StopResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_StopResponse.Unmarshal(m, b) @@ -6088,7 +6129,7 @@ func (m *GraphTopologySubscription) Reset() { *m = GraphTopologySubscrip func (m *GraphTopologySubscription) String() string { return proto.CompactTextString(m) } func (*GraphTopologySubscription) ProtoMessage() {} func (*GraphTopologySubscription) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{85} + return fileDescriptor_rpc_854431eb46daab93, []int{85} } func (m *GraphTopologySubscription) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_GraphTopologySubscription.Unmarshal(m, b) @@ -6121,7 +6162,7 @@ func (m *GraphTopologyUpdate) Reset() { *m = GraphTopologyUpdate{} } func (m *GraphTopologyUpdate) String() string { return proto.CompactTextString(m) } func (*GraphTopologyUpdate) ProtoMessage() {} func (*GraphTopologyUpdate) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{86} + return fileDescriptor_rpc_854431eb46daab93, []int{86} } func (m *GraphTopologyUpdate) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_GraphTopologyUpdate.Unmarshal(m, b) @@ -6176,7 +6217,7 @@ func (m *NodeUpdate) Reset() { *m = NodeUpdate{} } func (m *NodeUpdate) String() string { return proto.CompactTextString(m) } func (*NodeUpdate) ProtoMessage() {} func (*NodeUpdate) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{87} + return fileDescriptor_rpc_854431eb46daab93, []int{87} } func (m *NodeUpdate) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_NodeUpdate.Unmarshal(m, b) @@ -6244,7 +6285,7 @@ func (m *ChannelEdgeUpdate) Reset() { *m = ChannelEdgeUpdate{} } func (m *ChannelEdgeUpdate) String() string { return proto.CompactTextString(m) } func (*ChannelEdgeUpdate) ProtoMessage() {} func (*ChannelEdgeUpdate) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{88} + return fileDescriptor_rpc_854431eb46daab93, []int{88} } func (m *ChannelEdgeUpdate) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ChannelEdgeUpdate.Unmarshal(m, b) @@ -6324,7 +6365,7 @@ func (m *ClosedChannelUpdate) Reset() { *m = ClosedChannelUpdate{} } func (m *ClosedChannelUpdate) String() string { return proto.CompactTextString(m) } func (*ClosedChannelUpdate) ProtoMessage() {} func (*ClosedChannelUpdate) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{89} + return fileDescriptor_rpc_854431eb46daab93, []int{89} } func (m *ClosedChannelUpdate) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ClosedChannelUpdate.Unmarshal(m, b) @@ -6394,7 +6435,7 @@ func (m *HopHint) Reset() { *m = HopHint{} } func (m *HopHint) String() string { return proto.CompactTextString(m) } func (*HopHint) ProtoMessage() {} func (*HopHint) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{90} + return fileDescriptor_rpc_854431eb46daab93, []int{90} } func (m *HopHint) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_HopHint.Unmarshal(m, b) @@ -6463,7 +6504,7 @@ func (m *RouteHint) Reset() { *m = RouteHint{} } func (m *RouteHint) String() string { return proto.CompactTextString(m) } func (*RouteHint) ProtoMessage() {} func (*RouteHint) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{91} + return fileDescriptor_rpc_854431eb46daab93, []int{91} } func (m *RouteHint) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_RouteHint.Unmarshal(m, b) @@ -6578,7 +6619,7 @@ func (m *Invoice) Reset() { *m = Invoice{} } func (m *Invoice) String() string { return proto.CompactTextString(m) } func (*Invoice) ProtoMessage() {} func (*Invoice) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{92} + return fileDescriptor_rpc_854431eb46daab93, []int{92} } func (m *Invoice) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_Invoice.Unmarshal(m, b) @@ -6770,7 +6811,7 @@ func (m *AddInvoiceResponse) Reset() { *m = AddInvoiceResponse{} } func (m *AddInvoiceResponse) String() string { return proto.CompactTextString(m) } func (*AddInvoiceResponse) ProtoMessage() {} func (*AddInvoiceResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{93} + return fileDescriptor_rpc_854431eb46daab93, []int{93} } func (m *AddInvoiceResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_AddInvoiceResponse.Unmarshal(m, b) @@ -6827,7 +6868,7 @@ func (m *PaymentHash) Reset() { *m = PaymentHash{} } func (m *PaymentHash) String() string { return proto.CompactTextString(m) } func (*PaymentHash) ProtoMessage() {} func (*PaymentHash) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{94} + return fileDescriptor_rpc_854431eb46daab93, []int{94} } func (m *PaymentHash) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_PaymentHash.Unmarshal(m, b) @@ -6883,7 +6924,7 @@ func (m *ListInvoiceRequest) Reset() { *m = ListInvoiceRequest{} } func (m *ListInvoiceRequest) String() string { return proto.CompactTextString(m) } func (*ListInvoiceRequest) ProtoMessage() {} func (*ListInvoiceRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{95} + return fileDescriptor_rpc_854431eb46daab93, []int{95} } func (m *ListInvoiceRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ListInvoiceRequest.Unmarshal(m, b) @@ -6953,7 +6994,7 @@ func (m *ListInvoiceResponse) Reset() { *m = ListInvoiceResponse{} } func (m *ListInvoiceResponse) String() string { return proto.CompactTextString(m) } func (*ListInvoiceResponse) ProtoMessage() {} func (*ListInvoiceResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{96} + return fileDescriptor_rpc_854431eb46daab93, []int{96} } func (m *ListInvoiceResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ListInvoiceResponse.Unmarshal(m, b) @@ -7016,7 +7057,7 @@ func (m *InvoiceSubscription) Reset() { *m = InvoiceSubscription{} } func (m *InvoiceSubscription) String() string { return proto.CompactTextString(m) } func (*InvoiceSubscription) ProtoMessage() {} func (*InvoiceSubscription) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{97} + return fileDescriptor_rpc_854431eb46daab93, []int{97} } func (m *InvoiceSubscription) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_InvoiceSubscription.Unmarshal(m, b) @@ -7076,7 +7117,7 @@ func (m *Payment) Reset() { *m = Payment{} } func (m *Payment) String() string { return proto.CompactTextString(m) } func (*Payment) ProtoMessage() {} func (*Payment) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{98} + return fileDescriptor_rpc_854431eb46daab93, []int{98} } func (m *Payment) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_Payment.Unmarshal(m, b) @@ -7163,7 +7204,7 @@ func (m *ListPaymentsRequest) Reset() { *m = ListPaymentsRequest{} } func (m *ListPaymentsRequest) String() string { return proto.CompactTextString(m) } func (*ListPaymentsRequest) ProtoMessage() {} func (*ListPaymentsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{99} + return fileDescriptor_rpc_854431eb46daab93, []int{99} } func (m *ListPaymentsRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ListPaymentsRequest.Unmarshal(m, b) @@ -7195,7 +7236,7 @@ func (m *ListPaymentsResponse) Reset() { *m = ListPaymentsResponse{} } func (m *ListPaymentsResponse) String() string { return proto.CompactTextString(m) } func (*ListPaymentsResponse) ProtoMessage() {} func (*ListPaymentsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{100} + return fileDescriptor_rpc_854431eb46daab93, []int{100} } func (m *ListPaymentsResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ListPaymentsResponse.Unmarshal(m, b) @@ -7232,7 +7273,7 @@ func (m *DeleteAllPaymentsRequest) Reset() { *m = DeleteAllPaymentsReque func (m *DeleteAllPaymentsRequest) String() string { return proto.CompactTextString(m) } func (*DeleteAllPaymentsRequest) ProtoMessage() {} func (*DeleteAllPaymentsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{101} + return fileDescriptor_rpc_854431eb46daab93, []int{101} } func (m *DeleteAllPaymentsRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_DeleteAllPaymentsRequest.Unmarshal(m, b) @@ -7262,7 +7303,7 @@ func (m *DeleteAllPaymentsResponse) Reset() { *m = DeleteAllPaymentsResp func (m *DeleteAllPaymentsResponse) String() string { return proto.CompactTextString(m) } func (*DeleteAllPaymentsResponse) ProtoMessage() {} func (*DeleteAllPaymentsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{102} + return fileDescriptor_rpc_854431eb46daab93, []int{102} } func (m *DeleteAllPaymentsResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_DeleteAllPaymentsResponse.Unmarshal(m, b) @@ -7293,7 +7334,7 @@ func (m *AbandonChannelRequest) Reset() { *m = AbandonChannelRequest{} } func (m *AbandonChannelRequest) String() string { return proto.CompactTextString(m) } func (*AbandonChannelRequest) ProtoMessage() {} func (*AbandonChannelRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{103} + return fileDescriptor_rpc_854431eb46daab93, []int{103} } func (m *AbandonChannelRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_AbandonChannelRequest.Unmarshal(m, b) @@ -7330,7 +7371,7 @@ func (m *AbandonChannelResponse) Reset() { *m = AbandonChannelResponse{} func (m *AbandonChannelResponse) String() string { return proto.CompactTextString(m) } func (*AbandonChannelResponse) ProtoMessage() {} func (*AbandonChannelResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{104} + return fileDescriptor_rpc_854431eb46daab93, []int{104} } func (m *AbandonChannelResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_AbandonChannelResponse.Unmarshal(m, b) @@ -7362,7 +7403,7 @@ func (m *DebugLevelRequest) Reset() { *m = DebugLevelRequest{} } func (m *DebugLevelRequest) String() string { return proto.CompactTextString(m) } func (*DebugLevelRequest) ProtoMessage() {} func (*DebugLevelRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{105} + return fileDescriptor_rpc_854431eb46daab93, []int{105} } func (m *DebugLevelRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_DebugLevelRequest.Unmarshal(m, b) @@ -7407,7 +7448,7 @@ func (m *DebugLevelResponse) Reset() { *m = DebugLevelResponse{} } func (m *DebugLevelResponse) String() string { return proto.CompactTextString(m) } func (*DebugLevelResponse) ProtoMessage() {} func (*DebugLevelResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{106} + return fileDescriptor_rpc_854431eb46daab93, []int{106} } func (m *DebugLevelResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_DebugLevelResponse.Unmarshal(m, b) @@ -7446,7 +7487,7 @@ func (m *PayReqString) Reset() { *m = PayReqString{} } func (m *PayReqString) String() string { return proto.CompactTextString(m) } func (*PayReqString) ProtoMessage() {} func (*PayReqString) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{107} + return fileDescriptor_rpc_854431eb46daab93, []int{107} } func (m *PayReqString) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_PayReqString.Unmarshal(m, b) @@ -7493,7 +7534,7 @@ func (m *PayReq) Reset() { *m = PayReq{} } func (m *PayReq) String() string { return proto.CompactTextString(m) } func (*PayReq) ProtoMessage() {} func (*PayReq) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{108} + return fileDescriptor_rpc_854431eb46daab93, []int{108} } func (m *PayReq) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_PayReq.Unmarshal(m, b) @@ -7593,7 +7634,7 @@ func (m *FeeReportRequest) Reset() { *m = FeeReportRequest{} } func (m *FeeReportRequest) String() string { return proto.CompactTextString(m) } func (*FeeReportRequest) ProtoMessage() {} func (*FeeReportRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{109} + return fileDescriptor_rpc_854431eb46daab93, []int{109} } func (m *FeeReportRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_FeeReportRequest.Unmarshal(m, b) @@ -7631,7 +7672,7 @@ func (m *ChannelFeeReport) Reset() { *m = ChannelFeeReport{} } func (m *ChannelFeeReport) String() string { return proto.CompactTextString(m) } func (*ChannelFeeReport) ProtoMessage() {} func (*ChannelFeeReport) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{110} + return fileDescriptor_rpc_854431eb46daab93, []int{110} } func (m *ChannelFeeReport) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ChannelFeeReport.Unmarshal(m, b) @@ -7697,7 +7738,7 @@ func (m *FeeReportResponse) Reset() { *m = FeeReportResponse{} } func (m *FeeReportResponse) String() string { return proto.CompactTextString(m) } func (*FeeReportResponse) ProtoMessage() {} func (*FeeReportResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{111} + return fileDescriptor_rpc_854431eb46daab93, []int{111} } func (m *FeeReportResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_FeeReportResponse.Unmarshal(m, b) @@ -7765,7 +7806,7 @@ func (m *PolicyUpdateRequest) Reset() { *m = PolicyUpdateRequest{} } func (m *PolicyUpdateRequest) String() string { return proto.CompactTextString(m) } func (*PolicyUpdateRequest) ProtoMessage() {} func (*PolicyUpdateRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{112} + return fileDescriptor_rpc_854431eb46daab93, []int{112} } func (m *PolicyUpdateRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_PolicyUpdateRequest.Unmarshal(m, b) @@ -7926,7 +7967,7 @@ func (m *PolicyUpdateResponse) Reset() { *m = PolicyUpdateResponse{} } func (m *PolicyUpdateResponse) String() string { return proto.CompactTextString(m) } func (*PolicyUpdateResponse) ProtoMessage() {} func (*PolicyUpdateResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{113} + return fileDescriptor_rpc_854431eb46daab93, []int{113} } func (m *PolicyUpdateResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_PolicyUpdateResponse.Unmarshal(m, b) @@ -7964,7 +8005,7 @@ func (m *ForwardingHistoryRequest) Reset() { *m = ForwardingHistoryReque func (m *ForwardingHistoryRequest) String() string { return proto.CompactTextString(m) } func (*ForwardingHistoryRequest) ProtoMessage() {} func (*ForwardingHistoryRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{114} + return fileDescriptor_rpc_854431eb46daab93, []int{114} } func (m *ForwardingHistoryRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ForwardingHistoryRequest.Unmarshal(m, b) @@ -8036,7 +8077,7 @@ func (m *ForwardingEvent) Reset() { *m = ForwardingEvent{} } func (m *ForwardingEvent) String() string { return proto.CompactTextString(m) } func (*ForwardingEvent) ProtoMessage() {} func (*ForwardingEvent) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{115} + return fileDescriptor_rpc_854431eb46daab93, []int{115} } func (m *ForwardingEvent) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ForwardingEvent.Unmarshal(m, b) @@ -8119,7 +8160,7 @@ func (m *ForwardingHistoryResponse) Reset() { *m = ForwardingHistoryResp func (m *ForwardingHistoryResponse) String() string { return proto.CompactTextString(m) } func (*ForwardingHistoryResponse) ProtoMessage() {} func (*ForwardingHistoryResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{116} + return fileDescriptor_rpc_854431eb46daab93, []int{116} } func (m *ForwardingHistoryResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ForwardingHistoryResponse.Unmarshal(m, b) @@ -8165,7 +8206,7 @@ func (m *ExportChannelBackupRequest) Reset() { *m = ExportChannelBackupR func (m *ExportChannelBackupRequest) String() string { return proto.CompactTextString(m) } func (*ExportChannelBackupRequest) ProtoMessage() {} func (*ExportChannelBackupRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{117} + return fileDescriptor_rpc_854431eb46daab93, []int{117} } func (m *ExportChannelBackupRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ExportChannelBackupRequest.Unmarshal(m, b) @@ -8210,7 +8251,7 @@ func (m *ChannelBackup) Reset() { *m = ChannelBackup{} } func (m *ChannelBackup) String() string { return proto.CompactTextString(m) } func (*ChannelBackup) ProtoMessage() {} func (*ChannelBackup) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{118} + return fileDescriptor_rpc_854431eb46daab93, []int{118} } func (m *ChannelBackup) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ChannelBackup.Unmarshal(m, b) @@ -8262,7 +8303,7 @@ func (m *MultiChanBackup) Reset() { *m = MultiChanBackup{} } func (m *MultiChanBackup) String() string { return proto.CompactTextString(m) } func (*MultiChanBackup) ProtoMessage() {} func (*MultiChanBackup) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{119} + return fileDescriptor_rpc_854431eb46daab93, []int{119} } func (m *MultiChanBackup) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_MultiChanBackup.Unmarshal(m, b) @@ -8306,7 +8347,7 @@ func (m *ChanBackupExportRequest) Reset() { *m = ChanBackupExportRequest func (m *ChanBackupExportRequest) String() string { return proto.CompactTextString(m) } func (*ChanBackupExportRequest) ProtoMessage() {} func (*ChanBackupExportRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{120} + return fileDescriptor_rpc_854431eb46daab93, []int{120} } func (m *ChanBackupExportRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ChanBackupExportRequest.Unmarshal(m, b) @@ -8344,7 +8385,7 @@ func (m *ChanBackupSnapshot) Reset() { *m = ChanBackupSnapshot{} } func (m *ChanBackupSnapshot) String() string { return proto.CompactTextString(m) } func (*ChanBackupSnapshot) ProtoMessage() {} func (*ChanBackupSnapshot) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{121} + return fileDescriptor_rpc_854431eb46daab93, []int{121} } func (m *ChanBackupSnapshot) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ChanBackupSnapshot.Unmarshal(m, b) @@ -8391,7 +8432,7 @@ func (m *ChannelBackups) Reset() { *m = ChannelBackups{} } func (m *ChannelBackups) String() string { return proto.CompactTextString(m) } func (*ChannelBackups) ProtoMessage() {} func (*ChannelBackups) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{122} + return fileDescriptor_rpc_854431eb46daab93, []int{122} } func (m *ChannelBackups) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ChannelBackups.Unmarshal(m, b) @@ -8432,7 +8473,7 @@ func (m *RestoreChanBackupRequest) Reset() { *m = RestoreChanBackupReque func (m *RestoreChanBackupRequest) String() string { return proto.CompactTextString(m) } func (*RestoreChanBackupRequest) ProtoMessage() {} func (*RestoreChanBackupRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{123} + return fileDescriptor_rpc_854431eb46daab93, []int{123} } func (m *RestoreChanBackupRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_RestoreChanBackupRequest.Unmarshal(m, b) @@ -8569,7 +8610,7 @@ func (m *RestoreBackupResponse) Reset() { *m = RestoreBackupResponse{} } func (m *RestoreBackupResponse) String() string { return proto.CompactTextString(m) } func (*RestoreBackupResponse) ProtoMessage() {} func (*RestoreBackupResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{124} + return fileDescriptor_rpc_854431eb46daab93, []int{124} } func (m *RestoreBackupResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_RestoreBackupResponse.Unmarshal(m, b) @@ -8599,7 +8640,7 @@ func (m *ChannelBackupSubscription) Reset() { *m = ChannelBackupSubscrip func (m *ChannelBackupSubscription) String() string { return proto.CompactTextString(m) } func (*ChannelBackupSubscription) ProtoMessage() {} func (*ChannelBackupSubscription) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{125} + return fileDescriptor_rpc_854431eb46daab93, []int{125} } func (m *ChannelBackupSubscription) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ChannelBackupSubscription.Unmarshal(m, b) @@ -8631,7 +8672,7 @@ func (m *VerifyChanBackupResponse) Reset() { *m = VerifyChanBackupRespon func (m *VerifyChanBackupResponse) String() string { return proto.CompactTextString(m) } func (*VerifyChanBackupResponse) ProtoMessage() {} func (*VerifyChanBackupResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_rpc_e15c66dc1b25da1b, []int{126} + return fileDescriptor_rpc_854431eb46daab93, []int{126} } func (m *VerifyChanBackupResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_VerifyChanBackupResponse.Unmarshal(m, b) @@ -8802,6 +8843,7 @@ func init() { proto.RegisterType((*VerifyChanBackupResponse)(nil), "lnrpc.VerifyChanBackupResponse") proto.RegisterEnum("lnrpc.AddressType", AddressType_name, AddressType_value) proto.RegisterEnum("lnrpc.ChannelCloseSummary_ClosureType", ChannelCloseSummary_ClosureType_name, ChannelCloseSummary_ClosureType_value) + proto.RegisterEnum("lnrpc.Peer_SyncType", Peer_SyncType_name, Peer_SyncType_value) proto.RegisterEnum("lnrpc.ChannelEventUpdate_UpdateType", ChannelEventUpdate_UpdateType_name, ChannelEventUpdate_UpdateType_value) proto.RegisterEnum("lnrpc.Invoice_InvoiceState", Invoice_InvoiceState_name, Invoice_InvoiceState_value) } @@ -11438,489 +11480,493 @@ var _Lightning_serviceDesc = grpc.ServiceDesc{ Metadata: "rpc.proto", } -func init() { proto.RegisterFile("rpc.proto", fileDescriptor_rpc_e15c66dc1b25da1b) } +func init() { proto.RegisterFile("rpc.proto", fileDescriptor_rpc_854431eb46daab93) } -var fileDescriptor_rpc_e15c66dc1b25da1b = []byte{ - // 7696 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x7c, 0x5d, 0x6c, 0x1c, 0xc9, - 0x79, 0xa0, 0x7a, 0x7e, 0xc8, 0x99, 0x6f, 0x86, 0xc3, 0x61, 0xf1, 0x6f, 0x34, 0xd2, 0x6a, 0xb9, - 0x6d, 0x59, 0xa2, 0xb9, 0x7b, 0xa2, 0x56, 0xb6, 0xd7, 0xf2, 0xea, 0x7c, 0x77, 0xfc, 0x93, 0x28, - 0x9b, 0x4b, 0xd1, 0x4d, 0xc9, 0x3a, 0xaf, 0x7d, 0x18, 0x37, 0x67, 0x8a, 0xc3, 0x5e, 0xcd, 0x74, - 0xcf, 0x76, 0xf7, 0x90, 0xa2, 0xf7, 0x74, 0x38, 0x1c, 0x0e, 0x49, 0x10, 0x24, 0x08, 0x9c, 0x20, - 0x41, 0x1c, 0x24, 0x08, 0x60, 0x07, 0x48, 0x8c, 0x3c, 0xe5, 0xc1, 0x41, 0x80, 0xc4, 0x79, 0x0d, - 0x60, 0x20, 0x08, 0x02, 0x3f, 0x06, 0x08, 0x10, 0x24, 0x2f, 0x49, 0x1e, 0x82, 0x04, 0xc8, 0x63, - 0x80, 0xa0, 0xbe, 0xfa, 0xe9, 0xaa, 0xee, 0x1e, 0x51, 0x6b, 0x3b, 0x79, 0x9a, 0xa9, 0xaf, 0xbe, - 0xae, 0xdf, 0xef, 0xbf, 0xbe, 0x2a, 0xa8, 0x86, 0xa3, 0xee, 0xad, 0x51, 0x18, 0xc4, 0x01, 0x29, - 0x0f, 0xfc, 0x70, 0xd4, 0x6d, 0x5f, 0xed, 0x07, 0x41, 0x7f, 0x40, 0xd7, 0xdd, 0x91, 0xb7, 0xee, - 0xfa, 0x7e, 0x10, 0xbb, 0xb1, 0x17, 0xf8, 0x11, 0x47, 0xb2, 0xbf, 0x01, 0x8d, 0x07, 0xd4, 0x3f, - 0xa4, 0xb4, 0xe7, 0xd0, 0x0f, 0xc7, 0x34, 0x8a, 0xc9, 0x9b, 0x30, 0xe7, 0xd2, 0x6f, 0x52, 0xda, - 0xeb, 0x8c, 0xdc, 0x28, 0x1a, 0x9d, 0x84, 0x6e, 0x44, 0x5b, 0xd6, 0x8a, 0xb5, 0x5a, 0x77, 0x9a, - 0xbc, 0xe2, 0x40, 0xc1, 0xc9, 0x1b, 0x50, 0x8f, 0x18, 0x2a, 0xf5, 0xe3, 0x30, 0x18, 0x9d, 0xb7, - 0x0a, 0x88, 0x57, 0x63, 0xb0, 0x1d, 0x0e, 0xb2, 0x07, 0x30, 0xab, 0x7a, 0x88, 0x46, 0x81, 0x1f, - 0x51, 0x72, 0x1b, 0x16, 0xba, 0xde, 0xe8, 0x84, 0x86, 0x1d, 0xfc, 0x78, 0xe8, 0xd3, 0x61, 0xe0, - 0x7b, 0xdd, 0x96, 0xb5, 0x52, 0x5c, 0xad, 0x3a, 0x84, 0xd7, 0xb1, 0x2f, 0xde, 0x13, 0x35, 0xe4, - 0x26, 0xcc, 0x52, 0x9f, 0xc3, 0x69, 0x0f, 0xbf, 0x12, 0x5d, 0x35, 0x12, 0x30, 0xfb, 0xc0, 0xfe, - 0xb9, 0x02, 0xcc, 0x3d, 0xf4, 0xbd, 0xf8, 0xa9, 0x3b, 0x18, 0xd0, 0x58, 0xce, 0xe9, 0x26, 0xcc, - 0x9e, 0x21, 0x00, 0xe7, 0x74, 0x16, 0x84, 0x3d, 0x31, 0xa3, 0x06, 0x07, 0x1f, 0x08, 0xe8, 0xc4, - 0x91, 0x15, 0x26, 0x8e, 0x2c, 0x77, 0xb9, 0x8a, 0x13, 0x96, 0xeb, 0x26, 0xcc, 0x86, 0xb4, 0x1b, - 0x9c, 0xd2, 0xf0, 0xbc, 0x73, 0xe6, 0xf9, 0xbd, 0xe0, 0xac, 0x55, 0x5a, 0xb1, 0x56, 0xcb, 0x4e, - 0x43, 0x82, 0x9f, 0x22, 0x94, 0x6c, 0xc2, 0x6c, 0xf7, 0xc4, 0xf5, 0x7d, 0x3a, 0xe8, 0x1c, 0xb9, - 0xdd, 0x67, 0xe3, 0x51, 0xd4, 0x2a, 0xaf, 0x58, 0xab, 0xb5, 0x3b, 0x97, 0x6f, 0xe1, 0xae, 0xde, - 0xda, 0x3a, 0x71, 0xfd, 0x4d, 0xac, 0x39, 0xf4, 0xdd, 0x51, 0x74, 0x12, 0xc4, 0x4e, 0x43, 0x7c, - 0xc1, 0xc1, 0x91, 0xbd, 0x00, 0x44, 0x5f, 0x09, 0xbe, 0xf6, 0xf6, 0xef, 0x5b, 0x30, 0xff, 0xc4, - 0x1f, 0x04, 0xdd, 0x67, 0x3f, 0xe6, 0x12, 0xe5, 0xcc, 0xa1, 0xf0, 0xaa, 0x73, 0x28, 0x7e, 0xdc, - 0x39, 0x2c, 0xc1, 0x82, 0x39, 0x58, 0x31, 0x0b, 0x0a, 0x8b, 0xec, 0xeb, 0x3e, 0x95, 0xc3, 0x92, - 0xd3, 0xf8, 0x14, 0x34, 0xbb, 0xe3, 0x30, 0xa4, 0x7e, 0x66, 0x1e, 0xb3, 0x02, 0xae, 0x26, 0xf2, - 0x06, 0xd4, 0x7d, 0x7a, 0x96, 0xa0, 0x09, 0xda, 0xf5, 0xe9, 0x99, 0x44, 0xb1, 0x5b, 0xb0, 0x94, - 0xee, 0x46, 0x0c, 0xe0, 0x6f, 0x2c, 0x28, 0x3d, 0x89, 0x9f, 0x07, 0xe4, 0x16, 0x94, 0xe2, 0xf3, - 0x11, 0xe7, 0x90, 0xc6, 0x1d, 0x22, 0xa6, 0xb6, 0xd1, 0xeb, 0x85, 0x34, 0x8a, 0x1e, 0x9f, 0x8f, - 0xa8, 0x53, 0x77, 0x79, 0xa1, 0xc3, 0xf0, 0x48, 0x0b, 0xa6, 0x45, 0x19, 0x3b, 0xac, 0x3a, 0xb2, - 0x48, 0xae, 0x01, 0xb8, 0xc3, 0x60, 0xec, 0xc7, 0x9d, 0xc8, 0x8d, 0x71, 0xa9, 0x8a, 0x8e, 0x06, - 0x21, 0x57, 0xa1, 0x3a, 0x7a, 0xd6, 0x89, 0xba, 0xa1, 0x37, 0x8a, 0x91, 0x6c, 0xaa, 0x4e, 0x02, - 0x20, 0x6f, 0x42, 0x25, 0x18, 0xc7, 0xa3, 0xc0, 0xf3, 0x63, 0x41, 0x2a, 0xb3, 0x62, 0x2c, 0x8f, - 0xc6, 0xf1, 0x01, 0x03, 0x3b, 0x0a, 0x81, 0x5c, 0x87, 0x99, 0x6e, 0xe0, 0x1f, 0x7b, 0xe1, 0x90, - 0x0b, 0x83, 0xd6, 0x14, 0xf6, 0x66, 0x02, 0xed, 0x6f, 0x17, 0xa0, 0xf6, 0x38, 0x74, 0xfd, 0xc8, - 0xed, 0x32, 0x00, 0x1b, 0x7a, 0xfc, 0xbc, 0x73, 0xe2, 0x46, 0x27, 0x38, 0xdb, 0xaa, 0x23, 0x8b, - 0x64, 0x09, 0xa6, 0xf8, 0x40, 0x71, 0x4e, 0x45, 0x47, 0x94, 0xc8, 0x5b, 0x30, 0xe7, 0x8f, 0x87, - 0x1d, 0xb3, 0xaf, 0x22, 0x52, 0x4b, 0xb6, 0x82, 0x2d, 0xc0, 0x11, 0xdb, 0x6b, 0xde, 0x05, 0x9f, - 0xa1, 0x06, 0x21, 0x36, 0xd4, 0x45, 0x89, 0x7a, 0xfd, 0x13, 0x3e, 0xcd, 0xb2, 0x63, 0xc0, 0x58, - 0x1b, 0xb1, 0x37, 0xa4, 0x9d, 0x28, 0x76, 0x87, 0x23, 0x31, 0x2d, 0x0d, 0x82, 0xf5, 0x41, 0xec, - 0x0e, 0x3a, 0xc7, 0x94, 0x46, 0xad, 0x69, 0x51, 0xaf, 0x20, 0xe4, 0x06, 0x34, 0x7a, 0x34, 0x8a, - 0x3b, 0x62, 0x53, 0x68, 0xd4, 0xaa, 0x20, 0xeb, 0xa7, 0xa0, 0x8c, 0x32, 0x1e, 0xd0, 0x58, 0x5b, - 0x9d, 0x48, 0x50, 0xa0, 0xbd, 0x07, 0x44, 0x03, 0x6f, 0xd3, 0xd8, 0xf5, 0x06, 0x11, 0x79, 0x07, - 0xea, 0xb1, 0x86, 0x8c, 0xa2, 0xae, 0xa6, 0xc8, 0x45, 0xfb, 0xc0, 0x31, 0xf0, 0xec, 0x07, 0x50, - 0xb9, 0x4f, 0xe9, 0x9e, 0x37, 0xf4, 0x62, 0xb2, 0x04, 0xe5, 0x63, 0xef, 0x39, 0xe5, 0x04, 0x5d, - 0xdc, 0xbd, 0xe4, 0xf0, 0x22, 0x69, 0xc3, 0xf4, 0x88, 0x86, 0x5d, 0x2a, 0x97, 0x7f, 0xf7, 0x92, - 0x23, 0x01, 0x9b, 0xd3, 0x50, 0x1e, 0xb0, 0x8f, 0xed, 0x7f, 0x2e, 0x40, 0xed, 0x90, 0xfa, 0x8a, - 0x51, 0x08, 0x94, 0xd8, 0x94, 0x04, 0x73, 0xe0, 0x7f, 0xf2, 0x3a, 0xd4, 0x70, 0x9a, 0x51, 0x1c, - 0x7a, 0x7e, 0x5f, 0xd0, 0x27, 0x30, 0xd0, 0x21, 0x42, 0x48, 0x13, 0x8a, 0xee, 0x50, 0xd2, 0x26, - 0xfb, 0xcb, 0x98, 0x68, 0xe4, 0x9e, 0x0f, 0x19, 0xbf, 0xa9, 0x5d, 0xab, 0x3b, 0x35, 0x01, 0xdb, - 0x65, 0xdb, 0x76, 0x0b, 0xe6, 0x75, 0x14, 0xd9, 0x7a, 0x19, 0x5b, 0x9f, 0xd3, 0x30, 0x45, 0x27, - 0x37, 0x61, 0x56, 0xe2, 0x87, 0x7c, 0xb0, 0xb8, 0x8f, 0x55, 0xa7, 0x21, 0xc0, 0x72, 0x0a, 0xab, - 0xd0, 0x3c, 0xf6, 0x7c, 0x77, 0xd0, 0xe9, 0x0e, 0xe2, 0xd3, 0x4e, 0x8f, 0x0e, 0x62, 0x17, 0x77, - 0xb4, 0xec, 0x34, 0x10, 0xbe, 0x35, 0x88, 0x4f, 0xb7, 0x19, 0x94, 0xbc, 0x05, 0xd5, 0x63, 0x4a, - 0x3b, 0xb8, 0x12, 0xad, 0x8a, 0xc1, 0x1d, 0x72, 0x75, 0x9d, 0xca, 0xb1, 0x5c, 0xe7, 0x55, 0x68, - 0x06, 0xe3, 0xb8, 0x1f, 0x78, 0x7e, 0xbf, 0xc3, 0xe4, 0x51, 0xc7, 0xeb, 0xb5, 0xaa, 0x2b, 0xd6, - 0x6a, 0xc9, 0x69, 0x48, 0x38, 0x93, 0x0a, 0x0f, 0x7b, 0xe4, 0x35, 0x00, 0xec, 0x9b, 0x37, 0x0c, - 0x2b, 0xd6, 0xea, 0x8c, 0x53, 0x65, 0x10, 0x6c, 0xc8, 0xfe, 0x23, 0x0b, 0xea, 0x7c, 0xcd, 0x85, - 0xe2, 0xbb, 0x0e, 0x33, 0x72, 0x6a, 0x34, 0x0c, 0x83, 0x50, 0xf0, 0x91, 0x09, 0x24, 0x6b, 0xd0, - 0x94, 0x80, 0x51, 0x48, 0xbd, 0xa1, 0xdb, 0xa7, 0x42, 0x38, 0x65, 0xe0, 0xe4, 0x4e, 0xd2, 0x62, - 0x18, 0x8c, 0x63, 0x2a, 0x44, 0x6c, 0x5d, 0xcc, 0xce, 0x61, 0x30, 0xc7, 0x44, 0x61, 0x7c, 0x94, - 0xb3, 0x67, 0x06, 0xcc, 0xfe, 0xbe, 0x05, 0x84, 0x0d, 0xfd, 0x71, 0xc0, 0x9b, 0x10, 0x4b, 0x9e, - 0xde, 0x6e, 0xeb, 0x95, 0xb7, 0xbb, 0x30, 0x69, 0xbb, 0x57, 0x61, 0x0a, 0x87, 0xc5, 0x04, 0x43, - 0x31, 0x3d, 0xf4, 0xcd, 0x42, 0xcb, 0x72, 0x44, 0x3d, 0xb1, 0xa1, 0xcc, 0xe7, 0x58, 0xca, 0x99, - 0x23, 0xaf, 0xb2, 0xbf, 0x63, 0x41, 0x7d, 0x8b, 0xeb, 0x10, 0x14, 0x7a, 0xe4, 0x36, 0x90, 0xe3, - 0xb1, 0xdf, 0x63, 0x7b, 0x19, 0x3f, 0xf7, 0x7a, 0x9d, 0xa3, 0x73, 0xd6, 0x15, 0x8e, 0x7b, 0xf7, - 0x92, 0x93, 0x53, 0x47, 0xde, 0x82, 0xa6, 0x01, 0x8d, 0xe2, 0x90, 0x8f, 0x7e, 0xf7, 0x92, 0x93, - 0xa9, 0x61, 0x8b, 0xc9, 0xc4, 0xea, 0x38, 0xee, 0x78, 0x7e, 0x8f, 0x3e, 0xc7, 0xf5, 0x9f, 0x71, - 0x0c, 0xd8, 0x66, 0x03, 0xea, 0xfa, 0x77, 0xf6, 0x07, 0x50, 0x91, 0x42, 0x19, 0x05, 0x52, 0x6a, - 0x5c, 0x8e, 0x06, 0x21, 0x6d, 0xa8, 0x98, 0xa3, 0x70, 0x2a, 0x1f, 0xa7, 0x6f, 0xfb, 0xbf, 0x41, - 0x73, 0x8f, 0x49, 0x46, 0xdf, 0xf3, 0xfb, 0x42, 0x2b, 0x31, 0x71, 0x3d, 0x1a, 0x1f, 0x3d, 0xa3, - 0xe7, 0x82, 0xfe, 0x44, 0x89, 0xc9, 0x84, 0x93, 0x20, 0x8a, 0x45, 0x3f, 0xf8, 0xdf, 0xfe, 0x33, - 0x0b, 0xc8, 0x4e, 0x14, 0x7b, 0x43, 0x37, 0xa6, 0xf7, 0xa9, 0x22, 0x84, 0x47, 0x50, 0x67, 0xad, - 0x3d, 0x0e, 0x36, 0xb8, 0xdc, 0xe7, 0xf2, 0xec, 0x4d, 0xb1, 0x25, 0xd9, 0x0f, 0x6e, 0xe9, 0xd8, - 0xcc, 0x34, 0x3c, 0x77, 0x8c, 0x06, 0x98, 0xec, 0x89, 0xdd, 0xb0, 0x4f, 0x63, 0x54, 0x0a, 0xc2, - 0xa4, 0x00, 0x0e, 0xda, 0x0a, 0xfc, 0xe3, 0xf6, 0x7f, 0x87, 0xb9, 0x4c, 0x1b, 0x4c, 0x20, 0x25, - 0xd3, 0x60, 0x7f, 0xc9, 0x02, 0x94, 0x4f, 0xdd, 0xc1, 0x98, 0x0a, 0x4d, 0xc4, 0x0b, 0xef, 0x16, - 0xee, 0x5a, 0x76, 0x17, 0xe6, 0x8d, 0x71, 0x09, 0x9e, 0x6c, 0xc1, 0x34, 0x93, 0x0d, 0x4c, 0xe7, - 0xa2, 0x5c, 0x75, 0x64, 0x91, 0xdc, 0x81, 0x85, 0x63, 0x4a, 0x43, 0x37, 0xc6, 0x62, 0x67, 0x44, - 0x43, 0xdc, 0x13, 0xd1, 0x72, 0x6e, 0x9d, 0xfd, 0xb7, 0x16, 0xcc, 0x32, 0xbe, 0x79, 0xcf, 0xf5, - 0xcf, 0xe5, 0x5a, 0xed, 0xe5, 0xae, 0xd5, 0xaa, 0x58, 0xab, 0x14, 0xf6, 0xc7, 0x5d, 0xa8, 0x62, - 0x7a, 0xa1, 0xc8, 0x0a, 0xd4, 0x8d, 0xe1, 0x96, 0xb9, 0x92, 0x8b, 0xdc, 0xf8, 0x80, 0x86, 0x9b, - 0xe7, 0x31, 0xfd, 0xc9, 0x97, 0xf2, 0x06, 0x34, 0x93, 0x61, 0x8b, 0x75, 0x24, 0x50, 0x62, 0x84, - 0x29, 0x1a, 0xc0, 0xff, 0xf6, 0x6f, 0x5a, 0x1c, 0x71, 0x2b, 0xf0, 0x94, 0x82, 0x64, 0x88, 0x4c, - 0x8f, 0x4a, 0x44, 0xf6, 0x7f, 0xa2, 0x01, 0xf1, 0x93, 0x4f, 0x96, 0x5c, 0x86, 0x4a, 0x44, 0xfd, - 0x5e, 0xc7, 0x1d, 0x0c, 0x50, 0x8f, 0x54, 0x9c, 0x69, 0x56, 0xde, 0x18, 0x0c, 0xec, 0x9b, 0x30, - 0xa7, 0x8d, 0xee, 0x25, 0xf3, 0xd8, 0x07, 0xb2, 0xe7, 0x45, 0xf1, 0x13, 0x3f, 0x1a, 0x69, 0xfa, - 0xe7, 0x0a, 0x54, 0x87, 0x9e, 0x8f, 0x23, 0xe3, 0x9c, 0x5b, 0x76, 0x2a, 0x43, 0xcf, 0x67, 0xe3, - 0x8a, 0xb0, 0xd2, 0x7d, 0x2e, 0x2a, 0x0b, 0xa2, 0xd2, 0x7d, 0x8e, 0x95, 0xf6, 0x5d, 0x98, 0x37, - 0xda, 0x13, 0x5d, 0xbf, 0x01, 0xe5, 0x71, 0xfc, 0x3c, 0x90, 0xd6, 0x41, 0x4d, 0x50, 0x08, 0xb3, - 0x33, 0x1d, 0x5e, 0x63, 0xdf, 0x83, 0xb9, 0x7d, 0x7a, 0x26, 0x18, 0x59, 0x0e, 0xe4, 0xc6, 0x85, - 0x36, 0x28, 0xd6, 0xdb, 0xb7, 0x80, 0xe8, 0x1f, 0x27, 0x0c, 0x20, 0x2d, 0x52, 0xcb, 0xb0, 0x48, - 0xed, 0x1b, 0x40, 0x0e, 0xbd, 0xbe, 0xff, 0x1e, 0x8d, 0x22, 0xb7, 0xaf, 0x58, 0xbf, 0x09, 0xc5, - 0x61, 0xd4, 0x17, 0xa2, 0x8a, 0xfd, 0xb5, 0x3f, 0x0d, 0xf3, 0x06, 0x9e, 0x68, 0xf8, 0x2a, 0x54, - 0x23, 0xaf, 0xef, 0xbb, 0xf1, 0x38, 0xa4, 0xa2, 0xe9, 0x04, 0x60, 0xdf, 0x87, 0x85, 0xaf, 0xd0, - 0xd0, 0x3b, 0x3e, 0xbf, 0xa8, 0x79, 0xb3, 0x9d, 0x42, 0xba, 0x9d, 0x1d, 0x58, 0x4c, 0xb5, 0x23, - 0xba, 0xe7, 0xe4, 0x2b, 0x76, 0xb2, 0xe2, 0xf0, 0x82, 0x26, 0xfb, 0x0a, 0xba, 0xec, 0xb3, 0x9f, - 0x00, 0xd9, 0x0a, 0x7c, 0x9f, 0x76, 0xe3, 0x03, 0x4a, 0xc3, 0xc4, 0x19, 0x4e, 0x68, 0xb5, 0x76, - 0x67, 0x59, 0xac, 0x6c, 0x5a, 0xa0, 0x0a, 0x22, 0x26, 0x50, 0x1a, 0xd1, 0x70, 0x88, 0x0d, 0x57, - 0x1c, 0xfc, 0x6f, 0x2f, 0xc2, 0xbc, 0xd1, 0xac, 0x70, 0x1f, 0xde, 0x86, 0xc5, 0x6d, 0x2f, 0xea, - 0x66, 0x3b, 0x6c, 0xc1, 0xf4, 0x68, 0x7c, 0xd4, 0x49, 0x38, 0x51, 0x16, 0x99, 0xc5, 0x99, 0xfe, - 0x44, 0x34, 0xf6, 0x33, 0x16, 0x94, 0x76, 0x1f, 0xef, 0x6d, 0x31, 0x5d, 0xe1, 0xf9, 0xdd, 0x60, - 0xc8, 0xf4, 0x2d, 0x9f, 0xb4, 0x2a, 0x4f, 0xe4, 0xb0, 0xab, 0x50, 0x45, 0x35, 0xcd, 0x8c, 0x68, - 0xe1, 0xb7, 0x26, 0x00, 0x66, 0xc0, 0xd3, 0xe7, 0x23, 0x2f, 0x44, 0x0b, 0x5d, 0xda, 0xdd, 0x25, - 0x54, 0x33, 0xd9, 0x0a, 0xfb, 0x87, 0x65, 0x98, 0x16, 0xca, 0x17, 0xfb, 0xeb, 0xc6, 0xde, 0x29, - 0x15, 0x23, 0x11, 0x25, 0x66, 0x02, 0x85, 0x74, 0x18, 0xc4, 0xb4, 0x63, 0x6c, 0x83, 0x09, 0x44, - 0x07, 0x45, 0xf8, 0x8e, 0xdc, 0xa5, 0x29, 0x72, 0x2c, 0x03, 0xc8, 0x16, 0x4b, 0xda, 0x67, 0x25, - 0xb4, 0xcf, 0x64, 0x91, 0xad, 0x44, 0xd7, 0x1d, 0xb9, 0x5d, 0x2f, 0x3e, 0x17, 0x22, 0x41, 0x95, - 0x59, 0xdb, 0x83, 0xa0, 0xeb, 0x32, 0xaf, 0x74, 0xe0, 0xfa, 0x5d, 0x2a, 0x9d, 0x1f, 0x03, 0xc8, - 0x1c, 0x01, 0x31, 0x24, 0x89, 0xc6, 0x9d, 0x85, 0x14, 0x94, 0xe9, 0xef, 0x6e, 0x30, 0x1c, 0x7a, - 0x31, 0xf3, 0x1f, 0xd0, 0xb6, 0x2c, 0x3a, 0x1a, 0x84, 0xbb, 0x5a, 0x58, 0x3a, 0xe3, 0xab, 0x57, - 0x95, 0xae, 0x96, 0x06, 0x64, 0xad, 0x30, 0xad, 0xc3, 0xc4, 0xd8, 0xb3, 0x33, 0x34, 0x24, 0x8b, - 0x8e, 0x06, 0x61, 0xfb, 0x30, 0xf6, 0x23, 0x1a, 0xc7, 0x03, 0xda, 0x53, 0x03, 0xaa, 0x21, 0x5a, - 0xb6, 0x82, 0xdc, 0x86, 0x79, 0xee, 0xd2, 0x44, 0x6e, 0x1c, 0x44, 0x27, 0x5e, 0xd4, 0x89, 0x98, - 0x73, 0x50, 0x47, 0xfc, 0xbc, 0x2a, 0x72, 0x17, 0x96, 0x53, 0xe0, 0x90, 0x76, 0xa9, 0x77, 0x4a, - 0x7b, 0xad, 0x19, 0xfc, 0x6a, 0x52, 0x35, 0x59, 0x81, 0x1a, 0xf3, 0xe4, 0xc6, 0xa3, 0x9e, 0xcb, - 0x0c, 0x98, 0x06, 0xee, 0x83, 0x0e, 0x22, 0x6f, 0xc3, 0xcc, 0x88, 0x72, 0xeb, 0xe7, 0x24, 0x1e, - 0x74, 0xa3, 0xd6, 0xac, 0x21, 0xdd, 0x18, 0xe5, 0x3a, 0x26, 0x06, 0x23, 0xca, 0x6e, 0x84, 0x26, - 0xbd, 0x7b, 0xde, 0x6a, 0x0a, 0xb3, 0x5a, 0x02, 0x90, 0x47, 0x42, 0xef, 0xd4, 0x8d, 0x69, 0x6b, - 0x8e, 0x0b, 0x74, 0x51, 0x64, 0xdf, 0x79, 0xbe, 0x17, 0x7b, 0x6e, 0x1c, 0x84, 0x2d, 0x82, 0x75, - 0x09, 0x80, 0x2d, 0x22, 0xd2, 0x47, 0x14, 0xbb, 0xf1, 0x38, 0xea, 0x1c, 0x0f, 0xdc, 0x7e, 0xd4, - 0x9a, 0xe7, 0x76, 0x69, 0xa6, 0xc2, 0xfe, 0x6d, 0x8b, 0x0b, 0x69, 0x41, 0xd0, 0x4a, 0xd8, 0xbe, - 0x0e, 0x35, 0x4e, 0xca, 0x9d, 0xc0, 0x1f, 0x9c, 0x0b, 0xea, 0x06, 0x0e, 0x7a, 0xe4, 0x0f, 0xce, - 0xc9, 0x27, 0x60, 0xc6, 0xf3, 0x75, 0x14, 0x2e, 0x0f, 0xea, 0x12, 0x88, 0x48, 0xaf, 0x43, 0x6d, - 0x34, 0x3e, 0x1a, 0x78, 0x5d, 0x8e, 0x52, 0xe4, 0xad, 0x70, 0x10, 0x22, 0x30, 0x4b, 0x9b, 0xcf, - 0x8a, 0x63, 0x94, 0x10, 0xa3, 0x26, 0x60, 0x0c, 0xc5, 0xde, 0x84, 0x05, 0x73, 0x80, 0x42, 0xf0, - 0xad, 0x41, 0x45, 0xf0, 0x49, 0xd4, 0xaa, 0xe1, 0x5a, 0x37, 0xb4, 0x88, 0x8b, 0x4f, 0x07, 0x8e, - 0xaa, 0xb7, 0xff, 0xb0, 0x04, 0xf3, 0x02, 0xba, 0x35, 0x08, 0x22, 0x7a, 0x38, 0x1e, 0x0e, 0xdd, - 0x30, 0x87, 0x01, 0xad, 0x0b, 0x18, 0xb0, 0x60, 0x32, 0x20, 0x63, 0x8b, 0x13, 0xd7, 0xf3, 0xb9, - 0x9b, 0xc0, 0xb9, 0x57, 0x83, 0x90, 0x55, 0x98, 0xed, 0x0e, 0x82, 0x88, 0x9b, 0xc4, 0xba, 0xc3, - 0x9f, 0x06, 0x67, 0x05, 0x46, 0x39, 0x4f, 0x60, 0xe8, 0x0c, 0x3f, 0x95, 0x62, 0x78, 0x1b, 0xea, - 0xac, 0x51, 0x2a, 0xe5, 0xd7, 0x34, 0x37, 0x93, 0x75, 0x18, 0x1b, 0x4f, 0x9a, 0xbd, 0x38, 0x2f, - 0xcf, 0xe6, 0x31, 0x97, 0x37, 0xa4, 0x28, 0x1f, 0x35, 0xec, 0xaa, 0x60, 0xae, 0x6c, 0x15, 0xb9, - 0xcf, 0xbc, 0x44, 0xd6, 0x17, 0x2a, 0x69, 0x40, 0x25, 0x7d, 0xc3, 0xdc, 0x11, 0x7d, 0xed, 0x6f, - 0xb1, 0xc2, 0x38, 0xa4, 0xa8, 0xb8, 0xb5, 0x2f, 0xed, 0x9f, 0xb7, 0xa0, 0xa6, 0xd5, 0x91, 0x45, - 0x98, 0xdb, 0x7a, 0xf4, 0xe8, 0x60, 0xc7, 0xd9, 0x78, 0xfc, 0xf0, 0x2b, 0x3b, 0x9d, 0xad, 0xbd, - 0x47, 0x87, 0x3b, 0xcd, 0x4b, 0x0c, 0xbc, 0xf7, 0x68, 0x6b, 0x63, 0xaf, 0x73, 0xff, 0x91, 0xb3, - 0x25, 0xc1, 0x16, 0x59, 0x02, 0xe2, 0xec, 0xbc, 0xf7, 0xe8, 0xf1, 0x8e, 0x01, 0x2f, 0x90, 0x26, - 0xd4, 0x37, 0x9d, 0x9d, 0x8d, 0xad, 0x5d, 0x01, 0x29, 0x92, 0x05, 0x68, 0xde, 0x7f, 0xb2, 0xbf, - 0xfd, 0x70, 0xff, 0x41, 0x67, 0x6b, 0x63, 0x7f, 0x6b, 0x67, 0x6f, 0x67, 0xbb, 0x59, 0x22, 0x33, - 0x50, 0xdd, 0xd8, 0xdc, 0xd8, 0xdf, 0x7e, 0xb4, 0xbf, 0xb3, 0xdd, 0x2c, 0xdb, 0x7f, 0x6d, 0xc1, - 0x22, 0x8e, 0xba, 0x97, 0x66, 0x90, 0x15, 0xa8, 0x75, 0x83, 0x60, 0xc4, 0x8c, 0xe3, 0x44, 0xfc, - 0xeb, 0x20, 0x46, 0xfc, 0x5c, 0xd8, 0x1e, 0x07, 0x61, 0x97, 0x0a, 0xfe, 0x00, 0x04, 0xdd, 0x67, - 0x10, 0x46, 0xfc, 0x62, 0x7b, 0x39, 0x06, 0x67, 0x8f, 0x1a, 0x87, 0x71, 0x94, 0x25, 0x98, 0x3a, - 0x0a, 0xa9, 0xdb, 0x3d, 0x11, 0x9c, 0x21, 0x4a, 0xe4, 0x53, 0x89, 0xf7, 0xd6, 0x65, 0xab, 0x3f, - 0xa0, 0x3d, 0xa4, 0x98, 0x8a, 0x33, 0x2b, 0xe0, 0x5b, 0x02, 0xcc, 0xa4, 0x85, 0x7b, 0xe4, 0xfa, - 0xbd, 0xc0, 0xa7, 0x3d, 0x61, 0x1a, 0x26, 0x00, 0xfb, 0x00, 0x96, 0xd2, 0xf3, 0x13, 0xfc, 0xf5, - 0x8e, 0xc6, 0x5f, 0xdc, 0x52, 0x6b, 0x4f, 0xde, 0x4d, 0x8d, 0xd7, 0xfe, 0xc1, 0x82, 0x12, 0x53, - 0xdc, 0x93, 0x95, 0xbc, 0x6e, 0x8b, 0x15, 0x33, 0xd1, 0x41, 0x74, 0x08, 0xb9, 0x28, 0xe7, 0xea, - 0x4e, 0x83, 0x24, 0xf5, 0x21, 0xed, 0x9e, 0xe2, 0x8c, 0x55, 0x3d, 0x83, 0x30, 0x06, 0x61, 0x86, - 0x32, 0x7e, 0x2d, 0x18, 0x44, 0x96, 0x65, 0x1d, 0x7e, 0x39, 0x9d, 0xd4, 0xe1, 0x77, 0x2d, 0x98, - 0xf6, 0xfc, 0xa3, 0x60, 0xec, 0xf7, 0x90, 0x21, 0x2a, 0x8e, 0x2c, 0x62, 0x3c, 0x12, 0x19, 0xd5, - 0x1b, 0x4a, 0xf2, 0x4f, 0x00, 0x36, 0x61, 0x7e, 0x67, 0x84, 0x86, 0x8a, 0x0a, 0x8d, 0xbd, 0x03, - 0x73, 0x1a, 0x2c, 0x31, 0x7a, 0x47, 0x0c, 0x90, 0x32, 0x7a, 0xd1, 0xc2, 0xe1, 0x35, 0x76, 0x13, - 0x1a, 0x0f, 0x68, 0xfc, 0xd0, 0x3f, 0x0e, 0x64, 0x4b, 0xbf, 0x5b, 0x82, 0x59, 0x05, 0x12, 0x0d, - 0xad, 0xc2, 0xac, 0xd7, 0xa3, 0x7e, 0xec, 0xc5, 0xe7, 0x1d, 0xc3, 0xbd, 0x4d, 0x83, 0x99, 0x65, - 0xe8, 0x0e, 0x3c, 0x57, 0x46, 0x60, 0x79, 0x81, 0xb9, 0x7b, 0x4c, 0x6d, 0x49, 0x4d, 0xa4, 0xb6, - 0x98, 0x7b, 0xd5, 0xb9, 0x75, 0x4c, 0x18, 0x30, 0xb8, 0x90, 0xf6, 0xea, 0x13, 0x6e, 0x21, 0xe5, - 0x55, 0xb1, 0x55, 0xe3, 0x2d, 0xb1, 0x29, 0x97, 0xb9, 0x6a, 0x53, 0x80, 0x4c, 0x88, 0x73, 0x8a, - 0x8b, 0xaa, 0x74, 0x88, 0x53, 0x0b, 0x93, 0x56, 0x32, 0x61, 0x52, 0x26, 0xca, 0xce, 0xfd, 0x2e, - 0xed, 0x75, 0xe2, 0xa0, 0x83, 0x22, 0x17, 0x77, 0xa7, 0xe2, 0xa4, 0xc1, 0xe4, 0x2a, 0x4c, 0xc7, - 0x34, 0x8a, 0x7d, 0xca, 0x63, 0x57, 0x15, 0x8c, 0xb6, 0x48, 0x10, 0x33, 0x67, 0xc7, 0xa1, 0x17, - 0xb5, 0xea, 0x18, 0x00, 0xc5, 0xff, 0xe4, 0x33, 0xb0, 0x78, 0x44, 0xa3, 0xb8, 0x73, 0x42, 0xdd, - 0x1e, 0x0d, 0x71, 0xa7, 0x79, 0xa4, 0x95, 0x5b, 0x09, 0xf9, 0x95, 0x8c, 0x86, 0x4e, 0x69, 0x18, - 0x79, 0x81, 0x8f, 0xf6, 0x41, 0xd5, 0x91, 0x45, 0xd6, 0x1e, 0x9b, 0xbc, 0xd2, 0x97, 0x6a, 0x05, - 0x67, 0x71, 0xe2, 0xf9, 0x95, 0xe4, 0x3a, 0x4c, 0xe1, 0x04, 0xa2, 0x56, 0xd3, 0x08, 0x19, 0x6d, - 0x31, 0xa0, 0x23, 0xea, 0xbe, 0x58, 0xaa, 0xd4, 0x9a, 0x75, 0xfb, 0x73, 0x50, 0x46, 0x30, 0xdb, - 0x74, 0xbe, 0x18, 0x9c, 0x28, 0x78, 0x81, 0x0d, 0xcd, 0xa7, 0xf1, 0x59, 0x10, 0x3e, 0x93, 0xe1, - 0x78, 0x51, 0xb4, 0xbf, 0x89, 0x0e, 0x81, 0x0a, 0x4f, 0x3f, 0x41, 0x6b, 0x86, 0xb9, 0x75, 0x7c, - 0xa9, 0xa3, 0x13, 0x57, 0xf8, 0x28, 0x15, 0x04, 0x1c, 0x9e, 0xb8, 0x4c, 0x6c, 0x19, 0xbb, 0xc7, - 0xdd, 0xbe, 0x1a, 0xc2, 0x76, 0xf9, 0xe6, 0x5d, 0x87, 0x86, 0x0c, 0x7c, 0x47, 0x9d, 0x01, 0x3d, - 0x8e, 0x65, 0xd0, 0xc6, 0x1f, 0x0f, 0xd1, 0x37, 0xdc, 0xa3, 0xc7, 0xb1, 0xbd, 0x0f, 0x73, 0x42, - 0x94, 0x3c, 0x1a, 0x51, 0xd9, 0xf5, 0xe7, 0xf3, 0x54, 0x72, 0xed, 0xce, 0xbc, 0x29, 0x7b, 0x78, - 0xa8, 0xdf, 0xc4, 0xb4, 0x1d, 0x20, 0xba, 0x68, 0x12, 0x0d, 0x0a, 0xbd, 0x28, 0xc3, 0x52, 0x62, - 0x3a, 0x06, 0x8c, 0xad, 0x4f, 0x34, 0xee, 0x76, 0xe5, 0x71, 0x05, 0x73, 0x9e, 0x79, 0xd1, 0xfe, - 0x3d, 0x0b, 0xe6, 0xb1, 0x35, 0x69, 0x54, 0x08, 0xf1, 0x7f, 0xf7, 0x63, 0x0c, 0xb3, 0xde, 0xd5, - 0x43, 0x75, 0x0b, 0x50, 0xd6, 0x15, 0x02, 0x2f, 0x7c, 0xfc, 0x10, 0x40, 0x29, 0x1d, 0x02, 0xb0, - 0x7f, 0xdd, 0x82, 0x39, 0x2e, 0x93, 0xd1, 0xc0, 0x13, 0xd3, 0xff, 0xaf, 0x30, 0xc3, 0x95, 0xab, - 0xe0, 0x6a, 0x31, 0xd0, 0x05, 0x25, 0x80, 0x10, 0xca, 0x91, 0x77, 0x2f, 0x39, 0x26, 0x32, 0xb9, - 0x87, 0x06, 0x8e, 0xdf, 0x41, 0x68, 0xce, 0xc1, 0x96, 0xb9, 0xd6, 0xbb, 0x97, 0x1c, 0x0d, 0x7d, - 0xb3, 0x02, 0x53, 0xdc, 0x3a, 0xb6, 0x1f, 0xc0, 0x8c, 0xd1, 0x91, 0x11, 0x7e, 0xa8, 0xf3, 0xf0, - 0x43, 0x26, 0xce, 0x57, 0xc8, 0x89, 0xf3, 0xfd, 0x41, 0x11, 0x08, 0x23, 0x96, 0xd4, 0x6e, 0x30, - 0xf3, 0x3c, 0xe8, 0x19, 0xce, 0x56, 0xdd, 0xd1, 0x41, 0xe4, 0x16, 0x10, 0xad, 0x28, 0xc3, 0xb5, - 0x5c, 0xfb, 0xe4, 0xd4, 0x30, 0x31, 0x29, 0x94, 0xb7, 0x50, 0xb3, 0xc2, 0xad, 0xe4, 0xcb, 0x9e, - 0x5b, 0xc7, 0x14, 0xcc, 0x68, 0x1c, 0x9d, 0x60, 0x90, 0x4d, 0xb8, 0x63, 0xb2, 0x9c, 0xde, 0xdf, - 0xa9, 0x0b, 0xf7, 0x77, 0x3a, 0x13, 0xe2, 0xd1, 0x1c, 0x82, 0x8a, 0xe9, 0x10, 0x5c, 0x87, 0x99, - 0x21, 0x33, 0x39, 0xe3, 0x41, 0xb7, 0x33, 0x64, 0xbd, 0x0b, 0xef, 0xcb, 0x00, 0x92, 0x35, 0x68, - 0x0a, 0x73, 0x23, 0xf1, 0x3a, 0x78, 0x30, 0x3f, 0x03, 0x67, 0xf2, 0x3b, 0x09, 0xfa, 0xd4, 0x70, - 0xb0, 0x09, 0x80, 0xb9, 0x18, 0x11, 0xa3, 0x90, 0xce, 0xd8, 0x17, 0x67, 0x5b, 0xb4, 0x87, 0x7e, - 0x57, 0xc5, 0xc9, 0x56, 0xd8, 0xbf, 0x6c, 0x41, 0x93, 0xed, 0x99, 0x41, 0x96, 0xef, 0x02, 0x72, - 0xc5, 0x2b, 0x52, 0xa5, 0x81, 0x4b, 0xee, 0x42, 0x15, 0xcb, 0xc1, 0x88, 0xfa, 0x82, 0x26, 0x5b, - 0x26, 0x4d, 0x26, 0xf2, 0x64, 0xf7, 0x92, 0x93, 0x20, 0x6b, 0x14, 0xf9, 0x17, 0x16, 0xd4, 0x44, - 0x2f, 0x3f, 0x76, 0x50, 0xa1, 0xad, 0x1d, 0x46, 0x72, 0x4a, 0x4a, 0xce, 0x1e, 0x57, 0x61, 0x76, - 0xe8, 0xc6, 0xe3, 0x90, 0xe9, 0x63, 0x23, 0xa0, 0x90, 0x06, 0x33, 0xe5, 0x8a, 0xa2, 0x33, 0xea, - 0xc4, 0xde, 0xa0, 0x23, 0x6b, 0xc5, 0xb1, 0x5f, 0x5e, 0x15, 0x93, 0x20, 0x51, 0xec, 0xf6, 0xa9, - 0xd0, 0x9b, 0xbc, 0x60, 0xb7, 0x60, 0x49, 0x4c, 0x28, 0x65, 0xaa, 0xda, 0x3f, 0xa8, 0xc3, 0x72, - 0xa6, 0x4a, 0x25, 0x29, 0x08, 0x4f, 0x79, 0xe0, 0x0d, 0x8f, 0x02, 0x65, 0xe7, 0x5b, 0xba, 0x13, - 0x6d, 0x54, 0x91, 0x3e, 0x2c, 0x4a, 0x03, 0x81, 0xad, 0x69, 0xa2, 0xcc, 0x0a, 0xa8, 0xa5, 0xde, - 0x36, 0xb7, 0x30, 0xdd, 0xa1, 0x84, 0xeb, 0x4c, 0x9c, 0xdf, 0x1e, 0x39, 0x81, 0x96, 0xb2, 0x44, - 0x84, 0xb0, 0xd6, 0xac, 0x15, 0xd6, 0xd7, 0x5b, 0x17, 0xf4, 0x65, 0x58, 0xb6, 0xce, 0xc4, 0xd6, - 0xc8, 0x39, 0x5c, 0x93, 0x75, 0x28, 0x8d, 0xb3, 0xfd, 0x95, 0x5e, 0x69, 0x6e, 0x68, 0xb3, 0x9b, - 0x9d, 0x5e, 0xd0, 0x30, 0xf9, 0x00, 0x96, 0xce, 0x5c, 0x2f, 0x96, 0xc3, 0xd2, 0x6c, 0x83, 0x32, - 0x76, 0x79, 0xe7, 0x82, 0x2e, 0x9f, 0xf2, 0x8f, 0x0d, 0x15, 0x35, 0xa1, 0xc5, 0xf6, 0x0f, 0x2d, - 0x68, 0x98, 0xed, 0x30, 0x32, 0x15, 0xbc, 0x2f, 0x65, 0xa0, 0xb4, 0x26, 0x53, 0xe0, 0xac, 0xab, - 0x5c, 0xc8, 0x73, 0x95, 0x75, 0x07, 0xb5, 0x78, 0x51, 0x44, 0xaa, 0xf4, 0x6a, 0x11, 0xa9, 0x72, - 0x5e, 0x44, 0xaa, 0xfd, 0xaf, 0x16, 0x90, 0x2c, 0x2d, 0x91, 0x07, 0xdc, 0x57, 0xf7, 0xe9, 0x40, - 0x88, 0x94, 0xff, 0xf2, 0x6a, 0xf4, 0x28, 0xd7, 0x4e, 0x7e, 0xcd, 0x18, 0x43, 0x3f, 0xb7, 0xd7, - 0x8d, 0x9d, 0x19, 0x27, 0xaf, 0x2a, 0x15, 0x23, 0x2b, 0x5d, 0x1c, 0x23, 0x2b, 0x5f, 0x1c, 0x23, - 0x9b, 0x4a, 0xc7, 0xc8, 0xda, 0xff, 0xdf, 0x82, 0xf9, 0x9c, 0x4d, 0xff, 0xe9, 0x4d, 0x9c, 0x6d, - 0x93, 0x21, 0x0b, 0x0a, 0x62, 0x9b, 0x74, 0x60, 0xfb, 0x7f, 0xc3, 0x8c, 0x41, 0xe8, 0x3f, 0xbd, - 0xfe, 0xd3, 0xf6, 0x1a, 0xa7, 0x33, 0x03, 0xd6, 0xfe, 0xc7, 0x02, 0x90, 0x2c, 0xb3, 0xfd, 0xa7, - 0x8e, 0x21, 0xbb, 0x4e, 0xc5, 0x9c, 0x75, 0xfa, 0x0f, 0xd5, 0x03, 0x6f, 0xc1, 0x9c, 0x48, 0x46, - 0xd2, 0x22, 0x34, 0x9c, 0x62, 0xb2, 0x15, 0xcc, 0x62, 0x35, 0x03, 0x94, 0x15, 0x23, 0x39, 0x43, - 0x53, 0x86, 0xa9, 0x38, 0xa5, 0xdd, 0x86, 0x96, 0x58, 0xa1, 0x9d, 0x53, 0xea, 0xc7, 0x87, 0xe3, - 0x23, 0x9e, 0x8d, 0xe3, 0x05, 0xbe, 0xfd, 0xfd, 0xa2, 0x32, 0xba, 0xb1, 0x52, 0xa8, 0xf7, 0xcf, - 0x40, 0x5d, 0x17, 0xe6, 0x62, 0x3b, 0x52, 0x01, 0x3a, 0xa6, 0xd8, 0x75, 0x2c, 0xb2, 0x0d, 0x0d, - 0x14, 0x59, 0x3d, 0xf5, 0x5d, 0x01, 0xbf, 0x7b, 0x49, 0xe0, 0x61, 0xf7, 0x92, 0x93, 0xfa, 0x86, - 0x7c, 0x01, 0x1a, 0xa6, 0x2b, 0x25, 0x6c, 0x84, 0x3c, 0xdb, 0x9c, 0x7d, 0x6e, 0x22, 0x93, 0x0d, - 0x68, 0xa6, 0x7d, 0x31, 0x71, 0x14, 0x3f, 0xa1, 0x81, 0x0c, 0x3a, 0xb9, 0x2b, 0x4e, 0xaa, 0xca, - 0x18, 0x04, 0xbb, 0x6e, 0x7e, 0xa6, 0x2d, 0xd3, 0x2d, 0xfe, 0xa3, 0x9d, 0x5d, 0x7d, 0x1d, 0x20, - 0x81, 0x91, 0x26, 0xd4, 0x1f, 0x1d, 0xec, 0xec, 0x77, 0xb6, 0x76, 0x37, 0xf6, 0xf7, 0x77, 0xf6, - 0x9a, 0x97, 0x08, 0x81, 0x06, 0xc6, 0xaf, 0xb6, 0x15, 0xcc, 0x62, 0xb0, 0x8d, 0x2d, 0x1e, 0x1b, - 0x13, 0xb0, 0x02, 0x59, 0x80, 0xe6, 0xc3, 0xfd, 0x14, 0xb4, 0xb8, 0x59, 0x55, 0xfc, 0x61, 0x2f, - 0xc1, 0x02, 0x4f, 0x36, 0xdb, 0xe4, 0xe4, 0x21, 0x6d, 0x85, 0xdf, 0xb2, 0x60, 0x31, 0x55, 0x91, - 0x64, 0x75, 0x70, 0x73, 0xc0, 0xb4, 0x11, 0x4c, 0x20, 0x46, 0x9f, 0xa5, 0xe5, 0x97, 0x92, 0x20, - 0xd9, 0x0a, 0x46, 0xf3, 0x9a, 0xa5, 0x98, 0xe2, 0xa4, 0xbc, 0x2a, 0x7b, 0x99, 0xa7, 0xc4, 0x61, - 0xf2, 0x9c, 0x31, 0xf0, 0x63, 0x9e, 0xc4, 0xa6, 0x57, 0x24, 0x27, 0x7f, 0xe6, 0x90, 0x65, 0x91, - 0x19, 0xf9, 0x86, 0xe9, 0x61, 0x8e, 0x37, 0xb7, 0xce, 0xfe, 0xd3, 0x02, 0x90, 0x2f, 0x8f, 0x69, - 0x78, 0x8e, 0x09, 0x19, 0x2a, 0x1c, 0xb8, 0x9c, 0x0e, 0x76, 0x4d, 0x8d, 0xc6, 0x47, 0x5f, 0xa2, - 0xe7, 0x32, 0x99, 0xa8, 0xa0, 0x27, 0x13, 0x01, 0x73, 0x8e, 0x55, 0x3a, 0x88, 0xb5, 0x5a, 0xc6, - 0x90, 0x44, 0xd5, 0x1f, 0x0f, 0x79, 0xa3, 0xb9, 0x39, 0x3f, 0xa5, 0x8b, 0x73, 0x7e, 0xca, 0x17, - 0xe5, 0xfc, 0x7c, 0x02, 0x66, 0xbc, 0xbe, 0x1f, 0x30, 0xb1, 0xc0, 0x14, 0x7b, 0xd4, 0x9a, 0x5a, - 0x29, 0x32, 0x67, 0x58, 0x00, 0xf7, 0x19, 0x8c, 0x7c, 0x2e, 0x41, 0xa2, 0xbd, 0x3e, 0xe6, 0x8f, - 0xe9, 0x82, 0x62, 0xa7, 0xd7, 0xa7, 0x7b, 0x41, 0xd7, 0x8d, 0x83, 0x50, 0x7d, 0xc8, 0x60, 0x11, - 0xf3, 0xfa, 0xa3, 0x60, 0xcc, 0xcc, 0x1c, 0xb9, 0x14, 0x3c, 0x6c, 0x53, 0xe7, 0xd0, 0x03, 0x5c, - 0x10, 0xfb, 0xab, 0x50, 0xd3, 0x9a, 0xc0, 0xe4, 0x22, 0x61, 0x42, 0x08, 0x7f, 0xb0, 0xc4, 0x2d, - 0x76, 0x9f, 0x0e, 0x1e, 0xf6, 0xc8, 0x9b, 0x30, 0xd7, 0xf3, 0x42, 0x8a, 0x79, 0x62, 0x9d, 0x90, - 0x9e, 0xd2, 0x30, 0x92, 0x9e, 0x73, 0x53, 0x55, 0x38, 0x1c, 0x6e, 0xdf, 0x83, 0x79, 0x63, 0x6b, - 0x14, 0xe5, 0xca, 0xdc, 0x1b, 0x2b, 0x9b, 0x7b, 0x23, 0xf3, 0x6e, 0xec, 0x9f, 0x2d, 0x40, 0x71, - 0x37, 0x18, 0xe9, 0xd1, 0x7e, 0xcb, 0x8c, 0xf6, 0x0b, 0x13, 0xa8, 0xa3, 0x2c, 0x1c, 0xa1, 0x19, - 0x0d, 0x20, 0x59, 0x83, 0x86, 0x3b, 0x8c, 0x3b, 0x71, 0xc0, 0x4c, 0xbe, 0x33, 0x37, 0xec, 0x71, - 0x72, 0xc6, 0x2d, 0x4e, 0xd5, 0x90, 0x05, 0x28, 0x2a, 0x5b, 0x01, 0x11, 0x58, 0x91, 0xf9, 0x1b, - 0x78, 0xea, 0x78, 0x2e, 0x22, 0x67, 0xa2, 0xc4, 0xb8, 0xc5, 0xfc, 0x9e, 0x3b, 0x7b, 0x5c, 0xe2, - 0xe7, 0x55, 0x31, 0x73, 0x8c, 0x51, 0x07, 0xa2, 0x89, 0x90, 0xa7, 0x2c, 0xeb, 0xe1, 0xd9, 0x8a, - 0x79, 0x06, 0xfb, 0xf7, 0x16, 0x94, 0x71, 0x6d, 0x98, 0xf6, 0xe2, 0xec, 0xad, 0x02, 0xfe, 0xb8, - 0x26, 0x33, 0x4e, 0x1a, 0x4c, 0x6c, 0x23, 0xe3, 0xb0, 0xa0, 0x26, 0xa4, 0x67, 0x1d, 0xae, 0x40, - 0x95, 0x97, 0x54, 0x76, 0x1d, 0xa7, 0x7b, 0x05, 0x24, 0xd7, 0xa0, 0x74, 0x12, 0x8c, 0xa4, 0xb9, - 0x0d, 0xf2, 0xec, 0x2c, 0x18, 0x39, 0x08, 0x4f, 0xc6, 0xc3, 0xda, 0xe3, 0xd3, 0xe2, 0x46, 0x54, - 0x1a, 0xcc, 0xcc, 0x48, 0xd5, 0xac, 0xbe, 0x4c, 0x29, 0xa8, 0xbd, 0x06, 0xb3, 0x8c, 0xea, 0xb5, - 0xa8, 0xeb, 0x44, 0x56, 0xb6, 0xff, 0xaf, 0x05, 0x15, 0x89, 0x4c, 0x56, 0xa1, 0xc4, 0x58, 0x28, - 0xe5, 0xb8, 0xaa, 0x33, 0x73, 0x86, 0xe7, 0x20, 0x06, 0x33, 0x26, 0x30, 0x18, 0x96, 0xf8, 0x49, - 0x32, 0x14, 0x96, 0xb8, 0x01, 0x6a, 0xb8, 0x29, 0xeb, 0x39, 0x05, 0xb5, 0xbf, 0x67, 0xc1, 0x8c, - 0xd1, 0x07, 0x59, 0x81, 0xda, 0xc0, 0x8d, 0x62, 0x71, 0x0e, 0x29, 0xb6, 0x47, 0x07, 0xe9, 0x1b, - 0x5d, 0x30, 0xe3, 0xf0, 0x2a, 0x42, 0x5c, 0xd4, 0x23, 0xc4, 0xb7, 0xa1, 0x9a, 0xe4, 0x85, 0x96, - 0x0c, 0xde, 0x67, 0x3d, 0xca, 0x6c, 0x80, 0x04, 0x09, 0x83, 0x8e, 0xc1, 0x20, 0x08, 0xc5, 0xa1, - 0x15, 0x2f, 0xd8, 0xf7, 0xa0, 0xa6, 0xe1, 0xeb, 0x31, 0x48, 0xcb, 0x88, 0x41, 0xaa, 0x54, 0x99, - 0x42, 0x92, 0x2a, 0x63, 0xff, 0x93, 0x05, 0x33, 0x8c, 0x06, 0x3d, 0xbf, 0x7f, 0x10, 0x0c, 0xbc, - 0xee, 0x39, 0xee, 0xbd, 0x24, 0x37, 0x21, 0x12, 0x25, 0x2d, 0x9a, 0x60, 0x46, 0xf5, 0x32, 0xf2, - 0x21, 0x58, 0x54, 0x95, 0x19, 0x0f, 0x33, 0x0e, 0x38, 0x72, 0x23, 0xc1, 0x16, 0xc2, 0x6a, 0x33, - 0x80, 0x8c, 0xd3, 0x18, 0x00, 0x13, 0x9f, 0x86, 0xde, 0x60, 0xe0, 0x71, 0x5c, 0x6e, 0xd3, 0xe7, - 0x55, 0xb1, 0x3e, 0x7b, 0x5e, 0xe4, 0x1e, 0x25, 0x07, 0x31, 0xaa, 0x8c, 0xe1, 0x19, 0xf7, 0xb9, - 0x16, 0x9e, 0x99, 0x42, 0xb9, 0x62, 0x02, 0xed, 0x3f, 0x2e, 0x40, 0x4d, 0x9a, 0x08, 0xbd, 0x3e, - 0x15, 0x67, 0x8b, 0xa6, 0x60, 0xd4, 0x20, 0xb2, 0xde, 0xf0, 0xc6, 0x34, 0x48, 0x9a, 0x30, 0x8a, - 0x59, 0xc2, 0xb8, 0x0a, 0x55, 0x46, 0xa0, 0x6f, 0xa3, 0xdb, 0x27, 0x52, 0xad, 0x15, 0x40, 0xd6, - 0xde, 0xc1, 0xda, 0x72, 0x52, 0x8b, 0x80, 0x97, 0x9e, 0x44, 0xde, 0x85, 0xba, 0x68, 0x06, 0x77, - 0x0e, 0x25, 0x4f, 0xc2, 0x22, 0xc6, 0xae, 0x3a, 0x06, 0xa6, 0xfc, 0xf2, 0x8e, 0xfc, 0xb2, 0x72, - 0xd1, 0x97, 0x12, 0xd3, 0x7e, 0xa0, 0x0e, 0x78, 0x1f, 0x84, 0xee, 0xe8, 0x44, 0xf2, 0xf2, 0x6d, - 0x98, 0xf7, 0xfc, 0xee, 0x60, 0xdc, 0xa3, 0x9d, 0xb1, 0xef, 0xfa, 0x7e, 0x30, 0xf6, 0xbb, 0x54, - 0xe6, 0xca, 0xe4, 0x55, 0xd9, 0x3d, 0x95, 0x59, 0x89, 0x0d, 0x91, 0x35, 0x28, 0x73, 0x55, 0xc9, - 0x75, 0x47, 0x3e, 0xa3, 0x73, 0x14, 0xb2, 0x0a, 0x65, 0xae, 0x31, 0x0b, 0x06, 0xd7, 0x68, 0xbb, - 0xea, 0x70, 0x04, 0x26, 0x76, 0x30, 0xb9, 0xd6, 0x14, 0x3b, 0xa6, 0xde, 0x99, 0xea, 0x62, 0xfa, - 0xad, 0xbd, 0x00, 0x64, 0x9f, 0x73, 0x8a, 0x7e, 0x36, 0xf4, 0x83, 0x22, 0xd4, 0x34, 0x30, 0x93, - 0x20, 0x7d, 0x36, 0xe0, 0x4e, 0xcf, 0x73, 0x87, 0x34, 0xa6, 0xa1, 0xe0, 0x8e, 0x14, 0x94, 0xe1, - 0xb9, 0xa7, 0xfd, 0x4e, 0x30, 0x8e, 0x3b, 0x3d, 0xda, 0x0f, 0x29, 0xd7, 0xa6, 0x4c, 0x35, 0x19, - 0x50, 0x86, 0xc7, 0xe8, 0x53, 0xc3, 0xe3, 0x14, 0x94, 0x82, 0xca, 0x93, 0x1e, 0xbe, 0x46, 0xa5, - 0xe4, 0xa4, 0x87, 0xaf, 0x48, 0x5a, 0xf6, 0x95, 0x73, 0x64, 0xdf, 0x3b, 0xb0, 0xc4, 0xa5, 0x9c, - 0x90, 0x07, 0x9d, 0x14, 0x61, 0x4d, 0xa8, 0x25, 0x6b, 0xd0, 0x64, 0x63, 0x96, 0x2c, 0x11, 0x79, - 0xdf, 0xe4, 0x51, 0x53, 0xcb, 0xc9, 0xc0, 0x19, 0x2e, 0x86, 0x2f, 0x75, 0x5c, 0x7e, 0xf2, 0x9d, - 0x81, 0x23, 0xae, 0xfb, 0xdc, 0xc4, 0xad, 0x0a, 0xdc, 0x14, 0x9c, 0xdc, 0x85, 0xe5, 0x21, 0xed, - 0x79, 0xae, 0xd9, 0x04, 0x46, 0x80, 0x79, 0x7a, 0xcb, 0xa4, 0x6a, 0x7b, 0x06, 0x6a, 0x87, 0x71, - 0x30, 0x92, 0xdb, 0xd9, 0x80, 0x3a, 0x2f, 0x8a, 0x6c, 0xa7, 0x2b, 0x70, 0x19, 0xe9, 0xef, 0x71, - 0x30, 0x0a, 0x06, 0x41, 0xff, 0xdc, 0x70, 0xba, 0xfe, 0xdc, 0x82, 0x79, 0xa3, 0x36, 0xf1, 0xba, - 0x30, 0x5e, 0x23, 0xd3, 0x54, 0x38, 0xc9, 0xce, 0x69, 0xc2, 0x9b, 0x23, 0xf2, 0xd0, 0xf8, 0x13, - 0x91, 0xb9, 0xb2, 0x91, 0xdc, 0x60, 0x91, 0x1f, 0x72, 0xfa, 0x6d, 0x65, 0xe9, 0x57, 0x7c, 0x2f, - 0x2f, 0xb0, 0xc8, 0x26, 0xbe, 0x20, 0x72, 0x0f, 0xb8, 0x13, 0x26, 0xc3, 0x73, 0xca, 0x6d, 0xd3, - 0x9d, 0x74, 0x39, 0x82, 0xae, 0x02, 0x46, 0xf6, 0x2f, 0x58, 0x00, 0xc9, 0xe8, 0xf0, 0xc4, 0x5a, - 0x29, 0x20, 0x7e, 0x5b, 0x4a, 0x53, 0x36, 0x6f, 0x40, 0x5d, 0x9d, 0x74, 0x26, 0x3a, 0xad, 0x26, - 0x61, 0xcc, 0xe6, 0xbe, 0x09, 0xb3, 0xfd, 0x41, 0x70, 0x84, 0x06, 0x01, 0xa6, 0xcf, 0x45, 0x22, - 0xe7, 0xab, 0xc1, 0xc1, 0xf7, 0x05, 0x34, 0x51, 0x80, 0x25, 0x4d, 0x01, 0xda, 0xbf, 0x58, 0x50, - 0x07, 0x53, 0xc9, 0x9c, 0x27, 0xf2, 0x27, 0xb9, 0x93, 0x11, 0xc4, 0x13, 0xce, 0x81, 0xd0, 0xac, - 0x3d, 0xb8, 0x30, 0x4e, 0x76, 0x0f, 0x1a, 0x21, 0x97, 0x74, 0x52, 0x0c, 0x96, 0x5e, 0x22, 0x06, - 0x67, 0x42, 0x43, 0x4b, 0x7e, 0x0a, 0x9a, 0x6e, 0xef, 0x94, 0x86, 0xb1, 0x87, 0x91, 0x0a, 0x34, - 0x51, 0xb8, 0xf0, 0x9e, 0xd5, 0xe0, 0x68, 0x39, 0xdc, 0x84, 0x59, 0x91, 0x67, 0xa7, 0x30, 0xc5, - 0x0d, 0x84, 0x04, 0xcc, 0x10, 0xed, 0xef, 0xca, 0x33, 0x30, 0x73, 0x0f, 0x27, 0xaf, 0x88, 0x3e, - 0xbb, 0x42, 0x6a, 0x76, 0x9f, 0x10, 0xe7, 0x51, 0x3d, 0x19, 0x0e, 0x29, 0x6a, 0x79, 0x2a, 0x3d, - 0x71, 0x7e, 0x68, 0x2e, 0x69, 0xe9, 0x55, 0x96, 0xd4, 0xfe, 0x91, 0x05, 0xd3, 0xbb, 0xc1, 0x68, - 0x57, 0x64, 0xec, 0x20, 0x23, 0xa8, 0x04, 0x57, 0x59, 0x7c, 0x49, 0x2e, 0x4f, 0xae, 0x65, 0x30, - 0x93, 0xb6, 0x0c, 0xfe, 0x07, 0x5c, 0xc1, 0x60, 0x5c, 0x18, 0x8c, 0x82, 0x90, 0x31, 0xa3, 0x3b, - 0xe0, 0x66, 0x40, 0xe0, 0xc7, 0x27, 0x52, 0x00, 0xbe, 0x0c, 0x05, 0x3d, 0x64, 0xe6, 0xd5, 0x71, - 0xa3, 0x5e, 0x58, 0x32, 0x5c, 0x2e, 0x66, 0x2b, 0xec, 0xcf, 0x43, 0x15, 0x4d, 0x71, 0x9c, 0xd6, - 0x5b, 0x50, 0x3d, 0x09, 0x46, 0x9d, 0x13, 0xcf, 0x8f, 0x25, 0x73, 0x37, 0x12, 0x1b, 0x79, 0x17, - 0x17, 0x44, 0x21, 0xd8, 0xbf, 0x3a, 0x05, 0xd3, 0x0f, 0xfd, 0xd3, 0xc0, 0xeb, 0xe2, 0x79, 0xdb, - 0x90, 0x0e, 0x03, 0x99, 0xee, 0xcb, 0xfe, 0x93, 0xab, 0x30, 0x8d, 0xf9, 0x6d, 0x23, 0x4e, 0xb4, - 0x75, 0x7e, 0x2e, 0x2e, 0x40, 0xcc, 0xbc, 0x08, 0x93, 0x8b, 0x19, 0x9c, 0x7d, 0x34, 0x08, 0x73, - 0x52, 0x42, 0xfd, 0x62, 0x85, 0x28, 0x25, 0xe9, 0xd4, 0x65, 0x2d, 0x9d, 0x9a, 0xf5, 0x25, 0x32, - 0x8c, 0x78, 0x0a, 0x0a, 0xef, 0x4b, 0x80, 0xd0, 0xb1, 0x0a, 0x29, 0x0f, 0xa6, 0xa2, 0xb1, 0x32, - 0x2d, 0x1c, 0x2b, 0x1d, 0xc8, 0x0c, 0x1a, 0xfe, 0x01, 0xc7, 0xe1, 0xe2, 0x5b, 0x07, 0x31, 0x13, - 0x31, 0x7d, 0xa7, 0xa6, 0xca, 0x69, 0x3f, 0x05, 0x66, 0x32, 0xbe, 0x47, 0x95, 0x40, 0xe5, 0xf3, - 0x00, 0x7e, 0xf9, 0x24, 0x0d, 0xd7, 0xdc, 0x31, 0x9e, 0x8a, 0x28, 0xdd, 0x31, 0x46, 0x30, 0xee, - 0x60, 0x70, 0xe4, 0x76, 0x9f, 0xe1, 0x95, 0x29, 0x3c, 0x01, 0xab, 0x3a, 0x26, 0x10, 0xf3, 0x84, - 0x92, 0x5d, 0xc5, 0x0c, 0x82, 0x92, 0xa3, 0x83, 0xc8, 0x1d, 0xa8, 0xa1, 0x0b, 0x2a, 0xf6, 0xb5, - 0x81, 0xfb, 0xda, 0xd4, 0x7d, 0x54, 0xdc, 0x59, 0x1d, 0x49, 0x3f, 0x0b, 0x9c, 0xcd, 0x24, 0x07, - 0xba, 0xbd, 0x9e, 0x38, 0x42, 0x6d, 0x72, 0x77, 0x5a, 0x01, 0x98, 0x3e, 0x16, 0x0b, 0xc6, 0x11, - 0xe6, 0x10, 0xc1, 0x80, 0x91, 0x6b, 0x50, 0x61, 0xee, 0xd1, 0xc8, 0xf5, 0x7a, 0x98, 0x5d, 0xc8, - 0xbd, 0x34, 0x05, 0x63, 0x6d, 0xc8, 0xff, 0xa8, 0xe8, 0xe6, 0x71, 0x55, 0x0c, 0x18, 0x5b, 0x1b, - 0x55, 0x46, 0x66, 0x5a, 0xe0, 0x3b, 0x6a, 0x00, 0xc9, 0xdb, 0x78, 0x90, 0x15, 0xd3, 0xd6, 0x22, - 0x06, 0xca, 0xae, 0x88, 0x39, 0x0b, 0xa2, 0x95, 0xbf, 0x87, 0x0c, 0xc5, 0xe1, 0x98, 0xf6, 0x06, - 0xd4, 0x75, 0x30, 0xa9, 0x40, 0xe9, 0xd1, 0xc1, 0xce, 0x7e, 0xf3, 0x12, 0xa9, 0xc1, 0xf4, 0xe1, - 0xce, 0xe3, 0xc7, 0x7b, 0x3b, 0xdb, 0x4d, 0x8b, 0xd4, 0xa1, 0xa2, 0x92, 0xba, 0x0a, 0xac, 0xb4, - 0xb1, 0xb5, 0xb5, 0x73, 0xf0, 0x78, 0x67, 0xbb, 0x59, 0xb4, 0x63, 0x20, 0x1b, 0xbd, 0x9e, 0x68, - 0x45, 0x05, 0x09, 0x12, 0x7a, 0xb6, 0x0c, 0x7a, 0xce, 0xa1, 0xa9, 0x42, 0x3e, 0x4d, 0xbd, 0x74, - 0xe5, 0xed, 0x1d, 0xa8, 0x1d, 0x68, 0xf7, 0x87, 0x90, 0xbd, 0xe4, 0xcd, 0x21, 0xc1, 0x96, 0x1a, - 0x44, 0x1b, 0x4e, 0x41, 0x1f, 0x8e, 0xfd, 0x3b, 0x16, 0x4f, 0xd2, 0x57, 0xc3, 0xe7, 0x7d, 0xdb, - 0x50, 0x57, 0xd1, 0xaa, 0x24, 0x5f, 0xd3, 0x80, 0x31, 0x1c, 0x1c, 0x4a, 0x27, 0x38, 0x3e, 0x8e, - 0xa8, 0xcc, 0xae, 0x32, 0x60, 0x8c, 0x2f, 0x98, 0x6d, 0xc6, 0xec, 0x1c, 0x8f, 0xf7, 0x10, 0x89, - 0x2c, 0xab, 0x0c, 0x9c, 0x49, 0x79, 0x11, 0x90, 0x91, 0x79, 0x65, 0xaa, 0xac, 0xd2, 0x4a, 0xd3, - 0xab, 0xbc, 0x06, 0x15, 0xd5, 0xae, 0x29, 0xc0, 0x24, 0xa6, 0xaa, 0x67, 0x82, 0x12, 0xbd, 0x15, - 0x63, 0xd0, 0x5c, 0x68, 0x67, 0x2b, 0xc8, 0x2d, 0x20, 0xc7, 0x5e, 0x98, 0x46, 0x2f, 0x22, 0x7a, - 0x4e, 0x8d, 0xfd, 0x14, 0xe6, 0x25, 0x21, 0x69, 0xa6, 0x95, 0xb9, 0x89, 0xd6, 0x45, 0xec, 0x53, - 0xc8, 0xb2, 0x8f, 0xfd, 0x6f, 0x16, 0x4c, 0x8b, 0x9d, 0xce, 0xdc, 0x41, 0xe3, 0xfb, 0x6c, 0xc0, - 0x48, 0xcb, 0xb8, 0x7f, 0x82, 0xbc, 0x26, 0x84, 0x66, 0x46, 0x2c, 0x16, 0xf3, 0xc4, 0x22, 0x81, - 0xd2, 0xc8, 0x8d, 0x4f, 0xd0, 0x53, 0xaf, 0x3a, 0xf8, 0x9f, 0x34, 0x79, 0x5c, 0x89, 0x8b, 0x60, - 0x8c, 0x29, 0xe5, 0xdd, 0xb6, 0xe3, 0xda, 0x3e, 0x7b, 0xdb, 0xee, 0x2a, 0x54, 0x71, 0x00, 0x9d, - 0x24, 0x6c, 0x94, 0x00, 0x18, 0xe5, 0xf2, 0x02, 0xf2, 0xb5, 0x48, 0x05, 0x4f, 0x20, 0xf6, 0x22, - 0xdf, 0x79, 0xb1, 0x04, 0xea, 0x10, 0x5a, 0xa4, 0xf1, 0x26, 0xe0, 0x84, 0x22, 0xc4, 0x00, 0xd2, - 0x14, 0x21, 0x50, 0x1d, 0x55, 0x6f, 0xb7, 0xa1, 0xb5, 0x4d, 0x07, 0x34, 0xa6, 0x1b, 0x83, 0x41, - 0xba, 0xfd, 0x2b, 0x70, 0x39, 0xa7, 0x4e, 0x58, 0xd3, 0x5f, 0x86, 0xc5, 0x0d, 0x9e, 0xf2, 0xf8, - 0xd3, 0x4a, 0xe3, 0xb1, 0x5b, 0xb0, 0x94, 0x6e, 0x52, 0x74, 0x76, 0x1f, 0xe6, 0xb6, 0xe9, 0xd1, - 0xb8, 0xbf, 0x47, 0x4f, 0x93, 0x8e, 0x08, 0x94, 0xa2, 0x93, 0xe0, 0x4c, 0x30, 0x26, 0xfe, 0x27, - 0xaf, 0x01, 0x0c, 0x18, 0x4e, 0x27, 0x1a, 0xd1, 0xae, 0xbc, 0xf2, 0x81, 0x90, 0xc3, 0x11, 0xed, - 0xda, 0xef, 0x00, 0xd1, 0xdb, 0x11, 0xeb, 0xc5, 0xb4, 0xe0, 0xf8, 0xa8, 0x13, 0x9d, 0x47, 0x31, - 0x1d, 0xca, 0xbb, 0x2c, 0x3a, 0xc8, 0xbe, 0x09, 0xf5, 0x03, 0xf7, 0xdc, 0xa1, 0x1f, 0x8a, 0xab, - 0x87, 0xcb, 0x30, 0x3d, 0x72, 0xcf, 0x99, 0x98, 0x52, 0xf1, 0x2c, 0xac, 0xb6, 0xff, 0xa5, 0x00, - 0x53, 0x1c, 0x93, 0xb5, 0xda, 0xa3, 0x51, 0xec, 0xf9, 0x48, 0x58, 0xb2, 0x55, 0x0d, 0x94, 0x21, - 0xe5, 0x42, 0x0e, 0x29, 0x0b, 0x6f, 0x4f, 0xa6, 0xcf, 0x0b, 0x7a, 0x35, 0x60, 0x8c, 0xb8, 0x92, - 0x7c, 0x3a, 0x1e, 0x50, 0x49, 0x00, 0xa9, 0xd0, 0x67, 0xa2, 0x6b, 0xf9, 0xf8, 0x24, 0x97, 0x0a, - 0xca, 0xd5, 0x41, 0xb9, 0x1a, 0x7d, 0x9a, 0x13, 0x78, 0x46, 0xa3, 0x67, 0x34, 0x77, 0xe5, 0x15, - 0x34, 0x37, 0x77, 0x01, 0x5f, 0xa6, 0xb9, 0xe1, 0x15, 0x34, 0xb7, 0x4d, 0xa0, 0x89, 0xf7, 0xf2, - 0x98, 0x6d, 0x28, 0x69, 0xf7, 0xdb, 0x16, 0x34, 0x05, 0x15, 0xa9, 0x3a, 0xf2, 0x86, 0x61, 0x03, - 0xe7, 0x26, 0xa6, 0x5f, 0x87, 0x19, 0xb4, 0x4c, 0x55, 0x8c, 0x57, 0x04, 0xa4, 0x0d, 0x20, 0x9b, - 0x87, 0x3c, 0x3f, 0x1e, 0x7a, 0x03, 0xb1, 0x29, 0x3a, 0x48, 0x86, 0x89, 0x43, 0x57, 0xe4, 0x95, - 0x59, 0x8e, 0x2a, 0xdb, 0x7f, 0x62, 0xc1, 0x9c, 0x36, 0x60, 0x41, 0x85, 0xf7, 0x40, 0x72, 0x03, - 0x0f, 0xf8, 0x72, 0xce, 0x5d, 0x36, 0xd9, 0x26, 0xf9, 0xcc, 0x40, 0xc6, 0xcd, 0x74, 0xcf, 0x71, - 0x80, 0xd1, 0x78, 0x28, 0x84, 0xa8, 0x0e, 0x62, 0x84, 0x74, 0x46, 0xe9, 0x33, 0x85, 0xc2, 0xc5, - 0xb8, 0x01, 0xc3, 0xa8, 0x1a, 0xb3, 0xa8, 0x15, 0x52, 0x49, 0x44, 0xd5, 0x74, 0xa0, 0xfd, 0x57, - 0x16, 0xcc, 0x73, 0xd7, 0x48, 0x38, 0x9e, 0xea, 0x06, 0xd2, 0x14, 0xf7, 0x05, 0x39, 0x47, 0xee, - 0x5e, 0x72, 0x44, 0x99, 0x7c, 0xf6, 0x15, 0xdd, 0x39, 0x95, 0xec, 0x36, 0x61, 0x2f, 0x8a, 0x79, - 0x7b, 0xf1, 0x92, 0x95, 0xce, 0x0b, 0x70, 0x96, 0x73, 0x03, 0x9c, 0x9b, 0xd3, 0x50, 0x8e, 0xba, - 0xc1, 0x88, 0xda, 0x4b, 0xb0, 0x60, 0x4e, 0x4e, 0x88, 0xa0, 0xef, 0x58, 0xd0, 0xba, 0xcf, 0x0f, - 0x02, 0x3c, 0xbf, 0xbf, 0xeb, 0x45, 0x71, 0x10, 0xaa, 0x8b, 0x9a, 0xd7, 0x00, 0xa2, 0xd8, 0x0d, - 0x63, 0x9e, 0xd2, 0x2c, 0x02, 0x8b, 0x09, 0x84, 0x8d, 0x91, 0xfa, 0x3d, 0x5e, 0xcb, 0xf7, 0x46, - 0x95, 0x33, 0x36, 0x84, 0x70, 0xde, 0x0c, 0x4d, 0x7c, 0x83, 0x27, 0x7f, 0x32, 0x5b, 0x81, 0x9e, - 0xa2, 0x5c, 0xe7, 0x5e, 0x51, 0x0a, 0x6a, 0xff, 0xa5, 0x05, 0xb3, 0xc9, 0x20, 0xf1, 0x58, 0xd4, - 0x94, 0x0e, 0x42, 0xfd, 0x26, 0xd2, 0x41, 0x86, 0x3c, 0x3d, 0xa6, 0x8f, 0xc5, 0xd8, 0x34, 0x08, - 0x72, 0xac, 0x28, 0x05, 0x63, 0x69, 0xe0, 0xe8, 0x20, 0x9e, 0xca, 0xc5, 0x2c, 0x01, 0x61, 0xd5, - 0x88, 0x12, 0x66, 0xa4, 0x0f, 0x63, 0xfc, 0x8a, 0x07, 0x67, 0x65, 0x51, 0xaa, 0xd2, 0x69, 0x84, - 0xa2, 0x2a, 0xd5, 0x0f, 0x55, 0x2a, 0x7c, 0x7d, 0x64, 0xd9, 0xfe, 0x25, 0x0b, 0x2e, 0xe7, 0x2c, - 0xbc, 0xe0, 0x9a, 0x6d, 0x98, 0x3b, 0x56, 0x95, 0x72, 0x71, 0x38, 0xeb, 0x2c, 0xc9, 0x43, 0x3b, - 0x73, 0x41, 0x9c, 0xec, 0x07, 0xca, 0x2e, 0xe2, 0xcb, 0x6d, 0x24, 0x4b, 0x66, 0x2b, 0xec, 0x03, - 0x68, 0xef, 0x3c, 0x67, 0x4c, 0xb8, 0xa5, 0xbf, 0x39, 0x22, 0x69, 0xe1, 0x4e, 0x46, 0xc8, 0x5c, - 0xec, 0x68, 0x1f, 0xc3, 0x8c, 0xd1, 0x16, 0xf9, 0xf4, 0xab, 0x36, 0x92, 0x0a, 0x4f, 0x63, 0x89, - 0x3f, 0x9a, 0x22, 0x53, 0x36, 0x35, 0x90, 0x7d, 0x0a, 0xb3, 0xef, 0x8d, 0x07, 0xb1, 0x97, 0x3c, - 0xa0, 0x42, 0x3e, 0x2b, 0x3e, 0xc2, 0x26, 0xe4, 0xd2, 0xe5, 0x76, 0xa5, 0xe3, 0xb1, 0x15, 0x1b, - 0xb2, 0x96, 0x3a, 0xd9, 0x1e, 0xb3, 0x15, 0xf6, 0x65, 0x58, 0x4e, 0xba, 0xe4, 0x6b, 0x27, 0x05, - 0xf5, 0x77, 0x2d, 0x9e, 0xed, 0x60, 0xbe, 0xe7, 0x42, 0x1e, 0xc0, 0x7c, 0xe4, 0xf9, 0xfd, 0x01, - 0xd5, 0xdb, 0x89, 0xc4, 0x4a, 0x2c, 0x9a, 0xc3, 0x13, 0x6f, 0xbe, 0x38, 0x79, 0x5f, 0x30, 0x02, - 0xc9, 0x1f, 0x68, 0x42, 0x20, 0xa9, 0x25, 0xc9, 0x9b, 0xc0, 0x17, 0xa1, 0x61, 0x76, 0x46, 0xee, - 0x8a, 0x6c, 0xcb, 0x64, 0x64, 0x7a, 0x2c, 0xdb, 0xa4, 0x0c, 0x03, 0xd3, 0xfe, 0x96, 0x05, 0x2d, - 0x87, 0x32, 0x32, 0xa6, 0x5a, 0xa7, 0x82, 0x7a, 0xee, 0x65, 0x9a, 0x9d, 0x3c, 0x61, 0x95, 0xc5, - 0x29, 0xe7, 0x7a, 0x6b, 0xe2, 0xa6, 0xec, 0x5e, 0xca, 0x99, 0xd5, 0x66, 0x05, 0xa6, 0xc4, 0xfc, - 0x96, 0x61, 0x51, 0x0c, 0x49, 0x0e, 0x27, 0x09, 0x9a, 0x1a, 0x9d, 0x1a, 0x41, 0xd3, 0x23, 0x68, - 0xf1, 0x1b, 0xb4, 0xfa, 0x3c, 0x92, 0xdc, 0x06, 0xbe, 0x1d, 0x51, 0x47, 0xbf, 0x4c, 0x6b, 0x02, - 0x19, 0xc9, 0xf2, 0x61, 0x71, 0x1c, 0x7e, 0x0a, 0xad, 0x83, 0xd6, 0x5e, 0x40, 0x4d, 0xbb, 0x8f, - 0x4c, 0x96, 0x61, 0xfe, 0xe9, 0xc3, 0xc7, 0xfb, 0x3b, 0x87, 0x87, 0x9d, 0x83, 0x27, 0x9b, 0x5f, - 0xda, 0xf9, 0x6a, 0x67, 0x77, 0xe3, 0x70, 0xb7, 0x79, 0x89, 0x2c, 0x01, 0xd9, 0xdf, 0x39, 0x7c, - 0xbc, 0xb3, 0x6d, 0xc0, 0x2d, 0x72, 0x0d, 0xda, 0x4f, 0xf6, 0x9f, 0x1c, 0xee, 0x6c, 0x77, 0xf2, - 0xbe, 0x2b, 0x90, 0xd7, 0xe0, 0xb2, 0xa8, 0xcf, 0xf9, 0xbc, 0x78, 0xe7, 0x5b, 0x45, 0x68, 0xf0, - 0xe4, 0x0d, 0xfe, 0x9c, 0x10, 0x0d, 0xc9, 0x7b, 0x30, 0x2d, 0xde, 0xa5, 0x22, 0x72, 0x5f, 0xcc, - 0x97, 0xb0, 0xda, 0x4b, 0x69, 0xb0, 0x58, 0xcc, 0xf9, 0xff, 0xf7, 0xa3, 0xbf, 0xfb, 0x95, 0xc2, - 0x0c, 0xa9, 0xad, 0x9f, 0xbe, 0xbd, 0xde, 0xa7, 0x7e, 0xc4, 0xda, 0xf8, 0x3a, 0x40, 0xf2, 0xda, - 0x12, 0x69, 0x29, 0xdf, 0x2d, 0xf5, 0x14, 0x55, 0xfb, 0x72, 0x4e, 0x8d, 0x68, 0xf7, 0x32, 0xb6, - 0x3b, 0x6f, 0x37, 0x58, 0xbb, 0x9e, 0xef, 0xc5, 0xfc, 0xe5, 0xa5, 0x77, 0xad, 0x35, 0xd2, 0x83, - 0xba, 0xfe, 0x0e, 0x12, 0x91, 0x01, 0xe4, 0x9c, 0x97, 0x9c, 0xda, 0x57, 0x72, 0xeb, 0x24, 0x21, - 0x60, 0x1f, 0x8b, 0x76, 0x93, 0xf5, 0x31, 0x46, 0x8c, 0xa4, 0x97, 0x01, 0x67, 0x8f, 0xe4, 0xb9, - 0x23, 0x72, 0x55, 0xa3, 0xd8, 0xcc, 0x63, 0x4b, 0xed, 0xd7, 0x26, 0xd4, 0x8a, 0xbe, 0x5e, 0xc3, - 0xbe, 0x96, 0x6d, 0xc2, 0xfa, 0xea, 0x22, 0x8e, 0x7c, 0x6c, 0xe9, 0x5d, 0x6b, 0xed, 0xce, 0xaf, - 0xdd, 0x80, 0xaa, 0x3a, 0x2c, 0x22, 0x1f, 0xc0, 0x8c, 0x91, 0x5d, 0x43, 0xe4, 0x34, 0xf2, 0x92, - 0x71, 0xda, 0x57, 0xf3, 0x2b, 0x45, 0xc7, 0xd7, 0xb0, 0xe3, 0x16, 0x59, 0x62, 0x1d, 0x8b, 0xf4, - 0x94, 0x75, 0xcc, 0x13, 0xe3, 0x97, 0x3e, 0x9e, 0x69, 0x62, 0x80, 0x77, 0x76, 0x35, 0xcd, 0x99, - 0x46, 0x6f, 0xaf, 0x4d, 0xa8, 0x15, 0xdd, 0x5d, 0xc5, 0xee, 0x96, 0xc8, 0x82, 0xde, 0x9d, 0x3a, - 0xc4, 0xa1, 0x78, 0x53, 0x49, 0x7f, 0x29, 0x88, 0xbc, 0xa6, 0x08, 0x2b, 0xef, 0x05, 0x21, 0x45, - 0x22, 0xd9, 0x67, 0x84, 0xec, 0x16, 0x76, 0x45, 0x08, 0x6e, 0x9f, 0xfe, 0x50, 0x10, 0x39, 0x82, - 0x9a, 0xf6, 0xba, 0x05, 0xb9, 0x3c, 0xf1, 0x25, 0x8e, 0x76, 0x3b, 0xaf, 0x2a, 0x6f, 0x2a, 0x7a, - 0xfb, 0xeb, 0x4c, 0xbf, 0x7f, 0x0d, 0xaa, 0xea, 0xbd, 0x04, 0xb2, 0xac, 0xbd, 0x5f, 0xa1, 0xbf, - 0xef, 0xd0, 0x6e, 0x65, 0x2b, 0xf2, 0x88, 0x4f, 0x6f, 0x9d, 0x11, 0xdf, 0x53, 0xa8, 0x69, 0x6f, - 0x22, 0xa8, 0x09, 0x64, 0xdf, 0x5d, 0x50, 0x13, 0xc8, 0x79, 0x42, 0xc1, 0x9e, 0xc3, 0x2e, 0x6a, - 0xa4, 0x8a, 0xf4, 0x1d, 0x3f, 0x0f, 0x22, 0xb2, 0x07, 0x8b, 0x42, 0xdc, 0x1d, 0xd1, 0x8f, 0xb3, - 0x0d, 0x39, 0x8f, 0x33, 0xdd, 0xb6, 0xc8, 0x3d, 0xa8, 0xc8, 0xa7, 0x2f, 0xc8, 0x52, 0xfe, 0x13, - 0x1e, 0xed, 0xe5, 0x0c, 0x5c, 0x48, 0xd3, 0xaf, 0x02, 0x24, 0x0f, 0x30, 0x28, 0x21, 0x91, 0x79, - 0xd0, 0x41, 0x51, 0x40, 0xf6, 0xb5, 0x06, 0x7b, 0x09, 0x27, 0xd8, 0x24, 0x28, 0x24, 0x7c, 0x7a, - 0x26, 0xef, 0x07, 0x7e, 0x03, 0x6a, 0xda, 0x1b, 0x0c, 0x6a, 0xf9, 0xb2, 0xef, 0x37, 0xa8, 0xe5, - 0xcb, 0x79, 0xb2, 0xc1, 0x6e, 0x63, 0xeb, 0x0b, 0xf6, 0x2c, 0x6b, 0x3d, 0xf2, 0xfa, 0xfe, 0x90, - 0x23, 0xb0, 0x0d, 0x3a, 0x81, 0x19, 0xe3, 0xa1, 0x05, 0xc5, 0xa1, 0x79, 0xcf, 0x38, 0x28, 0x0e, - 0xcd, 0x7d, 0x9b, 0x41, 0xd2, 0x99, 0x3d, 0xc7, 0xfa, 0x39, 0x45, 0x14, 0xad, 0xa7, 0xf7, 0xa1, - 0xa6, 0x3d, 0x9a, 0xa0, 0xe6, 0x92, 0x7d, 0x9f, 0x41, 0xcd, 0x25, 0xef, 0x8d, 0x85, 0x05, 0xec, - 0xa3, 0x61, 0x23, 0x29, 0xe0, 0xf5, 0x3a, 0xd6, 0xf6, 0x07, 0xd0, 0x30, 0x9f, 0x51, 0x50, 0xbc, - 0x9f, 0xfb, 0x20, 0x83, 0xe2, 0xfd, 0x09, 0x6f, 0x2f, 0x08, 0x92, 0x5e, 0x9b, 0x57, 0x9d, 0xac, - 0x7f, 0x24, 0x92, 0x48, 0x5e, 0x90, 0x2f, 0x33, 0x01, 0x27, 0xee, 0x3b, 0x92, 0x65, 0x8d, 0x6a, - 0xf5, 0x5b, 0x91, 0x8a, 0x5f, 0x32, 0x57, 0x23, 0x4d, 0x62, 0xe6, 0x17, 0x04, 0x51, 0x6b, 0xe1, - 0xbd, 0x47, 0x4d, 0x6b, 0xe9, 0x57, 0x23, 0x35, 0xad, 0x65, 0x5c, 0x8f, 0x4c, 0x6b, 0xad, 0xd8, - 0x63, 0x6d, 0xf8, 0x30, 0x9b, 0xca, 0x00, 0x56, 0x5c, 0x91, 0x7f, 0x65, 0xa2, 0x7d, 0xed, 0xe5, - 0x89, 0xc3, 0xa6, 0x04, 0x91, 0x42, 0x70, 0x5d, 0x5e, 0x50, 0xf9, 0x5f, 0x50, 0xd7, 0xaf, 0xac, - 0x13, 0x9d, 0x95, 0xd3, 0x3d, 0x5d, 0xc9, 0xad, 0x33, 0x37, 0x97, 0xd4, 0xf5, 0x6e, 0xc8, 0x57, - 0x60, 0x49, 0xb1, 0xba, 0x9e, 0x54, 0x1a, 0x91, 0xd7, 0x73, 0x52, 0x4d, 0x75, 0x23, 0xa8, 0x7d, - 0x79, 0x62, 0x2e, 0xea, 0x6d, 0x8b, 0x11, 0x8d, 0x79, 0x17, 0x38, 0x51, 0x18, 0x79, 0x57, 0xa0, - 0x13, 0x85, 0x91, 0x7b, 0x81, 0x58, 0x12, 0x0d, 0x99, 0x37, 0xd6, 0x88, 0x9f, 0xf3, 0x91, 0xf7, - 0x61, 0x56, 0x4b, 0xdb, 0x3f, 0x3c, 0xf7, 0xbb, 0x8a, 0x01, 0xb2, 0xf7, 0xbb, 0xda, 0x79, 0x26, - 0xbe, 0xbd, 0x8c, 0xed, 0xcf, 0xd9, 0xc6, 0xe2, 0x30, 0xe2, 0xdf, 0x82, 0x9a, 0x7e, 0x25, 0xe0, - 0x25, 0xed, 0x2e, 0x6b, 0x55, 0xfa, 0xf5, 0xa4, 0xdb, 0x16, 0xf9, 0x0d, 0x0b, 0xea, 0x46, 0x82, - 0xbd, 0x71, 0x9a, 0x9d, 0x6a, 0xa7, 0xa5, 0xd7, 0xe9, 0x0d, 0xd9, 0x0e, 0x0e, 0x72, 0x6f, 0xed, - 0x8b, 0xc6, 0x22, 0x7c, 0x64, 0xc4, 0x71, 0x6e, 0xa5, 0x9f, 0xdb, 0x7a, 0x91, 0x46, 0xd0, 0xef, - 0xc0, 0xbd, 0xb8, 0x6d, 0x91, 0xef, 0x59, 0xd0, 0x30, 0xa3, 0x8f, 0x6a, 0xab, 0x72, 0xe3, 0x9c, - 0x6a, 0xab, 0x26, 0x84, 0x2c, 0xdf, 0xc7, 0x51, 0x3e, 0x5e, 0x73, 0x8c, 0x51, 0x8a, 0x5b, 0xe2, - 0x3f, 0xd9, 0x68, 0xc9, 0xbb, 0xfc, 0x45, 0x3e, 0x19, 0x12, 0x27, 0x9a, 0xd6, 0x48, 0x6f, 0xaf, - 0xfe, 0x8a, 0xdc, 0xaa, 0x75, 0xdb, 0x22, 0xdf, 0xe0, 0xcf, 0x4c, 0x89, 0x6f, 0x91, 0x4a, 0x5e, - 0xf5, 0x7b, 0xfb, 0x3a, 0xce, 0xe9, 0x9a, 0x7d, 0xd9, 0x98, 0x53, 0x5a, 0x1f, 0x6f, 0xf0, 0xd1, - 0x89, 0x07, 0xe0, 0x12, 0x85, 0x92, 0x79, 0x14, 0x6e, 0xf2, 0x20, 0x87, 0x7c, 0x90, 0x02, 0xdd, - 0x20, 0xe5, 0x57, 0x6c, 0xc6, 0x5e, 0xc3, 0xb1, 0x5e, 0xb7, 0x5f, 0x9f, 0x38, 0xd6, 0x75, 0x8c, - 0x21, 0xb2, 0x11, 0x1f, 0x00, 0x24, 0xc7, 0x57, 0x24, 0x75, 0x7c, 0xa2, 0x18, 0x3c, 0x7b, 0xc2, - 0x65, 0xf2, 0x8b, 0x3c, 0x65, 0x61, 0x2d, 0x7e, 0x8d, 0x8b, 0xab, 0x87, 0xf2, 0xe0, 0x45, 0x37, - 0x4a, 0xcc, 0x73, 0x26, 0xc3, 0x28, 0x49, 0xb7, 0x6f, 0x08, 0x2b, 0x75, 0x8a, 0xf3, 0x04, 0x66, - 0xf6, 0x82, 0xe0, 0xd9, 0x78, 0xa4, 0x8e, 0xa2, 0xcd, 0xf0, 0xfe, 0xae, 0x1b, 0x9d, 0xb4, 0x53, - 0xb3, 0xb0, 0x57, 0xb0, 0xa9, 0x36, 0x69, 0x69, 0x4d, 0xad, 0x7f, 0x94, 0x1c, 0x8f, 0xbd, 0x20, - 0x2e, 0xcc, 0x29, 0x19, 0xa8, 0x06, 0xde, 0x36, 0x9b, 0x31, 0x24, 0x5f, 0xba, 0x0b, 0xc3, 0x7a, - 0x96, 0xa3, 0x5d, 0x8f, 0x64, 0x9b, 0xb7, 0x2d, 0x72, 0x00, 0xf5, 0x6d, 0xda, 0x0d, 0x7a, 0x54, - 0xc4, 0xc8, 0xe7, 0x93, 0x81, 0xab, 0xe0, 0x7a, 0x7b, 0xc6, 0x00, 0x9a, 0x7a, 0x61, 0xe4, 0x9e, - 0x87, 0xf4, 0xc3, 0xf5, 0x8f, 0x44, 0xf4, 0xfd, 0x85, 0xd4, 0x0b, 0xf2, 0x78, 0xc2, 0xd0, 0x0b, - 0xa9, 0xf3, 0x0c, 0x43, 0x2f, 0x64, 0xce, 0x33, 0x8c, 0xa5, 0x96, 0xc7, 0x23, 0x64, 0x00, 0x73, - 0x99, 0x23, 0x10, 0xa5, 0x12, 0x26, 0x1d, 0x9c, 0xb4, 0x57, 0x26, 0x23, 0x98, 0xbd, 0xad, 0x99, - 0xbd, 0x1d, 0xc2, 0xcc, 0x36, 0xe5, 0x8b, 0xc5, 0x33, 0xe5, 0x52, 0xb7, 0x34, 0xf4, 0x3c, 0xbc, - 0xb4, 0x00, 0xc7, 0x3a, 0x53, 0xf1, 0x63, 0x9a, 0x1a, 0xf9, 0x1a, 0xd4, 0x1e, 0xd0, 0x58, 0xa6, - 0xc6, 0x29, 0xd3, 0x33, 0x95, 0x2b, 0xd7, 0xce, 0xc9, 0xac, 0x33, 0x69, 0x06, 0x5b, 0x5b, 0xa7, - 0xbd, 0x3e, 0xe5, 0xc2, 0xa9, 0xe3, 0xf5, 0x5e, 0x90, 0xff, 0x89, 0x8d, 0xab, 0x0c, 0xde, 0x25, - 0x2d, 0x2f, 0x4a, 0x6f, 0x7c, 0x36, 0x05, 0xcf, 0x6b, 0xd9, 0x0f, 0x7a, 0x54, 0x33, 0x81, 0x7c, - 0xa8, 0x69, 0x89, 0xe7, 0x8a, 0x81, 0xb2, 0xf7, 0x04, 0x14, 0x03, 0xe5, 0xe4, 0xa9, 0xdb, 0xab, - 0xd8, 0x8f, 0x4d, 0x56, 0x92, 0x7e, 0x78, 0x6e, 0x7a, 0xd2, 0xd3, 0xfa, 0x47, 0xee, 0x30, 0x7e, - 0x41, 0x9e, 0xe2, 0x53, 0x11, 0x7a, 0xfa, 0x5f, 0x62, 0x4b, 0xa7, 0x33, 0x05, 0xd5, 0x62, 0x69, - 0x55, 0xa6, 0x7d, 0xcd, 0xbb, 0x42, 0x4b, 0xe9, 0xb3, 0x00, 0x87, 0x71, 0x30, 0xda, 0x76, 0xe9, - 0x30, 0xf0, 0x13, 0x59, 0x9b, 0x24, 0xaa, 0x25, 0xf2, 0x4b, 0xcb, 0x56, 0x23, 0x4f, 0x35, 0xe7, - 0xc3, 0xc8, 0x9e, 0x94, 0xc4, 0x35, 0x31, 0x97, 0x4d, 0x2d, 0x48, 0x4e, 0x3e, 0xdb, 0x6d, 0x8b, - 0x6c, 0x00, 0x24, 0x67, 0x60, 0xca, 0x95, 0xc8, 0x1c, 0xaf, 0x29, 0xb1, 0x97, 0x73, 0x60, 0x76, - 0x00, 0xd5, 0xe4, 0x50, 0x65, 0x39, 0xb9, 0x1b, 0x61, 0x1c, 0xc1, 0x28, 0x0d, 0x9e, 0x39, 0xea, - 0xb0, 0x9b, 0xb8, 0x54, 0x40, 0x2a, 0x6c, 0xa9, 0xf0, 0xfc, 0xc2, 0x83, 0x79, 0x3e, 0x40, 0x65, - 0x8e, 0x60, 0xea, 0x95, 0x9c, 0x49, 0xce, 0x71, 0x83, 0xe2, 0xe6, 0xdc, 0x68, 0xbd, 0x11, 0x11, - 0x61, 0xd4, 0xca, 0xd3, 0xbe, 0x98, 0x68, 0x1e, 0xc2, 0x5c, 0x26, 0x9c, 0xac, 0x58, 0x7a, 0x52, - 0x84, 0x5f, 0xb1, 0xf4, 0xc4, 0x48, 0xb4, 0xbd, 0x88, 0x5d, 0xce, 0xda, 0x80, 0x1e, 0xd0, 0x99, - 0x17, 0x77, 0x4f, 0x58, 0x77, 0xdf, 0xb5, 0x60, 0x3e, 0x27, 0x5a, 0x4c, 0xde, 0x90, 0xce, 0xf4, - 0xc4, 0x48, 0x72, 0x3b, 0x37, 0x98, 0x68, 0x1f, 0x62, 0x3f, 0xef, 0x91, 0x2f, 0x19, 0x8a, 0x8d, - 0xc7, 0xf1, 0x04, 0x67, 0xbe, 0xd4, 0xa8, 0xc8, 0xb5, 0x28, 0x3e, 0x84, 0x65, 0x3e, 0x90, 0x8d, - 0xc1, 0x20, 0x15, 0xe8, 0xbc, 0x96, 0x79, 0x74, 0xdb, 0x08, 0xe0, 0xb6, 0x27, 0x3f, 0xca, 0x3d, - 0xc1, 0x5c, 0xe5, 0x43, 0x25, 0x63, 0x68, 0xa6, 0x83, 0x87, 0x64, 0x72, 0x5b, 0xed, 0xd7, 0x0d, - 0xb7, 0x30, 0x1b, 0x70, 0xb4, 0x3f, 0x89, 0x9d, 0xbd, 0x6e, 0xb7, 0xf3, 0xd6, 0x85, 0x7b, 0x8a, - 0x6c, 0x3f, 0xfe, 0x8f, 0x8a, 0x74, 0xa6, 0xe6, 0x29, 0x3b, 0x98, 0x14, 0x9a, 0x55, 0x8e, 0x69, - 0x7e, 0xa0, 0xf4, 0x06, 0x76, 0xbf, 0x62, 0x5f, 0xc9, 0xeb, 0x3e, 0xe4, 0x9f, 0x70, 0x17, 0x75, - 0x39, 0xcd, 0xd7, 0x72, 0x04, 0x2b, 0x79, 0xfb, 0x3d, 0xd1, 0xd7, 0x48, 0xad, 0xf5, 0xa5, 0xdb, - 0xd6, 0xe6, 0xcd, 0xf7, 0x3f, 0xd9, 0xf7, 0xe2, 0x93, 0xf1, 0xd1, 0xad, 0x6e, 0x30, 0x5c, 0x1f, - 0xc8, 0x10, 0x99, 0x48, 0xf3, 0x5d, 0x1f, 0xf8, 0xbd, 0x75, 0xfc, 0xfe, 0x68, 0x0a, 0xdf, 0xf0, - 0xff, 0xf4, 0xbf, 0x07, 0x00, 0x00, 0xff, 0xff, 0x5d, 0xb7, 0x1a, 0xb7, 0xf5, 0x5f, 0x00, 0x00, +var fileDescriptor_rpc_854431eb46daab93 = []byte{ + // 7755 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x7d, 0x5b, 0x6c, 0x24, 0xd9, + 0x59, 0xb0, 0xab, 0x2f, 0x76, 0xf7, 0xd7, 0x6d, 0xbb, 0x7d, 0x7c, 0xeb, 0xe9, 0x99, 0x9d, 0xf5, + 0x56, 0x26, 0x33, 0x8e, 0x77, 0xff, 0xf1, 0xec, 0x24, 0xd9, 0x4c, 0x76, 0xfe, 0xfc, 0xf9, 0x7d, + 0x9b, 0xf1, 0x64, 0xbd, 0x1e, 0xa7, 0x3c, 0x93, 0xf9, 0x77, 0x93, 0x5f, 0x9d, 0x72, 0xf7, 0x71, + 0xbb, 0x76, 0xba, 0xab, 0x7a, 0xab, 0xaa, 0xed, 0x71, 0x96, 0x41, 0x08, 0x21, 0x40, 0x08, 0x84, + 0x02, 0x02, 0x11, 0x04, 0x42, 0x4a, 0x90, 0x20, 0xe2, 0x89, 0x87, 0x20, 0x24, 0x08, 0xaf, 0x48, + 0x91, 0x10, 0x42, 0x79, 0x44, 0x02, 0x21, 0x78, 0x41, 0x3c, 0x20, 0x90, 0x78, 0x44, 0x42, 0xe7, + 0x3b, 0x97, 0x3a, 0xa7, 0xaa, 0x7a, 0x3c, 0x9b, 0x04, 0x9e, 0xdc, 0xe7, 0x3b, 0x5f, 0x9d, 0xeb, + 0x77, 0x3f, 0xdf, 0x39, 0x86, 0x6a, 0x38, 0xec, 0xdc, 0x1c, 0x86, 0x41, 0x1c, 0x90, 0x72, 0xdf, + 0x0f, 0x87, 0x9d, 0xd6, 0x95, 0x5e, 0x10, 0xf4, 0xfa, 0x74, 0xdd, 0x1d, 0x7a, 0xeb, 0xae, 0xef, + 0x07, 0xb1, 0x1b, 0x7b, 0x81, 0x1f, 0x71, 0x24, 0xfb, 0xeb, 0x30, 0x73, 0x9f, 0xfa, 0x87, 0x94, + 0x76, 0x1d, 0xfa, 0xe1, 0x88, 0x46, 0x31, 0x79, 0x1d, 0xe6, 0x5c, 0xfa, 0x0d, 0x4a, 0xbb, 0xed, + 0xa1, 0x1b, 0x45, 0xc3, 0x93, 0xd0, 0x8d, 0x68, 0xd3, 0x5a, 0xb1, 0x56, 0xeb, 0x4e, 0x83, 0x57, + 0x1c, 0x28, 0x38, 0x79, 0x0d, 0xea, 0x11, 0x43, 0xa5, 0x7e, 0x1c, 0x06, 0xc3, 0xf3, 0x66, 0x01, + 0xf1, 0x6a, 0x0c, 0xb6, 0xc3, 0x41, 0x76, 0x1f, 0x66, 0x55, 0x0f, 0xd1, 0x30, 0xf0, 0x23, 0x4a, + 0x6e, 0xc1, 0x42, 0xc7, 0x1b, 0x9e, 0xd0, 0xb0, 0x8d, 0x1f, 0x0f, 0x7c, 0x3a, 0x08, 0x7c, 0xaf, + 0xd3, 0xb4, 0x56, 0x8a, 0xab, 0x55, 0x87, 0xf0, 0x3a, 0xf6, 0xc5, 0xbb, 0xa2, 0x86, 0xdc, 0x80, + 0x59, 0xea, 0x73, 0x38, 0xed, 0xe2, 0x57, 0xa2, 0xab, 0x99, 0x04, 0xcc, 0x3e, 0xb0, 0x7f, 0xb1, + 0x00, 0x73, 0x0f, 0x7c, 0x2f, 0x7e, 0xe2, 0xf6, 0xfb, 0x34, 0x96, 0x73, 0xba, 0x01, 0xb3, 0x67, + 0x08, 0xc0, 0x39, 0x9d, 0x05, 0x61, 0x57, 0xcc, 0x68, 0x86, 0x83, 0x0f, 0x04, 0x74, 0xec, 0xc8, + 0x0a, 0x63, 0x47, 0x96, 0xbb, 0x5c, 0xc5, 0x31, 0xcb, 0x75, 0x03, 0x66, 0x43, 0xda, 0x09, 0x4e, + 0x69, 0x78, 0xde, 0x3e, 0xf3, 0xfc, 0x6e, 0x70, 0xd6, 0x2c, 0xad, 0x58, 0xab, 0x65, 0x67, 0x46, + 0x82, 0x9f, 0x20, 0x94, 0x6c, 0xc2, 0x6c, 0xe7, 0xc4, 0xf5, 0x7d, 0xda, 0x6f, 0x1f, 0xb9, 0x9d, + 0xa7, 0xa3, 0x61, 0xd4, 0x2c, 0xaf, 0x58, 0xab, 0xb5, 0xdb, 0x97, 0x6e, 0xe2, 0xae, 0xde, 0xdc, + 0x3a, 0x71, 0xfd, 0x4d, 0xac, 0x39, 0xf4, 0xdd, 0x61, 0x74, 0x12, 0xc4, 0xce, 0x8c, 0xf8, 0x82, + 0x83, 0x23, 0x7b, 0x01, 0x88, 0xbe, 0x12, 0x7c, 0xed, 0xed, 0x3f, 0xb2, 0x60, 0xfe, 0xb1, 0xdf, + 0x0f, 0x3a, 0x4f, 0x7f, 0xc4, 0x25, 0xca, 0x99, 0x43, 0xe1, 0x65, 0xe7, 0x50, 0xfc, 0xb8, 0x73, + 0x58, 0x82, 0x05, 0x73, 0xb0, 0x62, 0x16, 0x14, 0x16, 0xd9, 0xd7, 0x3d, 0x2a, 0x87, 0x25, 0xa7, + 0xf1, 0x29, 0x68, 0x74, 0x46, 0x61, 0x48, 0xfd, 0xcc, 0x3c, 0x66, 0x05, 0x5c, 0x4d, 0xe4, 0x35, + 0xa8, 0xfb, 0xf4, 0x2c, 0x41, 0x13, 0xb4, 0xeb, 0xd3, 0x33, 0x89, 0x62, 0x37, 0x61, 0x29, 0xdd, + 0x8d, 0x18, 0xc0, 0x3f, 0x58, 0x50, 0x7a, 0x1c, 0x3f, 0x0b, 0xc8, 0x4d, 0x28, 0xc5, 0xe7, 0x43, + 0xce, 0x21, 0x33, 0xb7, 0x89, 0x98, 0xda, 0x46, 0xb7, 0x1b, 0xd2, 0x28, 0x7a, 0x74, 0x3e, 0xa4, + 0x4e, 0xdd, 0xe5, 0x85, 0x36, 0xc3, 0x23, 0x4d, 0x98, 0x12, 0x65, 0xec, 0xb0, 0xea, 0xc8, 0x22, + 0xb9, 0x0a, 0xe0, 0x0e, 0x82, 0x91, 0x1f, 0xb7, 0x23, 0x37, 0xc6, 0xa5, 0x2a, 0x3a, 0x1a, 0x84, + 0x5c, 0x81, 0xea, 0xf0, 0x69, 0x3b, 0xea, 0x84, 0xde, 0x30, 0x46, 0xb2, 0xa9, 0x3a, 0x09, 0x80, + 0xbc, 0x0e, 0x95, 0x60, 0x14, 0x0f, 0x03, 0xcf, 0x8f, 0x05, 0xa9, 0xcc, 0x8a, 0xb1, 0x3c, 0x1c, + 0xc5, 0x07, 0x0c, 0xec, 0x28, 0x04, 0x72, 0x0d, 0xa6, 0x3b, 0x81, 0x7f, 0xec, 0x85, 0x03, 0x2e, + 0x0c, 0x9a, 0x93, 0xd8, 0x9b, 0x09, 0xb4, 0xbf, 0x55, 0x80, 0xda, 0xa3, 0xd0, 0xf5, 0x23, 0xb7, + 0xc3, 0x00, 0x6c, 0xe8, 0xf1, 0xb3, 0xf6, 0x89, 0x1b, 0x9d, 0xe0, 0x6c, 0xab, 0x8e, 0x2c, 0x92, + 0x25, 0x98, 0xe4, 0x03, 0xc5, 0x39, 0x15, 0x1d, 0x51, 0x22, 0x6f, 0xc0, 0x9c, 0x3f, 0x1a, 0xb4, + 0xcd, 0xbe, 0x8a, 0x48, 0x2d, 0xd9, 0x0a, 0xb6, 0x00, 0x47, 0x6c, 0xaf, 0x79, 0x17, 0x7c, 0x86, + 0x1a, 0x84, 0xd8, 0x50, 0x17, 0x25, 0xea, 0xf5, 0x4e, 0xf8, 0x34, 0xcb, 0x8e, 0x01, 0x63, 0x6d, + 0xc4, 0xde, 0x80, 0xb6, 0xa3, 0xd8, 0x1d, 0x0c, 0xc5, 0xb4, 0x34, 0x08, 0xd6, 0x07, 0xb1, 0xdb, + 0x6f, 0x1f, 0x53, 0x1a, 0x35, 0xa7, 0x44, 0xbd, 0x82, 0x90, 0xeb, 0x30, 0xd3, 0xa5, 0x51, 0xdc, + 0x16, 0x9b, 0x42, 0xa3, 0x66, 0x05, 0x59, 0x3f, 0x05, 0x65, 0x94, 0x71, 0x9f, 0xc6, 0xda, 0xea, + 0x44, 0x82, 0x02, 0xed, 0x3d, 0x20, 0x1a, 0x78, 0x9b, 0xc6, 0xae, 0xd7, 0x8f, 0xc8, 0x5b, 0x50, + 0x8f, 0x35, 0x64, 0x14, 0x75, 0x35, 0x45, 0x2e, 0xda, 0x07, 0x8e, 0x81, 0x67, 0xdf, 0x87, 0xca, + 0x3d, 0x4a, 0xf7, 0xbc, 0x81, 0x17, 0x93, 0x25, 0x28, 0x1f, 0x7b, 0xcf, 0x28, 0x27, 0xe8, 0xe2, + 0xee, 0x84, 0xc3, 0x8b, 0xa4, 0x05, 0x53, 0x43, 0x1a, 0x76, 0xa8, 0x5c, 0xfe, 0xdd, 0x09, 0x47, + 0x02, 0x36, 0xa7, 0xa0, 0xdc, 0x67, 0x1f, 0xdb, 0xff, 0x56, 0x80, 0xda, 0x21, 0xf5, 0x15, 0xa3, + 0x10, 0x28, 0xb1, 0x29, 0x09, 0xe6, 0xc0, 0xdf, 0xe4, 0x55, 0xa8, 0xe1, 0x34, 0xa3, 0x38, 0xf4, + 0xfc, 0x9e, 0xa0, 0x4f, 0x60, 0xa0, 0x43, 0x84, 0x90, 0x06, 0x14, 0xdd, 0x81, 0xa4, 0x4d, 0xf6, + 0x93, 0x31, 0xd1, 0xd0, 0x3d, 0x1f, 0x30, 0x7e, 0x53, 0xbb, 0x56, 0x77, 0x6a, 0x02, 0xb6, 0xcb, + 0xb6, 0xed, 0x26, 0xcc, 0xeb, 0x28, 0xb2, 0xf5, 0x32, 0xb6, 0x3e, 0xa7, 0x61, 0x8a, 0x4e, 0x6e, + 0xc0, 0xac, 0xc4, 0x0f, 0xf9, 0x60, 0x71, 0x1f, 0xab, 0xce, 0x8c, 0x00, 0xcb, 0x29, 0xac, 0x42, + 0xe3, 0xd8, 0xf3, 0xdd, 0x7e, 0xbb, 0xd3, 0x8f, 0x4f, 0xdb, 0x5d, 0xda, 0x8f, 0x5d, 0xdc, 0xd1, + 0xb2, 0x33, 0x83, 0xf0, 0xad, 0x7e, 0x7c, 0xba, 0xcd, 0xa0, 0xe4, 0x0d, 0xa8, 0x1e, 0x53, 0xda, + 0xc6, 0x95, 0x68, 0x56, 0x0c, 0xee, 0x90, 0xab, 0xeb, 0x54, 0x8e, 0xe5, 0x3a, 0xaf, 0x42, 0x23, + 0x18, 0xc5, 0xbd, 0xc0, 0xf3, 0x7b, 0x6d, 0x26, 0x8f, 0xda, 0x5e, 0xb7, 0x59, 0x5d, 0xb1, 0x56, + 0x4b, 0xce, 0x8c, 0x84, 0x33, 0xa9, 0xf0, 0xa0, 0x4b, 0x5e, 0x01, 0xc0, 0xbe, 0x79, 0xc3, 0xb0, + 0x62, 0xad, 0x4e, 0x3b, 0x55, 0x06, 0xc1, 0x86, 0xec, 0x3f, 0xb5, 0xa0, 0xce, 0xd7, 0x5c, 0x28, + 0xbe, 0x6b, 0x30, 0x2d, 0xa7, 0x46, 0xc3, 0x30, 0x08, 0x05, 0x1f, 0x99, 0x40, 0xb2, 0x06, 0x0d, + 0x09, 0x18, 0x86, 0xd4, 0x1b, 0xb8, 0x3d, 0x2a, 0x84, 0x53, 0x06, 0x4e, 0x6e, 0x27, 0x2d, 0x86, + 0xc1, 0x28, 0xa6, 0x42, 0xc4, 0xd6, 0xc5, 0xec, 0x1c, 0x06, 0x73, 0x4c, 0x14, 0xc6, 0x47, 0x39, + 0x7b, 0x66, 0xc0, 0xec, 0xef, 0x59, 0x40, 0xd8, 0xd0, 0x1f, 0x05, 0xbc, 0x09, 0xb1, 0xe4, 0xe9, + 0xed, 0xb6, 0x5e, 0x7a, 0xbb, 0x0b, 0xe3, 0xb6, 0x7b, 0x15, 0x26, 0x71, 0x58, 0x4c, 0x30, 0x14, + 0xd3, 0x43, 0xdf, 0x2c, 0x34, 0x2d, 0x47, 0xd4, 0x13, 0x1b, 0xca, 0x7c, 0x8e, 0xa5, 0x9c, 0x39, + 0xf2, 0x2a, 0xfb, 0xdb, 0x16, 0xd4, 0xb7, 0xb8, 0x0e, 0x41, 0xa1, 0x47, 0x6e, 0x01, 0x39, 0x1e, + 0xf9, 0x5d, 0xb6, 0x97, 0xf1, 0x33, 0xaf, 0xdb, 0x3e, 0x3a, 0x67, 0x5d, 0xe1, 0xb8, 0x77, 0x27, + 0x9c, 0x9c, 0x3a, 0xf2, 0x06, 0x34, 0x0c, 0x68, 0x14, 0x87, 0x7c, 0xf4, 0xbb, 0x13, 0x4e, 0xa6, + 0x86, 0x2d, 0x26, 0x13, 0xab, 0xa3, 0xb8, 0xed, 0xf9, 0x5d, 0xfa, 0x0c, 0xd7, 0x7f, 0xda, 0x31, + 0x60, 0x9b, 0x33, 0x50, 0xd7, 0xbf, 0xb3, 0x3f, 0x80, 0x8a, 0x14, 0xca, 0x28, 0x90, 0x52, 0xe3, + 0x72, 0x34, 0x08, 0x69, 0x41, 0xc5, 0x1c, 0x85, 0x53, 0xf9, 0x38, 0x7d, 0xdb, 0xff, 0x07, 0x1a, + 0x7b, 0x4c, 0x32, 0xfa, 0x9e, 0xdf, 0x13, 0x5a, 0x89, 0x89, 0xeb, 0xe1, 0xe8, 0xe8, 0x29, 0x3d, + 0x17, 0xf4, 0x27, 0x4a, 0x4c, 0x26, 0x9c, 0x04, 0x51, 0x2c, 0xfa, 0xc1, 0xdf, 0xf6, 0x5f, 0x5a, + 0x40, 0x76, 0xa2, 0xd8, 0x1b, 0xb8, 0x31, 0xbd, 0x47, 0x15, 0x21, 0x3c, 0x84, 0x3a, 0x6b, 0xed, + 0x51, 0xb0, 0xc1, 0xe5, 0x3e, 0x97, 0x67, 0xaf, 0x8b, 0x2d, 0xc9, 0x7e, 0x70, 0x53, 0xc7, 0x66, + 0xa6, 0xe1, 0xb9, 0x63, 0x34, 0xc0, 0x64, 0x4f, 0xec, 0x86, 0x3d, 0x1a, 0xa3, 0x52, 0x10, 0x26, + 0x05, 0x70, 0xd0, 0x56, 0xe0, 0x1f, 0xb7, 0xbe, 0x08, 0x73, 0x99, 0x36, 0x98, 0x40, 0x4a, 0xa6, + 0xc1, 0x7e, 0x92, 0x05, 0x28, 0x9f, 0xba, 0xfd, 0x11, 0x15, 0x9a, 0x88, 0x17, 0xde, 0x2e, 0xdc, + 0xb1, 0xec, 0x0e, 0xcc, 0x1b, 0xe3, 0x12, 0x3c, 0xd9, 0x84, 0x29, 0x26, 0x1b, 0x98, 0xce, 0x45, + 0xb9, 0xea, 0xc8, 0x22, 0xb9, 0x0d, 0x0b, 0xc7, 0x94, 0x86, 0x6e, 0x8c, 0xc5, 0xf6, 0x90, 0x86, + 0xb8, 0x27, 0xa2, 0xe5, 0xdc, 0x3a, 0xfb, 0x1f, 0x2d, 0x98, 0x65, 0x7c, 0xf3, 0xae, 0xeb, 0x9f, + 0xcb, 0xb5, 0xda, 0xcb, 0x5d, 0xab, 0x55, 0xb1, 0x56, 0x29, 0xec, 0x8f, 0xbb, 0x50, 0xc5, 0xf4, + 0x42, 0x91, 0x15, 0xa8, 0x1b, 0xc3, 0x2d, 0x73, 0x25, 0x17, 0xb9, 0xf1, 0x01, 0x0d, 0x37, 0xcf, + 0x63, 0xfa, 0xe3, 0x2f, 0xe5, 0x75, 0x68, 0x24, 0xc3, 0x16, 0xeb, 0x48, 0xa0, 0xc4, 0x08, 0x53, + 0x34, 0x80, 0xbf, 0xed, 0xdf, 0xb1, 0x38, 0xe2, 0x56, 0xe0, 0x29, 0x05, 0xc9, 0x10, 0x99, 0x1e, + 0x95, 0x88, 0xec, 0xf7, 0x58, 0x03, 0xe2, 0xc7, 0x9f, 0x2c, 0xb9, 0x04, 0x95, 0x88, 0xfa, 0xdd, + 0xb6, 0xdb, 0xef, 0xa3, 0x1e, 0xa9, 0x38, 0x53, 0xac, 0xbc, 0xd1, 0xef, 0xdb, 0x37, 0x60, 0x4e, + 0x1b, 0xdd, 0x0b, 0xe6, 0xb1, 0x0f, 0x64, 0xcf, 0x8b, 0xe2, 0xc7, 0x7e, 0x34, 0xd4, 0xf4, 0xcf, + 0x65, 0xa8, 0x0e, 0x3c, 0x1f, 0x47, 0xc6, 0x39, 0xb7, 0xec, 0x54, 0x06, 0x9e, 0xcf, 0xc6, 0x15, + 0x61, 0xa5, 0xfb, 0x4c, 0x54, 0x16, 0x44, 0xa5, 0xfb, 0x0c, 0x2b, 0xed, 0x3b, 0x30, 0x6f, 0xb4, + 0x27, 0xba, 0x7e, 0x0d, 0xca, 0xa3, 0xf8, 0x59, 0x20, 0xad, 0x83, 0x9a, 0xa0, 0x10, 0x66, 0x67, + 0x3a, 0xbc, 0xc6, 0xbe, 0x0b, 0x73, 0xfb, 0xf4, 0x4c, 0x30, 0xb2, 0x1c, 0xc8, 0xf5, 0x0b, 0x6d, + 0x50, 0xac, 0xb7, 0x6f, 0x02, 0xd1, 0x3f, 0x4e, 0x18, 0x40, 0x5a, 0xa4, 0x96, 0x61, 0x91, 0xda, + 0xd7, 0x81, 0x1c, 0x7a, 0x3d, 0xff, 0x5d, 0x1a, 0x45, 0x6e, 0x4f, 0xb1, 0x7e, 0x03, 0x8a, 0x83, + 0xa8, 0x27, 0x44, 0x15, 0xfb, 0x69, 0x7f, 0x1a, 0xe6, 0x0d, 0x3c, 0xd1, 0xf0, 0x15, 0xa8, 0x46, + 0x5e, 0xcf, 0x77, 0xe3, 0x51, 0x48, 0x45, 0xd3, 0x09, 0xc0, 0xbe, 0x07, 0x0b, 0x5f, 0xa1, 0xa1, + 0x77, 0x7c, 0x7e, 0x51, 0xf3, 0x66, 0x3b, 0x85, 0x74, 0x3b, 0x3b, 0xb0, 0x98, 0x6a, 0x47, 0x74, + 0xcf, 0xc9, 0x57, 0xec, 0x64, 0xc5, 0xe1, 0x05, 0x4d, 0xf6, 0x15, 0x74, 0xd9, 0x67, 0x3f, 0x06, + 0xb2, 0x15, 0xf8, 0x3e, 0xed, 0xc4, 0x07, 0x94, 0x86, 0x89, 0x33, 0x9c, 0xd0, 0x6a, 0xed, 0xf6, + 0xb2, 0x58, 0xd9, 0xb4, 0x40, 0x15, 0x44, 0x4c, 0xa0, 0x34, 0xa4, 0xe1, 0x00, 0x1b, 0xae, 0x38, + 0xf8, 0xdb, 0x5e, 0x84, 0x79, 0xa3, 0x59, 0xe1, 0x3e, 0xbc, 0x09, 0x8b, 0xdb, 0x5e, 0xd4, 0xc9, + 0x76, 0xd8, 0x84, 0xa9, 0xe1, 0xe8, 0xa8, 0x9d, 0x70, 0xa2, 0x2c, 0x32, 0x8b, 0x33, 0xfd, 0x89, + 0x68, 0xec, 0xe7, 0x2d, 0x28, 0xed, 0x3e, 0xda, 0xdb, 0x62, 0xba, 0xc2, 0xf3, 0x3b, 0xc1, 0x80, + 0xe9, 0x5b, 0x3e, 0x69, 0x55, 0x1e, 0xcb, 0x61, 0x57, 0xa0, 0x8a, 0x6a, 0x9a, 0x19, 0xd1, 0xc2, + 0x6f, 0x4d, 0x00, 0xcc, 0x80, 0xa7, 0xcf, 0x86, 0x5e, 0x88, 0x16, 0xba, 0xb4, 0xbb, 0x4b, 0xa8, + 0x66, 0xb2, 0x15, 0xf6, 0x0f, 0xca, 0x30, 0x25, 0x94, 0x2f, 0xf6, 0xd7, 0x89, 0xbd, 0x53, 0x2a, + 0x46, 0x22, 0x4a, 0xcc, 0x04, 0x0a, 0xe9, 0x20, 0x88, 0x69, 0xdb, 0xd8, 0x06, 0x13, 0x88, 0x0e, + 0x8a, 0xf0, 0x1d, 0xb9, 0x4b, 0x53, 0xe4, 0x58, 0x06, 0x90, 0x2d, 0x96, 0xb4, 0xcf, 0x4a, 0x68, + 0x9f, 0xc9, 0x22, 0x5b, 0x89, 0x8e, 0x3b, 0x74, 0x3b, 0x5e, 0x7c, 0x2e, 0x44, 0x82, 0x2a, 0xb3, + 0xb6, 0xfb, 0x41, 0xc7, 0x65, 0x5e, 0x69, 0xdf, 0xf5, 0x3b, 0x54, 0x3a, 0x3f, 0x06, 0x90, 0x39, + 0x02, 0x62, 0x48, 0x12, 0x8d, 0x3b, 0x0b, 0x29, 0x28, 0xd3, 0xdf, 0x9d, 0x60, 0x30, 0xf0, 0x62, + 0xe6, 0x3f, 0xa0, 0x6d, 0x59, 0x74, 0x34, 0x08, 0x77, 0xb5, 0xb0, 0x74, 0xc6, 0x57, 0xaf, 0x2a, + 0x5d, 0x2d, 0x0d, 0xc8, 0x5a, 0x61, 0x5a, 0x87, 0x89, 0xb1, 0xa7, 0x67, 0x68, 0x48, 0x16, 0x1d, + 0x0d, 0xc2, 0xf6, 0x61, 0xe4, 0x47, 0x34, 0x8e, 0xfb, 0xb4, 0xab, 0x06, 0x54, 0x43, 0xb4, 0x6c, + 0x05, 0xb9, 0x05, 0xf3, 0xdc, 0xa5, 0x89, 0xdc, 0x38, 0x88, 0x4e, 0xbc, 0xa8, 0x1d, 0x31, 0xe7, + 0xa0, 0x8e, 0xf8, 0x79, 0x55, 0xe4, 0x0e, 0x2c, 0xa7, 0xc0, 0x21, 0xed, 0x50, 0xef, 0x94, 0x76, + 0x9b, 0xd3, 0xf8, 0xd5, 0xb8, 0x6a, 0xb2, 0x02, 0x35, 0xe6, 0xc9, 0x8d, 0x86, 0x5d, 0x97, 0x19, + 0x30, 0x33, 0xb8, 0x0f, 0x3a, 0x88, 0xbc, 0x09, 0xd3, 0x43, 0xca, 0xad, 0x9f, 0x93, 0xb8, 0xdf, + 0x89, 0x9a, 0xb3, 0x86, 0x74, 0x63, 0x94, 0xeb, 0x98, 0x18, 0x8c, 0x28, 0x3b, 0x11, 0x9a, 0xf4, + 0xee, 0x79, 0xb3, 0x21, 0xcc, 0x6a, 0x09, 0x40, 0x1e, 0x09, 0xbd, 0x53, 0x37, 0xa6, 0xcd, 0x39, + 0x2e, 0xd0, 0x45, 0x91, 0x7d, 0xe7, 0xf9, 0x5e, 0xec, 0xb9, 0x71, 0x10, 0x36, 0x09, 0xd6, 0x25, + 0x00, 0xb6, 0x88, 0x48, 0x1f, 0x51, 0xec, 0xc6, 0xa3, 0xa8, 0x7d, 0xdc, 0x77, 0x7b, 0x51, 0x73, + 0x9e, 0xdb, 0xa5, 0x99, 0x0a, 0xfb, 0xf7, 0x2c, 0x2e, 0xa4, 0x05, 0x41, 0x2b, 0x61, 0xfb, 0x2a, + 0xd4, 0x38, 0x29, 0xb7, 0x03, 0xbf, 0x7f, 0x2e, 0xa8, 0x1b, 0x38, 0xe8, 0xa1, 0xdf, 0x3f, 0x27, + 0x9f, 0x80, 0x69, 0xcf, 0xd7, 0x51, 0xb8, 0x3c, 0xa8, 0x4b, 0x20, 0x22, 0xbd, 0x0a, 0xb5, 0xe1, + 0xe8, 0xa8, 0xef, 0x75, 0x38, 0x4a, 0x91, 0xb7, 0xc2, 0x41, 0x88, 0xc0, 0x2c, 0x6d, 0x3e, 0x2b, + 0x8e, 0x51, 0x42, 0x8c, 0x9a, 0x80, 0x31, 0x14, 0x7b, 0x13, 0x16, 0xcc, 0x01, 0x0a, 0xc1, 0xb7, + 0x06, 0x15, 0xc1, 0x27, 0x51, 0xb3, 0x86, 0x6b, 0x3d, 0xa3, 0x45, 0x5c, 0x7c, 0xda, 0x77, 0x54, + 0xbd, 0xfd, 0x27, 0x25, 0x98, 0x17, 0xd0, 0xad, 0x7e, 0x10, 0xd1, 0xc3, 0xd1, 0x60, 0xe0, 0x86, + 0x39, 0x0c, 0x68, 0x5d, 0xc0, 0x80, 0x05, 0x93, 0x01, 0x19, 0x5b, 0x9c, 0xb8, 0x9e, 0xcf, 0xdd, + 0x04, 0xce, 0xbd, 0x1a, 0x84, 0xac, 0xc2, 0x6c, 0xa7, 0x1f, 0x44, 0xdc, 0x24, 0xd6, 0x1d, 0xfe, + 0x34, 0x38, 0x2b, 0x30, 0xca, 0x79, 0x02, 0x43, 0x67, 0xf8, 0xc9, 0x14, 0xc3, 0xdb, 0x50, 0x67, + 0x8d, 0x52, 0x29, 0xbf, 0xa6, 0xb8, 0x99, 0xac, 0xc3, 0xd8, 0x78, 0xd2, 0xec, 0xc5, 0x79, 0x79, + 0x36, 0x8f, 0xb9, 0xbc, 0x01, 0x45, 0xf9, 0xa8, 0x61, 0x57, 0x05, 0x73, 0x65, 0xab, 0xc8, 0x3d, + 0xe6, 0x25, 0xb2, 0xbe, 0x50, 0x49, 0x03, 0x2a, 0xe9, 0xeb, 0xe6, 0x8e, 0xe8, 0x6b, 0x7f, 0x93, + 0x15, 0x46, 0x21, 0x45, 0xc5, 0xad, 0x7d, 0x69, 0xff, 0x92, 0x05, 0x35, 0xad, 0x8e, 0x2c, 0xc2, + 0xdc, 0xd6, 0xc3, 0x87, 0x07, 0x3b, 0xce, 0xc6, 0xa3, 0x07, 0x5f, 0xd9, 0x69, 0x6f, 0xed, 0x3d, + 0x3c, 0xdc, 0x69, 0x4c, 0x30, 0xf0, 0xde, 0xc3, 0xad, 0x8d, 0xbd, 0xf6, 0xbd, 0x87, 0xce, 0x96, + 0x04, 0x5b, 0x64, 0x09, 0x88, 0xb3, 0xf3, 0xee, 0xc3, 0x47, 0x3b, 0x06, 0xbc, 0x40, 0x1a, 0x50, + 0xdf, 0x74, 0x76, 0x36, 0xb6, 0x76, 0x05, 0xa4, 0x48, 0x16, 0xa0, 0x71, 0xef, 0xf1, 0xfe, 0xf6, + 0x83, 0xfd, 0xfb, 0xed, 0xad, 0x8d, 0xfd, 0xad, 0x9d, 0xbd, 0x9d, 0xed, 0x46, 0x89, 0x4c, 0x43, + 0x75, 0x63, 0x73, 0x63, 0x7f, 0xfb, 0xe1, 0xfe, 0xce, 0x76, 0xa3, 0x6c, 0xff, 0x9d, 0x05, 0x8b, + 0x38, 0xea, 0x6e, 0x9a, 0x41, 0x56, 0xa0, 0xd6, 0x09, 0x82, 0x21, 0x33, 0x8e, 0x13, 0xf1, 0xaf, + 0x83, 0x18, 0xf1, 0x73, 0x61, 0x7b, 0x1c, 0x84, 0x1d, 0x2a, 0xf8, 0x03, 0x10, 0x74, 0x8f, 0x41, + 0x18, 0xf1, 0x8b, 0xed, 0xe5, 0x18, 0x9c, 0x3d, 0x6a, 0x1c, 0xc6, 0x51, 0x96, 0x60, 0xf2, 0x28, + 0xa4, 0x6e, 0xe7, 0x44, 0x70, 0x86, 0x28, 0x91, 0x4f, 0x25, 0xde, 0x5b, 0x87, 0xad, 0x7e, 0x9f, + 0x76, 0x91, 0x62, 0x2a, 0xce, 0xac, 0x80, 0x6f, 0x09, 0x30, 0x93, 0x16, 0xee, 0x91, 0xeb, 0x77, + 0x03, 0x9f, 0x76, 0x85, 0x69, 0x98, 0x00, 0xec, 0x03, 0x58, 0x4a, 0xcf, 0x4f, 0xf0, 0xd7, 0x5b, + 0x1a, 0x7f, 0x71, 0x4b, 0xad, 0x35, 0x7e, 0x37, 0x35, 0x5e, 0xfb, 0xfb, 0x02, 0x94, 0x98, 0xe2, + 0x1e, 0xaf, 0xe4, 0x75, 0x5b, 0xac, 0x98, 0x89, 0x0e, 0xa2, 0x43, 0xc8, 0x45, 0x39, 0x57, 0x77, + 0x1a, 0x24, 0xa9, 0x0f, 0x69, 0xe7, 0x14, 0x67, 0xac, 0xea, 0x19, 0x84, 0x31, 0x08, 0x33, 0x94, + 0xf1, 0x6b, 0xc1, 0x20, 0xb2, 0x2c, 0xeb, 0xf0, 0xcb, 0xa9, 0xa4, 0x0e, 0xbf, 0x6b, 0xc2, 0x94, + 0xe7, 0x1f, 0x05, 0x23, 0xbf, 0x8b, 0x0c, 0x51, 0x71, 0x64, 0x11, 0xe3, 0x91, 0xc8, 0xa8, 0xde, + 0x40, 0x92, 0x7f, 0x02, 0x20, 0xb7, 0xa1, 0x1a, 0x9d, 0xfb, 0x1d, 0x9d, 0xe6, 0x17, 0xc4, 0x2a, + 0xb1, 0x35, 0xb8, 0x79, 0x78, 0xee, 0x77, 0x90, 0xc2, 0x13, 0x34, 0xfb, 0x8b, 0x50, 0x91, 0x60, + 0x46, 0x96, 0x8f, 0xf7, 0xdf, 0xd9, 0x7f, 0xf8, 0x64, 0xbf, 0x7d, 0xf8, 0xde, 0xfe, 0x56, 0x63, + 0x82, 0xcc, 0x42, 0x6d, 0x63, 0x0b, 0x29, 0x1d, 0x01, 0x16, 0x43, 0x39, 0xd8, 0x38, 0x3c, 0x54, + 0x90, 0x82, 0x4d, 0x98, 0xb3, 0x1b, 0xa1, 0x75, 0xa4, 0xe2, 0x71, 0x6f, 0xc1, 0x9c, 0x06, 0x4b, + 0x2c, 0xed, 0x21, 0x03, 0xa4, 0x2c, 0x6d, 0x34, 0xab, 0x78, 0x8d, 0xdd, 0x80, 0x99, 0xfb, 0x34, + 0x7e, 0xe0, 0x1f, 0x07, 0xb2, 0xa5, 0x3f, 0x28, 0xc1, 0xac, 0x02, 0x89, 0x86, 0x56, 0x61, 0xd6, + 0xeb, 0x52, 0x3f, 0xf6, 0xe2, 0xf3, 0xb6, 0xe1, 0x53, 0xa7, 0xc1, 0xcc, 0x1c, 0x75, 0xfb, 0x9e, + 0x2b, 0xc3, 0xbe, 0xbc, 0xc0, 0x7c, 0x4c, 0xa6, 0x2b, 0xa5, 0xfa, 0x53, 0x74, 0xc5, 0x5d, 0xf9, + 0xdc, 0x3a, 0x26, 0x81, 0x18, 0x5c, 0xa8, 0x18, 0xf5, 0x09, 0x37, 0xcb, 0xf2, 0xaa, 0xd8, 0x56, + 0xf1, 0x96, 0xd8, 0x94, 0xcb, 0x5c, 0x9f, 0x2a, 0x40, 0x26, 0xae, 0x3a, 0xc9, 0xe5, 0x63, 0x3a, + 0xae, 0xaa, 0xc5, 0x66, 0x2b, 0x99, 0xd8, 0x2c, 0x93, 0x9f, 0xe7, 0x7e, 0x87, 0x76, 0xdb, 0x71, + 0xd0, 0x46, 0x39, 0x8f, 0x24, 0x51, 0x71, 0xd2, 0x60, 0x72, 0x05, 0xa6, 0x62, 0x1a, 0xc5, 0x3e, + 0xe5, 0x01, 0xb3, 0x0a, 0x86, 0x78, 0x24, 0x88, 0xd9, 0xd0, 0xa3, 0xd0, 0x8b, 0x9a, 0x75, 0x8c, + 0xba, 0xe2, 0x6f, 0xf2, 0x19, 0x58, 0x3c, 0xa2, 0x51, 0xdc, 0x3e, 0xa1, 0x6e, 0x97, 0x86, 0x48, + 0x5e, 0x3c, 0xbc, 0xcb, 0x4d, 0x93, 0xfc, 0x4a, 0x46, 0xb8, 0xa7, 0x34, 0x8c, 0xbc, 0xc0, 0x47, + 0xa3, 0xa4, 0xea, 0xc8, 0x22, 0x6b, 0x8f, 0x4d, 0x5e, 0x29, 0x69, 0xb5, 0x82, 0xb3, 0x38, 0xf1, + 0xfc, 0x4a, 0x72, 0x0d, 0x26, 0x71, 0x02, 0x51, 0xb3, 0x61, 0xc4, 0xa9, 0xb6, 0x18, 0xd0, 0x11, + 0x75, 0x5f, 0x2a, 0x55, 0x6a, 0x8d, 0xba, 0xfd, 0x39, 0x28, 0x23, 0x98, 0x6d, 0x3a, 0x5f, 0x0c, + 0x4e, 0x14, 0xbc, 0xc0, 0x86, 0xe6, 0xd3, 0xf8, 0x2c, 0x08, 0x9f, 0xca, 0x33, 0x00, 0x51, 0xb4, + 0xbf, 0x81, 0x5e, 0x88, 0x8a, 0x89, 0x3f, 0x46, 0x13, 0x8a, 0xf9, 0x92, 0x7c, 0xa9, 0xa3, 0x13, + 0x57, 0x38, 0x46, 0x15, 0x04, 0x1c, 0x9e, 0xb8, 0x4c, 0x56, 0x1a, 0xbb, 0xc7, 0x7d, 0xcd, 0x1a, + 0xc2, 0x76, 0xf9, 0xe6, 0x5d, 0x83, 0x19, 0x19, 0x6d, 0x8f, 0xda, 0x7d, 0x7a, 0x1c, 0xcb, 0x48, + 0x91, 0x3f, 0x1a, 0xa0, 0x43, 0xba, 0x47, 0x8f, 0x63, 0x7b, 0x1f, 0xe6, 0x84, 0xfc, 0x7a, 0x38, + 0xa4, 0xb2, 0xeb, 0xcf, 0xe7, 0xd9, 0x01, 0xb5, 0xdb, 0xf3, 0xa6, 0xc0, 0xe3, 0xe7, 0x0b, 0x26, + 0xa6, 0xed, 0x00, 0xd1, 0xe5, 0xa1, 0x68, 0x50, 0x28, 0x63, 0x19, 0x0b, 0x13, 0xd3, 0x31, 0x60, + 0x6c, 0x7d, 0xa2, 0x51, 0xa7, 0x23, 0xcf, 0x48, 0x98, 0xc7, 0xce, 0x8b, 0xf6, 0x1f, 0x5a, 0x30, + 0x8f, 0xad, 0x49, 0x4b, 0x46, 0xe8, 0x9c, 0x3b, 0x1f, 0x63, 0x98, 0xf5, 0x8e, 0x1e, 0x1f, 0x5c, + 0x80, 0xb2, 0xae, 0x85, 0x78, 0xe1, 0xe3, 0xc7, 0x1d, 0x4a, 0xe9, 0xb8, 0x83, 0xfd, 0x5b, 0x16, + 0xcc, 0x71, 0x45, 0x80, 0x56, 0xa5, 0x98, 0xfe, 0xff, 0x86, 0x69, 0xae, 0xd1, 0x05, 0x57, 0x8b, + 0x81, 0x26, 0xa2, 0x11, 0xa1, 0x1c, 0x79, 0x77, 0xc2, 0x31, 0x91, 0xc9, 0x5d, 0xb4, 0xaa, 0xfc, + 0x36, 0x42, 0x73, 0x4e, 0xd3, 0xcc, 0xb5, 0xde, 0x9d, 0x70, 0x34, 0xf4, 0xcd, 0x0a, 0x4c, 0x72, + 0x93, 0xdc, 0xbe, 0x0f, 0xd3, 0x46, 0x47, 0x46, 0xcc, 0xa3, 0xce, 0x63, 0x1e, 0x99, 0xe0, 0x62, + 0x21, 0x27, 0xb8, 0xf8, 0xc7, 0x45, 0x20, 0x8c, 0x58, 0x52, 0xbb, 0xc1, 0x7c, 0x82, 0xa0, 0x6b, + 0x78, 0x78, 0x75, 0x47, 0x07, 0x91, 0x9b, 0x40, 0xb4, 0xa2, 0x8c, 0x11, 0x73, 0x95, 0x97, 0x53, + 0xc3, 0xc4, 0xa4, 0xb0, 0x18, 0x84, 0x6e, 0x17, 0xbe, 0x2c, 0x5f, 0xf6, 0xdc, 0x3a, 0xa6, 0xd5, + 0x86, 0xa3, 0xe8, 0x04, 0x23, 0x7b, 0xc2, 0x07, 0x94, 0xe5, 0xf4, 0xfe, 0x4e, 0x5e, 0xb8, 0xbf, + 0x53, 0x99, 0xb8, 0x92, 0xe6, 0x85, 0x54, 0x4c, 0x2f, 0xe4, 0x1a, 0x4c, 0x0f, 0x98, 0x9d, 0x1b, + 0xf7, 0x3b, 0xed, 0x01, 0xeb, 0x5d, 0xb8, 0x7c, 0x06, 0x90, 0xac, 0x41, 0x43, 0xd8, 0x38, 0x89, + 0xab, 0xc3, 0x4f, 0x10, 0x32, 0x70, 0x26, 0xbf, 0x93, 0x48, 0x53, 0x0d, 0x07, 0x9b, 0x00, 0x98, + 0x5f, 0x13, 0x31, 0x0a, 0x69, 0x8f, 0x7c, 0x71, 0xa0, 0x46, 0xbb, 0xe8, 0xec, 0x55, 0x9c, 0x6c, + 0x85, 0xfd, 0x6b, 0x16, 0x34, 0xd8, 0x9e, 0x19, 0x64, 0xf9, 0x36, 0x20, 0x57, 0xbc, 0x24, 0x55, + 0x1a, 0xb8, 0xe4, 0x0e, 0x54, 0xb1, 0x1c, 0x0c, 0xa9, 0x2f, 0x68, 0xb2, 0x69, 0xd2, 0x64, 0x22, + 0x4f, 0x76, 0x27, 0x9c, 0x04, 0x59, 0xa3, 0xc8, 0xbf, 0xb6, 0xa0, 0x26, 0x7a, 0xf9, 0x91, 0x23, + 0x19, 0x2d, 0xed, 0x04, 0x94, 0x53, 0x52, 0x72, 0xe0, 0xb9, 0x0a, 0xb3, 0x03, 0x37, 0x1e, 0x85, + 0x4c, 0x1f, 0x1b, 0x51, 0x8c, 0x34, 0x98, 0x29, 0x57, 0x14, 0x9d, 0x51, 0x3b, 0xf6, 0xfa, 0x6d, + 0x59, 0x2b, 0xce, 0x1a, 0xf3, 0xaa, 0x98, 0x04, 0x89, 0x62, 0xb7, 0x47, 0x85, 0xde, 0xe4, 0x05, + 0xbb, 0x09, 0x4b, 0x62, 0x42, 0x29, 0xfb, 0xd8, 0xfe, 0x7e, 0x1d, 0x96, 0x33, 0x55, 0x2a, 0x33, + 0x42, 0xb8, 0xe7, 0x7d, 0x6f, 0x70, 0x14, 0x28, 0xe7, 0xc2, 0xd2, 0x3d, 0x77, 0xa3, 0x8a, 0xf4, + 0x60, 0x51, 0x1a, 0x08, 0x6c, 0x4d, 0x13, 0x65, 0x56, 0x40, 0x2d, 0xf5, 0xa6, 0xb9, 0x85, 0xe9, + 0x0e, 0x25, 0x5c, 0x67, 0xe2, 0xfc, 0xf6, 0xc8, 0x09, 0x34, 0x95, 0x25, 0x22, 0x84, 0xb5, 0x66, + 0xad, 0xb0, 0xbe, 0xde, 0xb8, 0xa0, 0x2f, 0xc3, 0x9c, 0x76, 0xc6, 0xb6, 0x46, 0xce, 0xe1, 0xaa, + 0xac, 0x43, 0x69, 0x9c, 0xed, 0xaf, 0xf4, 0x52, 0x73, 0x43, 0x47, 0xc1, 0xec, 0xf4, 0x82, 0x86, + 0xc9, 0x07, 0xb0, 0x74, 0xe6, 0x7a, 0xb1, 0x1c, 0x96, 0x66, 0x1b, 0x94, 0xb1, 0xcb, 0xdb, 0x17, + 0x74, 0xf9, 0x84, 0x7f, 0x6c, 0xa8, 0xa8, 0x31, 0x2d, 0xb6, 0x7e, 0x60, 0xc1, 0x8c, 0xd9, 0x0e, + 0x23, 0x53, 0xc1, 0xfb, 0x52, 0x06, 0x4a, 0x6b, 0x32, 0x05, 0xce, 0xfa, 0xe7, 0x85, 0x3c, 0xff, + 0x5c, 0xf7, 0x8a, 0x8b, 0x17, 0x85, 0xc1, 0x4a, 0x2f, 0x17, 0x06, 0x2b, 0xe7, 0x85, 0xc1, 0x5a, + 0xff, 0x61, 0x01, 0xc9, 0xd2, 0x12, 0xb9, 0xcf, 0x03, 0x04, 0x3e, 0xed, 0x0b, 0x91, 0xf2, 0xbf, + 0x5e, 0x8e, 0x1e, 0xe5, 0xda, 0xc9, 0xaf, 0x19, 0x63, 0xe8, 0xc9, 0x02, 0xba, 0xb1, 0x33, 0xed, + 0xe4, 0x55, 0xa5, 0x02, 0x73, 0xa5, 0x8b, 0x03, 0x73, 0xe5, 0x8b, 0x03, 0x73, 0x93, 0xe9, 0xc0, + 0x5c, 0xeb, 0xe7, 0x2c, 0x98, 0xcf, 0xd9, 0xf4, 0x9f, 0xdc, 0xc4, 0xd9, 0x36, 0x19, 0xb2, 0xa0, + 0x20, 0xb6, 0x49, 0x07, 0xb6, 0x7e, 0x0a, 0xa6, 0x0d, 0x42, 0xff, 0xc9, 0xf5, 0x9f, 0xb6, 0xd7, + 0x38, 0x9d, 0x19, 0xb0, 0xd6, 0xbf, 0x14, 0x80, 0x64, 0x99, 0xed, 0x7f, 0x74, 0x0c, 0xd9, 0x75, + 0x2a, 0xe6, 0xac, 0xd3, 0x7f, 0xab, 0x1e, 0x78, 0x03, 0xe6, 0x44, 0x06, 0x94, 0x16, 0x16, 0xe2, + 0x14, 0x93, 0xad, 0x60, 0x16, 0xab, 0x19, 0x15, 0xad, 0x18, 0x19, 0x21, 0x9a, 0x32, 0x4c, 0x05, + 0x47, 0xed, 0x16, 0x34, 0xc5, 0x0a, 0xed, 0x9c, 0x52, 0x3f, 0x3e, 0x1c, 0x1d, 0xf1, 0x14, 0x20, + 0x2f, 0xf0, 0xed, 0xef, 0x15, 0x95, 0xd1, 0x8d, 0x95, 0x42, 0xbd, 0x7f, 0x06, 0xea, 0xba, 0x30, + 0x17, 0xdb, 0x91, 0x8a, 0x0a, 0x32, 0xc5, 0xae, 0x63, 0x91, 0x6d, 0x98, 0x41, 0x91, 0xd5, 0x55, + 0xdf, 0x15, 0xf0, 0xbb, 0x17, 0x44, 0x3b, 0x76, 0x27, 0x9c, 0xd4, 0x37, 0xe4, 0x0b, 0x30, 0x63, + 0xba, 0x52, 0xc2, 0x46, 0xc8, 0xb3, 0xcd, 0xd9, 0xe7, 0x26, 0x32, 0xd9, 0x80, 0x46, 0xda, 0x17, + 0x13, 0xe7, 0xff, 0x63, 0x1a, 0xc8, 0xa0, 0x93, 0x3b, 0xe2, 0x78, 0xac, 0x8c, 0x51, 0x88, 0x6b, + 0xe6, 0x67, 0xda, 0x32, 0xdd, 0xe4, 0x7f, 0xb4, 0x03, 0xb3, 0xaf, 0x01, 0x24, 0x30, 0xd2, 0x80, + 0xfa, 0xc3, 0x83, 0x9d, 0xfd, 0xf6, 0xd6, 0xee, 0xc6, 0xfe, 0xfe, 0xce, 0x5e, 0x63, 0x82, 0x10, + 0x98, 0xc1, 0xa0, 0xd9, 0xb6, 0x82, 0x59, 0x0c, 0x26, 0xc2, 0x14, 0x12, 0x56, 0x20, 0x0b, 0xd0, + 0x78, 0xb0, 0x9f, 0x82, 0x16, 0x37, 0xab, 0x8a, 0x3f, 0xec, 0x25, 0x58, 0xe0, 0x19, 0x6e, 0x9b, + 0x9c, 0x3c, 0xa4, 0xad, 0xf0, 0xbb, 0x16, 0x2c, 0xa6, 0x2a, 0x92, 0x54, 0x12, 0x6e, 0x0e, 0x98, + 0x36, 0x82, 0x09, 0xc4, 0x90, 0xb7, 0xb4, 0xfc, 0x52, 0x12, 0x24, 0x5b, 0xc1, 0x68, 0x5e, 0xb3, + 0x14, 0x53, 0x9c, 0x94, 0x57, 0x65, 0x2f, 0xf3, 0x3c, 0x3c, 0xcc, 0xd8, 0x33, 0x06, 0x7e, 0xcc, + 0x33, 0xe7, 0xf4, 0x8a, 0xe4, 0xb8, 0xd1, 0x1c, 0xb2, 0x2c, 0x32, 0x23, 0xdf, 0x30, 0x3d, 0xcc, + 0xf1, 0xe6, 0xd6, 0xd9, 0x7f, 0x51, 0x00, 0xf2, 0xe5, 0x11, 0x0d, 0xcf, 0x31, 0x0b, 0x44, 0xc5, + 0x20, 0x97, 0xd3, 0x11, 0xb6, 0xc9, 0xe1, 0xe8, 0xe8, 0x1d, 0x7a, 0x2e, 0x33, 0x98, 0x0a, 0x7a, + 0x06, 0x13, 0x30, 0xe7, 0x58, 0xe5, 0xa0, 0x58, 0xab, 0x65, 0x0c, 0x49, 0x54, 0xfd, 0xd1, 0x80, + 0x37, 0x9a, 0x9b, 0x68, 0x54, 0xba, 0x38, 0xd1, 0xa8, 0x7c, 0x51, 0xa2, 0xd1, 0x27, 0x60, 0xda, + 0xeb, 0xf9, 0x01, 0x13, 0x0b, 0x4c, 0xb1, 0x47, 0xcd, 0xc9, 0x95, 0x22, 0x73, 0x86, 0x05, 0x70, + 0x9f, 0xc1, 0xc8, 0xe7, 0x12, 0x24, 0xda, 0xed, 0x61, 0xd2, 0x9a, 0x2e, 0x28, 0x76, 0xba, 0x3d, + 0xba, 0x17, 0x74, 0xdc, 0x38, 0x08, 0xd5, 0x87, 0x0c, 0x16, 0x31, 0xaf, 0x3f, 0x0a, 0x46, 0xcc, + 0xcc, 0x91, 0x4b, 0xc1, 0xc3, 0x36, 0x75, 0x0e, 0x3d, 0xc0, 0x05, 0xb1, 0xdf, 0x83, 0x9a, 0xd6, + 0x04, 0x66, 0x34, 0x09, 0x13, 0x42, 0xf8, 0x83, 0x25, 0x6e, 0xb1, 0xfb, 0xb4, 0xff, 0xa0, 0x4b, + 0x5e, 0x87, 0xb9, 0xae, 0x17, 0x52, 0x4c, 0x4e, 0x6b, 0x87, 0xf4, 0x94, 0x86, 0x91, 0xf4, 0x9c, + 0x1b, 0xaa, 0xc2, 0xe1, 0x70, 0xfb, 0x2e, 0xcc, 0x1b, 0x5b, 0xa3, 0x28, 0x57, 0x26, 0xfc, 0x58, + 0xd9, 0x84, 0x1f, 0x99, 0xec, 0x63, 0xff, 0x42, 0x01, 0x8a, 0xbb, 0xc1, 0x50, 0x3f, 0x62, 0xb0, + 0xcc, 0x23, 0x06, 0x61, 0x02, 0xb5, 0x95, 0x85, 0x23, 0x34, 0xa3, 0x01, 0x24, 0x6b, 0x30, 0xe3, + 0x0e, 0xe2, 0x76, 0x1c, 0x30, 0x93, 0xef, 0xcc, 0x0d, 0xbb, 0x9c, 0x9c, 0x71, 0x8b, 0x53, 0x35, + 0x64, 0x01, 0x8a, 0xca, 0x56, 0x40, 0x04, 0x56, 0x64, 0xfe, 0x06, 0x1e, 0x75, 0x9e, 0x8b, 0xc8, + 0x99, 0x28, 0x31, 0x6e, 0x31, 0xbf, 0xe7, 0xce, 0x1e, 0x97, 0xf8, 0x79, 0x55, 0xcc, 0x1c, 0x63, + 0xd4, 0x81, 0x68, 0x22, 0xce, 0x2a, 0xcb, 0x7a, 0x4c, 0xb8, 0x62, 0x1e, 0xfc, 0xfe, 0xb3, 0x05, + 0x65, 0x5c, 0x1b, 0xa6, 0xbd, 0x38, 0x7b, 0xab, 0x53, 0x06, 0x5c, 0x93, 0x69, 0x27, 0x0d, 0x26, + 0xb6, 0x91, 0xe6, 0x58, 0x50, 0x13, 0xd2, 0x53, 0x1d, 0x57, 0xa0, 0xca, 0x4b, 0x2a, 0xa5, 0x8f, + 0xd3, 0xbd, 0x02, 0x92, 0xab, 0x50, 0x3a, 0x09, 0x86, 0xd2, 0xdc, 0x06, 0x79, 0x60, 0x17, 0x0c, + 0x1d, 0x84, 0x27, 0xe3, 0x61, 0xed, 0xf1, 0x69, 0x71, 0x23, 0x2a, 0x0d, 0x66, 0x66, 0xa4, 0x6a, + 0x56, 0x5f, 0xa6, 0x14, 0xd4, 0x5e, 0x83, 0x59, 0x46, 0xf5, 0x5a, 0xd4, 0x75, 0x2c, 0x2b, 0xdb, + 0x3f, 0x63, 0x41, 0x45, 0x22, 0x93, 0x55, 0x28, 0x31, 0x16, 0x4a, 0x39, 0xae, 0xea, 0xa0, 0x9e, + 0xe1, 0x39, 0x88, 0xc1, 0x8c, 0x09, 0x0c, 0x86, 0x25, 0x7e, 0x92, 0x0c, 0x85, 0x25, 0x6e, 0x80, + 0x1a, 0x6e, 0xca, 0x7a, 0x4e, 0x41, 0xed, 0xef, 0x5a, 0x30, 0x6d, 0xf4, 0x41, 0x56, 0xa0, 0xd6, + 0x77, 0xa3, 0x58, 0x1c, 0x7e, 0x8a, 0xed, 0xd1, 0x41, 0xfa, 0x46, 0x17, 0xcc, 0xe0, 0xbf, 0x8a, + 0x10, 0x17, 0xf5, 0x08, 0xf1, 0x2d, 0xa8, 0x26, 0xc9, 0xa8, 0x25, 0x83, 0xf7, 0x59, 0x8f, 0x32, + 0x05, 0x21, 0x41, 0xc2, 0xa0, 0x63, 0xd0, 0x0f, 0x42, 0x71, 0x52, 0xc6, 0x0b, 0xf6, 0x5d, 0xa8, + 0x69, 0xf8, 0x7a, 0x0c, 0xd2, 0x32, 0x62, 0x90, 0x2a, 0x3f, 0xa7, 0x90, 0xe4, 0xe7, 0xd8, 0xff, + 0x6a, 0xc1, 0x34, 0xa3, 0x41, 0xcf, 0xef, 0x1d, 0x04, 0x7d, 0xaf, 0x73, 0x8e, 0x7b, 0x2f, 0xc9, + 0x4d, 0x88, 0x44, 0x49, 0x8b, 0x26, 0x98, 0x51, 0xbd, 0x8c, 0x7c, 0x08, 0x16, 0x55, 0x65, 0xc6, + 0xc3, 0x8c, 0x03, 0x8e, 0xdc, 0x48, 0xb0, 0x85, 0xb0, 0xda, 0x0c, 0x20, 0xe3, 0x34, 0x06, 0xc0, + 0x6c, 0xab, 0x81, 0xd7, 0xef, 0x7b, 0x1c, 0x97, 0xdb, 0xf4, 0x79, 0x55, 0xac, 0xcf, 0xae, 0x17, + 0xb9, 0x47, 0xc9, 0xe9, 0x8f, 0x2a, 0x63, 0x78, 0xc6, 0x7d, 0xa6, 0x85, 0x67, 0x26, 0x51, 0xae, + 0x98, 0x40, 0xfb, 0xcf, 0x0a, 0x50, 0x93, 0x26, 0x42, 0xb7, 0x47, 0xc5, 0x81, 0xa6, 0x29, 0x18, + 0x35, 0x88, 0xac, 0x37, 0xbc, 0x31, 0x0d, 0x92, 0x26, 0x8c, 0x62, 0x96, 0x30, 0xae, 0x40, 0x95, + 0x11, 0xe8, 0x9b, 0xe8, 0xf6, 0x89, 0xfc, 0x6e, 0x05, 0x90, 0xb5, 0xb7, 0xb1, 0xb6, 0x9c, 0xd4, + 0x22, 0xe0, 0x85, 0xc7, 0x9f, 0x77, 0xa0, 0x2e, 0x9a, 0xc1, 0x9d, 0x43, 0xc9, 0x93, 0xb0, 0x88, + 0xb1, 0xab, 0x8e, 0x81, 0x29, 0xbf, 0xbc, 0x2d, 0xbf, 0xac, 0x5c, 0xf4, 0xa5, 0xc4, 0xb4, 0xef, + 0xab, 0x53, 0xe5, 0xfb, 0xa1, 0x3b, 0x3c, 0x91, 0xbc, 0x7c, 0x0b, 0xe6, 0x3d, 0xbf, 0xd3, 0x1f, + 0x75, 0x69, 0x7b, 0xe4, 0xbb, 0xbe, 0x1f, 0x8c, 0xfc, 0x0e, 0x95, 0x09, 0x3a, 0x79, 0x55, 0x76, + 0x57, 0xa5, 0x73, 0x62, 0x43, 0x64, 0x0d, 0xca, 0x5c, 0x55, 0x72, 0xdd, 0x91, 0xcf, 0xe8, 0x1c, + 0x85, 0xac, 0x42, 0x99, 0x6b, 0xcc, 0x82, 0xc1, 0x35, 0xda, 0xae, 0x3a, 0x1c, 0x81, 0x89, 0x1d, + 0xcc, 0xe8, 0x35, 0xc5, 0x8e, 0xa9, 0x77, 0x26, 0x3b, 0x98, 0xf3, 0x6b, 0x2f, 0x00, 0xd9, 0xe7, + 0x9c, 0xa2, 0x9f, 0x0d, 0x7d, 0xbf, 0x08, 0x35, 0x0d, 0xcc, 0x24, 0x48, 0x8f, 0x0d, 0xb8, 0xdd, + 0xf5, 0xdc, 0x01, 0x8d, 0x69, 0x28, 0xb8, 0x23, 0x05, 0x65, 0x78, 0xee, 0x69, 0xaf, 0x1d, 0x8c, + 0xe2, 0x76, 0x97, 0xf6, 0x42, 0xca, 0xb5, 0x29, 0x53, 0x4d, 0x06, 0x94, 0xe1, 0x31, 0xfa, 0xd4, + 0xf0, 0x38, 0x05, 0xa5, 0xa0, 0xf2, 0xa4, 0x87, 0xaf, 0x51, 0x29, 0x39, 0xe9, 0xe1, 0x2b, 0x92, + 0x96, 0x7d, 0xe5, 0x1c, 0xd9, 0xf7, 0x16, 0x2c, 0x71, 0x29, 0x27, 0xe4, 0x41, 0x3b, 0x45, 0x58, + 0x63, 0x6a, 0xc9, 0x1a, 0x34, 0xd8, 0x98, 0x25, 0x4b, 0x44, 0xde, 0x37, 0x78, 0xd4, 0xd4, 0x72, + 0x32, 0x70, 0x86, 0x8b, 0xe1, 0x4b, 0x1d, 0x97, 0x1f, 0xb7, 0x67, 0xe0, 0x88, 0xeb, 0x3e, 0x33, + 0x71, 0xab, 0x02, 0x37, 0x05, 0x27, 0x77, 0x60, 0x79, 0x40, 0xbb, 0x9e, 0x6b, 0x36, 0x81, 0x11, + 0x60, 0x9e, 0x53, 0x33, 0xae, 0xda, 0x9e, 0x86, 0xda, 0x61, 0x1c, 0x0c, 0xe5, 0x76, 0xce, 0x40, + 0x9d, 0x17, 0x45, 0x8a, 0xd5, 0x65, 0xb8, 0x84, 0xf4, 0xf7, 0x28, 0x18, 0x06, 0xfd, 0xa0, 0x77, + 0x6e, 0x38, 0x5d, 0x7f, 0x65, 0xc1, 0xbc, 0x51, 0x9b, 0x78, 0x5d, 0x18, 0xaf, 0x91, 0xb9, 0x31, + 0x9c, 0x64, 0xe7, 0x34, 0xe1, 0xcd, 0x11, 0x79, 0x68, 0xfc, 0xb1, 0x48, 0x97, 0xd9, 0x48, 0xae, + 0xcd, 0xc8, 0x0f, 0x39, 0xfd, 0x36, 0xb3, 0xf4, 0x2b, 0xbe, 0x97, 0xb7, 0x66, 0x64, 0x13, 0x5f, + 0x10, 0x09, 0x0f, 0xdc, 0x09, 0x93, 0xe1, 0x39, 0xe5, 0xb6, 0xe9, 0x4e, 0xba, 0x1c, 0x41, 0x47, + 0x01, 0x23, 0xfb, 0x97, 0x2d, 0x80, 0x64, 0x74, 0x78, 0x4c, 0xae, 0x14, 0x10, 0xbf, 0xa2, 0xa5, + 0x29, 0x9b, 0xd7, 0xa0, 0xae, 0x4e, 0x3a, 0x13, 0x9d, 0x56, 0x93, 0x30, 0x66, 0x73, 0xdf, 0x80, + 0xd9, 0x5e, 0x3f, 0x38, 0x42, 0x83, 0x00, 0x73, 0xf6, 0x22, 0x91, 0x68, 0x36, 0xc3, 0xc1, 0xf7, + 0x04, 0x34, 0x51, 0x80, 0x25, 0x4d, 0x01, 0xda, 0xbf, 0x52, 0x50, 0x07, 0x53, 0xc9, 0x9c, 0xc7, + 0xf2, 0x27, 0xb9, 0x9d, 0x11, 0xc4, 0x63, 0xce, 0x81, 0xd0, 0xac, 0x3d, 0xb8, 0x30, 0x4e, 0x76, + 0x17, 0x66, 0x42, 0x2e, 0xe9, 0xa4, 0x18, 0x2c, 0xbd, 0x40, 0x0c, 0x4e, 0x87, 0x86, 0x96, 0xfc, + 0x14, 0x34, 0xdc, 0xee, 0x29, 0x0d, 0x63, 0x0f, 0x23, 0x15, 0x68, 0xa2, 0x70, 0xe1, 0x3d, 0xab, + 0xc1, 0xd1, 0x72, 0xb8, 0x01, 0xb3, 0x22, 0xb9, 0x4f, 0x61, 0x8a, 0x6b, 0x0f, 0x09, 0x98, 0x21, + 0xda, 0xdf, 0x91, 0x67, 0x60, 0xe6, 0x1e, 0x8e, 0x5f, 0x11, 0x7d, 0x76, 0x85, 0xd4, 0xec, 0x3e, + 0x21, 0xce, 0xa3, 0xba, 0x32, 0x1c, 0x52, 0xd4, 0x92, 0x63, 0xba, 0xe2, 0xfc, 0xd0, 0x5c, 0xd2, + 0xd2, 0xcb, 0x2c, 0xa9, 0xfd, 0x43, 0x0b, 0xa6, 0x76, 0x83, 0xe1, 0xae, 0x48, 0x13, 0x42, 0x46, + 0x50, 0x59, 0xb5, 0xb2, 0xf8, 0x82, 0x04, 0xa2, 0x5c, 0xcb, 0x60, 0x3a, 0x6d, 0x19, 0xfc, 0x5f, + 0xb8, 0x8c, 0xc1, 0xb8, 0x30, 0x18, 0x06, 0x21, 0x63, 0x46, 0xb7, 0xcf, 0xcd, 0x80, 0xc0, 0x8f, + 0x4f, 0xa4, 0x00, 0x7c, 0x11, 0x0a, 0x7a, 0xc8, 0xcc, 0xab, 0xe3, 0x46, 0xbd, 0xb0, 0x64, 0xb8, + 0x5c, 0xcc, 0x56, 0xd8, 0x9f, 0x87, 0x2a, 0x9a, 0xe2, 0x38, 0xad, 0x37, 0xa0, 0x7a, 0x12, 0x0c, + 0xdb, 0x27, 0x9e, 0x1f, 0x4b, 0xe6, 0x9e, 0x49, 0x6c, 0xe4, 0x5d, 0x5c, 0x10, 0x85, 0x60, 0xff, + 0xc6, 0x24, 0x4c, 0x3d, 0xf0, 0x4f, 0x03, 0xaf, 0x83, 0xe7, 0x6d, 0x03, 0x3a, 0x08, 0x64, 0x8e, + 0x31, 0xfb, 0x4d, 0xae, 0xc0, 0x14, 0x26, 0xd5, 0x0d, 0x39, 0xd1, 0xd6, 0xf9, 0xb9, 0xb8, 0x00, + 0x31, 0xf3, 0x22, 0x4c, 0x6e, 0x83, 0x70, 0xf6, 0xd1, 0x20, 0xcc, 0x49, 0x09, 0xf5, 0xdb, 0x1c, + 0xa2, 0x94, 0xe4, 0x70, 0x97, 0xb5, 0x1c, 0x6e, 0xd6, 0x97, 0x48, 0x6b, 0xe2, 0x79, 0x2f, 0xbc, + 0x2f, 0x01, 0x42, 0xc7, 0x2a, 0xa4, 0x3c, 0x98, 0x8a, 0xc6, 0xca, 0x94, 0x70, 0xac, 0x74, 0x20, + 0x33, 0x68, 0xf8, 0x07, 0x1c, 0x87, 0x8b, 0x6f, 0x1d, 0xc4, 0x4c, 0xc4, 0xf4, 0x45, 0x9e, 0x2a, + 0xa7, 0xfd, 0x14, 0x98, 0xc9, 0xf8, 0x2e, 0x55, 0x02, 0x95, 0xcf, 0x03, 0xf8, 0x8d, 0x97, 0x34, + 0x5c, 0x73, 0xc7, 0x78, 0xfe, 0xa3, 0x74, 0xc7, 0x18, 0xc1, 0xb8, 0xfd, 0xfe, 0x91, 0xdb, 0x79, + 0x8a, 0xf7, 0xb4, 0xf0, 0x04, 0xac, 0xea, 0x98, 0x40, 0x4c, 0x4e, 0x4a, 0x76, 0x15, 0x33, 0x08, + 0x4a, 0x8e, 0x0e, 0x22, 0xb7, 0xa1, 0x86, 0x2e, 0xa8, 0xd8, 0xd7, 0x19, 0xdc, 0xd7, 0x86, 0xee, + 0xa3, 0xe2, 0xce, 0xea, 0x48, 0xfa, 0x59, 0xe0, 0x6c, 0x26, 0x23, 0xd1, 0xed, 0x76, 0xc5, 0x11, + 0x6a, 0x83, 0xbb, 0xd3, 0x0a, 0xc0, 0xf4, 0xb1, 0x58, 0x30, 0x8e, 0x30, 0x87, 0x08, 0x06, 0x8c, + 0x5c, 0x85, 0x0a, 0x73, 0x8f, 0x86, 0xae, 0xd7, 0xc5, 0x94, 0x46, 0xee, 0xa5, 0x29, 0x18, 0x6b, + 0x43, 0xfe, 0x46, 0x45, 0x37, 0x8f, 0xab, 0x62, 0xc0, 0xd8, 0xda, 0xa8, 0x32, 0x32, 0xd3, 0x02, + 0xdf, 0x51, 0x03, 0x48, 0xde, 0xc4, 0x83, 0xac, 0x98, 0x36, 0x17, 0x31, 0x50, 0x76, 0x59, 0xcc, + 0x59, 0x10, 0xad, 0xfc, 0x7b, 0xc8, 0x50, 0x1c, 0x8e, 0x69, 0x6f, 0x40, 0x5d, 0x07, 0x93, 0x0a, + 0x94, 0x1e, 0x1e, 0xec, 0xec, 0x37, 0x26, 0x48, 0x0d, 0xa6, 0x0e, 0x77, 0x1e, 0x3d, 0xda, 0xdb, + 0xd9, 0x6e, 0x58, 0xa4, 0x0e, 0x15, 0x95, 0x49, 0x56, 0x60, 0xa5, 0x8d, 0xad, 0xad, 0x9d, 0x83, + 0x47, 0x3b, 0xdb, 0x8d, 0xa2, 0x1d, 0x03, 0xd9, 0xe8, 0x76, 0x45, 0x2b, 0x2a, 0x48, 0x90, 0xd0, + 0xb3, 0x65, 0xd0, 0x73, 0x0e, 0x4d, 0x15, 0xf2, 0x69, 0xea, 0x85, 0x2b, 0x6f, 0xef, 0x40, 0xed, + 0x40, 0xbb, 0xb4, 0x84, 0xec, 0x25, 0xaf, 0x2b, 0x09, 0xb6, 0xd4, 0x20, 0xda, 0x70, 0x0a, 0xfa, + 0x70, 0xec, 0xdf, 0xb7, 0xf8, 0xcd, 0x00, 0x35, 0x7c, 0xde, 0xb7, 0x0d, 0x75, 0x15, 0xad, 0x4a, + 0x92, 0x44, 0x0d, 0x18, 0xc3, 0xc1, 0xa1, 0xb4, 0x83, 0xe3, 0xe3, 0x88, 0xca, 0x94, 0x2e, 0x03, + 0xc6, 0xf8, 0x82, 0xd9, 0x66, 0xcc, 0xce, 0xf1, 0x78, 0x0f, 0x91, 0x48, 0xed, 0xca, 0xc0, 0x99, + 0x94, 0x17, 0x01, 0x19, 0x99, 0xcc, 0xa6, 0xca, 0x2a, 0x97, 0x35, 0xbd, 0xca, 0x6b, 0x50, 0x51, + 0xed, 0x9a, 0x02, 0x4c, 0x62, 0xaa, 0x7a, 0x26, 0x28, 0xd1, 0x5b, 0x31, 0x06, 0xcd, 0x85, 0x76, + 0xb6, 0x82, 0xdc, 0x04, 0x72, 0xec, 0x85, 0x69, 0xf4, 0x22, 0xa2, 0xe7, 0xd4, 0xd8, 0x4f, 0x60, + 0x5e, 0x12, 0x92, 0x66, 0x5a, 0x99, 0x9b, 0x68, 0x5d, 0xc4, 0x3e, 0x85, 0x2c, 0xfb, 0xd8, 0xff, + 0x69, 0xc1, 0x94, 0xd8, 0xe9, 0xcc, 0xc5, 0x37, 0xbe, 0xcf, 0x06, 0x8c, 0x34, 0x8d, 0x4b, 0x2f, + 0xc8, 0x6b, 0x42, 0x68, 0x66, 0xc4, 0x62, 0x31, 0x4f, 0x2c, 0x12, 0x28, 0x0d, 0xdd, 0xf8, 0x04, + 0x3d, 0xf5, 0xaa, 0x83, 0xbf, 0x49, 0x83, 0xc7, 0x95, 0xb8, 0x08, 0xc6, 0x98, 0x52, 0xde, 0x15, + 0x3f, 0xae, 0xed, 0xb3, 0x57, 0xfc, 0xae, 0x40, 0x15, 0x07, 0xd0, 0x4e, 0xc2, 0x46, 0x09, 0x80, + 0x51, 0x2e, 0x2f, 0x20, 0x5f, 0x8b, 0xfc, 0xf3, 0x04, 0x62, 0x2f, 0xf2, 0x9d, 0x17, 0x4b, 0xa0, + 0x0e, 0xa1, 0x45, 0xee, 0x70, 0x02, 0x4e, 0x28, 0x42, 0x0c, 0x20, 0x4d, 0x11, 0x02, 0xd5, 0x51, + 0xf5, 0x76, 0x0b, 0x9a, 0xdb, 0xb4, 0x4f, 0x63, 0xba, 0xd1, 0xef, 0xa7, 0xdb, 0xbf, 0x0c, 0x97, + 0x72, 0xea, 0x84, 0x35, 0xfd, 0x65, 0x58, 0xdc, 0xe0, 0x79, 0x96, 0x3f, 0xa9, 0x34, 0x1e, 0xbb, + 0x09, 0x4b, 0xe9, 0x26, 0x45, 0x67, 0xf7, 0x60, 0x6e, 0x9b, 0x1e, 0x8d, 0x7a, 0x7b, 0xf4, 0x34, + 0xe9, 0x88, 0x40, 0x29, 0x3a, 0x09, 0xce, 0x04, 0x63, 0xe2, 0x6f, 0xf2, 0x0a, 0x40, 0x9f, 0xe1, + 0xb4, 0xa3, 0x21, 0xed, 0xc8, 0x7b, 0x26, 0x08, 0x39, 0x1c, 0xd2, 0x8e, 0xfd, 0x16, 0x10, 0xbd, + 0x1d, 0xb1, 0x5e, 0x4c, 0x0b, 0x8e, 0x8e, 0xda, 0xd1, 0x79, 0x14, 0xd3, 0x81, 0xbc, 0x40, 0xa3, + 0x83, 0xec, 0x1b, 0x50, 0x3f, 0x70, 0xcf, 0x1d, 0xfa, 0xa1, 0xb8, 0xef, 0xb8, 0x0c, 0x53, 0x43, + 0xf7, 0x9c, 0x89, 0x29, 0x15, 0xcf, 0xc2, 0x6a, 0xfb, 0xdf, 0x0b, 0x30, 0xc9, 0x31, 0x59, 0xab, + 0x5d, 0x1a, 0xc5, 0x9e, 0x8f, 0x84, 0x25, 0x5b, 0xd5, 0x40, 0x19, 0x52, 0x2e, 0xe4, 0x90, 0xb2, + 0xf0, 0xf6, 0x64, 0xce, 0xbe, 0xa0, 0x57, 0x03, 0xc6, 0x88, 0x2b, 0xc9, 0xa7, 0xe3, 0x01, 0x95, + 0x04, 0x90, 0x0a, 0x7d, 0x26, 0xba, 0x96, 0x8f, 0x4f, 0x72, 0xa9, 0xa0, 0x5c, 0x1d, 0x94, 0xab, + 0xd1, 0xa7, 0x38, 0x81, 0x67, 0x34, 0x7a, 0x46, 0x73, 0x57, 0x5e, 0x42, 0x73, 0x73, 0x17, 0xf0, + 0x45, 0x9a, 0x1b, 0x5e, 0x42, 0x73, 0xdb, 0x04, 0x1a, 0x78, 0x19, 0x90, 0xd9, 0x86, 0x92, 0x76, + 0xbf, 0x65, 0x41, 0x43, 0x50, 0x91, 0xaa, 0x23, 0xaf, 0x19, 0x36, 0x70, 0x6e, 0x36, 0xfc, 0x35, + 0x98, 0x46, 0xcb, 0x54, 0xc5, 0x78, 0x45, 0x40, 0xda, 0x00, 0xb2, 0x79, 0xc8, 0xf3, 0xe3, 0x81, + 0xd7, 0x17, 0x9b, 0xa2, 0x83, 0x64, 0x98, 0x38, 0x74, 0x45, 0x5e, 0x99, 0xe5, 0xa8, 0xb2, 0xfd, + 0xe7, 0x16, 0xcc, 0x69, 0x03, 0x16, 0x54, 0x78, 0x17, 0x24, 0x37, 0xf0, 0x80, 0x2f, 0xe7, 0xdc, + 0x65, 0x93, 0x6d, 0x92, 0xcf, 0x0c, 0x64, 0xdc, 0x4c, 0xf7, 0x1c, 0x07, 0x18, 0x8d, 0x06, 0x42, + 0x88, 0xea, 0x20, 0x46, 0x48, 0x67, 0x94, 0x3e, 0x55, 0x28, 0x5c, 0x8c, 0x1b, 0x30, 0x8c, 0xaa, + 0x31, 0x8b, 0x5a, 0x21, 0x95, 0x44, 0x54, 0x4d, 0x07, 0xda, 0x7f, 0x6b, 0xc1, 0x3c, 0x77, 0x8d, + 0x84, 0xe3, 0xa9, 0xae, 0x3d, 0x4d, 0x72, 0x5f, 0x90, 0x73, 0xe4, 0xee, 0x84, 0x23, 0xca, 0xe4, + 0xb3, 0x2f, 0xe9, 0xce, 0xa9, 0x64, 0xb7, 0x31, 0x7b, 0x51, 0xcc, 0xdb, 0x8b, 0x17, 0xac, 0x74, + 0x5e, 0x80, 0xb3, 0x9c, 0x1b, 0xe0, 0xdc, 0x9c, 0x82, 0x72, 0xd4, 0x09, 0x86, 0xd4, 0x5e, 0x82, + 0x05, 0x73, 0x72, 0x42, 0x04, 0x7d, 0xdb, 0x82, 0xe6, 0x3d, 0x7e, 0x10, 0xe0, 0xf9, 0xbd, 0x5d, + 0x2f, 0x8a, 0x83, 0x50, 0xdd, 0x0e, 0xbd, 0x0a, 0x10, 0xc5, 0x6e, 0x18, 0xf3, 0x3c, 0x6a, 0x11, + 0x58, 0x4c, 0x20, 0x6c, 0x8c, 0xd4, 0xef, 0xf2, 0x5a, 0xbe, 0x37, 0xaa, 0x9c, 0xb1, 0x21, 0x84, + 0xf3, 0x66, 0x68, 0xe2, 0xeb, 0x3c, 0xf9, 0x93, 0xd9, 0x0a, 0xf4, 0x14, 0xe5, 0x3a, 0xf7, 0x8a, + 0x52, 0x50, 0xfb, 0x6f, 0x2c, 0x98, 0x4d, 0x06, 0x89, 0xc7, 0xa2, 0xa6, 0x74, 0x10, 0xea, 0x37, + 0x91, 0x0e, 0x32, 0xe4, 0xe9, 0x31, 0x7d, 0x2c, 0xc6, 0xa6, 0x41, 0x90, 0x63, 0x45, 0x29, 0x18, + 0x49, 0x03, 0x47, 0x07, 0xf1, 0x54, 0x2e, 0x66, 0x09, 0x08, 0xab, 0x46, 0x94, 0x30, 0x0d, 0x7e, + 0x10, 0xe3, 0x57, 0x3c, 0x38, 0x2b, 0x8b, 0x52, 0x95, 0x4e, 0x21, 0x14, 0x55, 0xa9, 0x7e, 0xa8, + 0x52, 0xe1, 0xeb, 0x23, 0xcb, 0xf6, 0xaf, 0x5a, 0x70, 0x29, 0x67, 0xe1, 0x05, 0xd7, 0x6c, 0xc3, + 0xdc, 0xb1, 0xaa, 0x94, 0x8b, 0xc3, 0x59, 0x67, 0x49, 0x1e, 0xda, 0x99, 0x0b, 0xe2, 0x64, 0x3f, + 0x50, 0x76, 0x11, 0x5f, 0x6e, 0x23, 0x59, 0x32, 0x5b, 0x61, 0x1f, 0x40, 0x6b, 0xe7, 0x19, 0x63, + 0xc2, 0x2d, 0xfd, 0xa1, 0x13, 0x49, 0x0b, 0xb7, 0x33, 0x42, 0xe6, 0x62, 0x47, 0xfb, 0x18, 0xa6, + 0x8d, 0xb6, 0xc8, 0xa7, 0x5f, 0xb6, 0x91, 0x54, 0x78, 0x1a, 0x4b, 0xfc, 0xa5, 0x16, 0x99, 0xb2, + 0xa9, 0x81, 0xec, 0x53, 0x98, 0x7d, 0x77, 0xd4, 0x8f, 0xbd, 0xe4, 0xd5, 0x16, 0xf2, 0x59, 0xf1, + 0x11, 0x36, 0x21, 0x97, 0x2e, 0xb7, 0x2b, 0x1d, 0x8f, 0xad, 0xd8, 0x80, 0xb5, 0xd4, 0xce, 0xf6, + 0x98, 0xad, 0xb0, 0x2f, 0xc1, 0x72, 0xd2, 0x25, 0x5f, 0x3b, 0x29, 0xa8, 0xbf, 0x63, 0xf1, 0x6c, + 0x07, 0xf3, 0x11, 0x19, 0x72, 0x1f, 0xe6, 0x23, 0xcf, 0xef, 0xf5, 0xa9, 0xde, 0x4e, 0x24, 0x56, + 0x62, 0xd1, 0x1c, 0x9e, 0x78, 0x68, 0xc6, 0xc9, 0xfb, 0x82, 0x11, 0x48, 0xfe, 0x40, 0x13, 0x02, + 0x49, 0x2d, 0x49, 0xde, 0x04, 0xbe, 0x04, 0x33, 0x66, 0x67, 0xe4, 0x8e, 0xc8, 0xb6, 0x4c, 0x46, + 0xa6, 0xc7, 0xb2, 0x4d, 0xca, 0x30, 0x30, 0xed, 0x6f, 0x5a, 0xd0, 0x74, 0x28, 0x23, 0x63, 0xaa, + 0x75, 0x2a, 0xa8, 0xe7, 0x6e, 0xa6, 0xd9, 0xf1, 0x13, 0x56, 0x59, 0x9c, 0x72, 0xae, 0x37, 0xc7, + 0x6e, 0xca, 0xee, 0x44, 0xce, 0xac, 0x36, 0x2b, 0x30, 0x29, 0xe6, 0xb7, 0x0c, 0x8b, 0x62, 0x48, + 0x72, 0x38, 0x49, 0xd0, 0xd4, 0xe8, 0xd4, 0x08, 0x9a, 0x1e, 0x41, 0x93, 0x5f, 0xdb, 0xd5, 0xe7, + 0x91, 0xe4, 0x36, 0xf0, 0xed, 0x88, 0xda, 0xfa, 0x0d, 0x5e, 0x13, 0xc8, 0x48, 0x96, 0x0f, 0x8b, + 0xe3, 0xf0, 0x53, 0x68, 0x1d, 0xb4, 0xf6, 0x1c, 0x6a, 0xda, 0x25, 0x68, 0xb2, 0x0c, 0xf3, 0x4f, + 0x1e, 0x3c, 0xda, 0xdf, 0x39, 0x3c, 0x6c, 0x1f, 0x3c, 0xde, 0x7c, 0x67, 0xe7, 0xbd, 0xf6, 0xee, + 0xc6, 0xe1, 0x6e, 0x63, 0x82, 0x2c, 0x01, 0xd9, 0xdf, 0x39, 0x7c, 0xb4, 0xb3, 0x6d, 0xc0, 0x2d, + 0x72, 0x15, 0x5a, 0x8f, 0xf7, 0x1f, 0x1f, 0xee, 0x6c, 0xb7, 0xf3, 0xbe, 0x2b, 0x90, 0x57, 0xe0, + 0x92, 0xa8, 0xcf, 0xf9, 0xbc, 0x78, 0xfb, 0x9b, 0x45, 0x98, 0xe1, 0xc9, 0x1b, 0xfc, 0x0d, 0x23, + 0x1a, 0x92, 0x77, 0x61, 0x4a, 0x3c, 0x86, 0x45, 0xe4, 0xbe, 0x98, 0xcf, 0x6f, 0xb5, 0x96, 0xd2, + 0x60, 0xb1, 0x98, 0xf3, 0x3f, 0xfb, 0xc3, 0x7f, 0xfa, 0xf5, 0xc2, 0x34, 0xa9, 0xad, 0x9f, 0xbe, + 0xb9, 0xde, 0xa3, 0x7e, 0xc4, 0xda, 0xf8, 0x1a, 0x40, 0xf2, 0xc4, 0x13, 0x69, 0x2a, 0xdf, 0x2d, + 0xf5, 0xfe, 0x55, 0xeb, 0x52, 0x4e, 0x8d, 0x68, 0xf7, 0x12, 0xb6, 0x3b, 0x6f, 0xcf, 0xb0, 0x76, + 0x3d, 0xdf, 0x8b, 0xf9, 0x73, 0x4f, 0x6f, 0x5b, 0x6b, 0xa4, 0x0b, 0x75, 0xfd, 0xf1, 0x25, 0x22, + 0x03, 0xc8, 0x39, 0xcf, 0x47, 0xb5, 0x2e, 0xe7, 0xd6, 0x49, 0x42, 0xc0, 0x3e, 0x16, 0xed, 0x06, + 0xeb, 0x63, 0x84, 0x18, 0x49, 0x2f, 0x7d, 0xce, 0x1e, 0xc9, 0x1b, 0x4b, 0xe4, 0x8a, 0x46, 0xb1, + 0x99, 0x17, 0x9e, 0x5a, 0xaf, 0x8c, 0xa9, 0x15, 0x7d, 0xbd, 0x82, 0x7d, 0x2d, 0xdb, 0x84, 0xf5, + 0xd5, 0x41, 0x1c, 0xf9, 0xc2, 0xd3, 0xdb, 0xd6, 0xda, 0xed, 0xdf, 0xbc, 0x0e, 0x55, 0x75, 0x58, + 0x44, 0x3e, 0x80, 0x69, 0x23, 0xbb, 0x86, 0xc8, 0x69, 0xe4, 0x25, 0xe3, 0xb4, 0xae, 0xe4, 0x57, + 0x8a, 0x8e, 0xaf, 0x62, 0xc7, 0x4d, 0xb2, 0xc4, 0x3a, 0x16, 0xe9, 0x29, 0xeb, 0x98, 0x27, 0xc6, + 0x2f, 0x7d, 0x3c, 0xd5, 0xc4, 0x00, 0xef, 0xec, 0x4a, 0x9a, 0x33, 0x8d, 0xde, 0x5e, 0x19, 0x53, + 0x2b, 0xba, 0xbb, 0x82, 0xdd, 0x2d, 0x91, 0x05, 0xbd, 0x3b, 0x75, 0x88, 0x43, 0xf1, 0xa6, 0x92, + 0xfe, 0x3c, 0x11, 0x79, 0x45, 0x11, 0x56, 0xde, 0xb3, 0x45, 0x8a, 0x44, 0xb2, 0x6f, 0x17, 0xd9, + 0x4d, 0xec, 0x8a, 0x10, 0xdc, 0x3e, 0xfd, 0x75, 0x22, 0x72, 0x04, 0x35, 0xed, 0x49, 0x0d, 0x72, + 0x69, 0xec, 0xf3, 0x1f, 0xad, 0x56, 0x5e, 0x55, 0xde, 0x54, 0xf4, 0xf6, 0xd7, 0x99, 0x7e, 0xff, + 0x2a, 0x54, 0xd5, 0x23, 0x0d, 0x64, 0x59, 0x7b, 0x34, 0x43, 0x7f, 0x54, 0xa2, 0xd5, 0xcc, 0x56, + 0xe4, 0x11, 0x9f, 0xde, 0x3a, 0x23, 0xbe, 0x27, 0x50, 0xd3, 0x1e, 0x62, 0x50, 0x13, 0xc8, 0x3e, + 0xf6, 0xa0, 0x26, 0x90, 0xf3, 0x6e, 0x83, 0x3d, 0x87, 0x5d, 0xd4, 0x48, 0x15, 0xe9, 0x3b, 0x7e, + 0x16, 0x44, 0x64, 0x0f, 0x16, 0x85, 0xb8, 0x3b, 0xa2, 0x1f, 0x67, 0x1b, 0x72, 0x5e, 0x84, 0xba, + 0x65, 0x91, 0xbb, 0x50, 0x91, 0xef, 0x6d, 0x90, 0xa5, 0xfc, 0x77, 0x43, 0x5a, 0xcb, 0x19, 0xb8, + 0x90, 0xa6, 0xef, 0x01, 0x24, 0xaf, 0x3e, 0x28, 0x21, 0x91, 0x79, 0x45, 0x42, 0x51, 0x40, 0xf6, + 0x89, 0x08, 0x7b, 0x09, 0x27, 0xd8, 0x20, 0x28, 0x24, 0x7c, 0x7a, 0x26, 0x2f, 0x25, 0x7e, 0x1d, + 0x6a, 0xda, 0xc3, 0x0f, 0x6a, 0xf9, 0xb2, 0x8f, 0x46, 0xa8, 0xe5, 0xcb, 0x79, 0x27, 0xc2, 0x6e, + 0x61, 0xeb, 0x0b, 0xf6, 0x2c, 0x6b, 0x3d, 0xf2, 0x7a, 0xfe, 0x80, 0x23, 0xb0, 0x0d, 0x3a, 0x81, + 0x69, 0xe3, 0x75, 0x07, 0xc5, 0xa1, 0x79, 0x6f, 0x47, 0x28, 0x0e, 0xcd, 0x7d, 0x10, 0x42, 0xd2, + 0x99, 0x3d, 0xc7, 0xfa, 0x39, 0x45, 0x14, 0xad, 0xa7, 0xf7, 0xa1, 0xa6, 0xbd, 0xd4, 0xa0, 0xe6, + 0x92, 0x7d, 0x14, 0x42, 0xcd, 0x25, 0xef, 0x61, 0x87, 0x05, 0xec, 0x63, 0xc6, 0x46, 0x52, 0xc0, + 0xeb, 0x75, 0xac, 0xed, 0x0f, 0x60, 0xc6, 0x7c, 0xbb, 0x41, 0xf1, 0x7e, 0xee, 0x2b, 0x10, 0x8a, + 0xf7, 0xc7, 0x3c, 0xf8, 0x20, 0x48, 0x7a, 0x6d, 0x5e, 0x75, 0xb2, 0xfe, 0x91, 0x48, 0x22, 0x79, + 0x4e, 0xbe, 0xcc, 0x04, 0x9c, 0xb8, 0xef, 0x48, 0x96, 0x35, 0xaa, 0xd5, 0x6f, 0x45, 0x2a, 0x7e, + 0xc9, 0x5c, 0x8d, 0x34, 0x89, 0x99, 0x5f, 0x10, 0x44, 0xad, 0x85, 0xf7, 0x1e, 0x35, 0xad, 0xa5, + 0x5f, 0x8d, 0xd4, 0xb4, 0x96, 0x71, 0x3d, 0x32, 0xad, 0xb5, 0x62, 0x8f, 0xb5, 0xe1, 0xc3, 0x6c, + 0x2a, 0x03, 0x58, 0x71, 0x45, 0xfe, 0x95, 0x89, 0xd6, 0xd5, 0x17, 0x27, 0x0e, 0x9b, 0x12, 0x44, + 0x0a, 0xc1, 0x75, 0x79, 0x41, 0xe5, 0xff, 0x43, 0x5d, 0xbf, 0x27, 0x4f, 0x74, 0x56, 0x4e, 0xf7, + 0x74, 0x39, 0xb7, 0xce, 0xdc, 0x5c, 0x52, 0xd7, 0xbb, 0x21, 0x5f, 0x81, 0x25, 0xc5, 0xea, 0x7a, + 0x52, 0x69, 0x44, 0x5e, 0xcd, 0x49, 0x35, 0xd5, 0x8d, 0xa0, 0xd6, 0xa5, 0xb1, 0xb9, 0xa8, 0xb7, + 0x2c, 0x46, 0x34, 0xe6, 0x05, 0xe4, 0x44, 0x61, 0xe4, 0xdd, 0xbb, 0x4e, 0x14, 0x46, 0xee, 0xad, + 0x65, 0x49, 0x34, 0x64, 0xde, 0x58, 0x23, 0x7e, 0xce, 0x47, 0xde, 0x87, 0x59, 0x2d, 0x6d, 0xff, + 0xf0, 0xdc, 0xef, 0x28, 0x06, 0xc8, 0xde, 0xef, 0x6a, 0xe5, 0x99, 0xf8, 0xf6, 0x32, 0xb6, 0x3f, + 0x67, 0x1b, 0x8b, 0xc3, 0x88, 0x7f, 0x0b, 0x6a, 0xfa, 0x95, 0x80, 0x17, 0xb4, 0xbb, 0xac, 0x55, + 0xe9, 0xd7, 0x93, 0x6e, 0x59, 0xe4, 0xb7, 0x2d, 0xa8, 0x1b, 0x09, 0xf6, 0xc6, 0x69, 0x76, 0xaa, + 0x9d, 0xa6, 0x5e, 0xa7, 0x37, 0x64, 0x3b, 0x38, 0xc8, 0xbd, 0xb5, 0x2f, 0x19, 0x8b, 0xf0, 0x91, + 0x11, 0xc7, 0xb9, 0x99, 0x7e, 0xe3, 0xeb, 0x79, 0x1a, 0x41, 0xbf, 0x03, 0xf7, 0xfc, 0x96, 0x45, + 0xbe, 0x6b, 0xc1, 0x8c, 0x19, 0x7d, 0x54, 0x5b, 0x95, 0x1b, 0xe7, 0x54, 0x5b, 0x35, 0x26, 0x64, + 0xf9, 0x3e, 0x8e, 0xf2, 0xd1, 0x9a, 0x63, 0x8c, 0x52, 0x5c, 0x4d, 0xff, 0xf1, 0x46, 0x4b, 0xde, + 0xe6, 0xcf, 0x00, 0xca, 0x90, 0x38, 0xd1, 0xb4, 0x46, 0x7a, 0x7b, 0xf5, 0xa7, 0xeb, 0x56, 0xad, + 0x5b, 0x16, 0xf9, 0x3a, 0x7f, 0xdb, 0x4a, 0x7c, 0x8b, 0x54, 0xf2, 0xb2, 0xdf, 0xdb, 0xd7, 0x70, + 0x4e, 0x57, 0xed, 0x4b, 0xc6, 0x9c, 0xd2, 0xfa, 0x78, 0x83, 0x8f, 0x4e, 0xbc, 0x3a, 0x97, 0x28, + 0x94, 0xcc, 0x4b, 0x74, 0xe3, 0x07, 0x39, 0xe0, 0x83, 0x14, 0xe8, 0x06, 0x29, 0xbf, 0x64, 0x33, + 0xf6, 0x1a, 0x8e, 0xf5, 0x9a, 0xfd, 0xea, 0xd8, 0xb1, 0xae, 0x63, 0x0c, 0x91, 0x8d, 0xf8, 0x00, + 0x20, 0x39, 0xbe, 0x22, 0xa9, 0xe3, 0x13, 0xc5, 0xe0, 0xd9, 0x13, 0x2e, 0x93, 0x5f, 0xe4, 0x29, + 0x0b, 0x6b, 0xf1, 0xab, 0x5c, 0x5c, 0x3d, 0x90, 0x07, 0x2f, 0xba, 0x51, 0x62, 0x9e, 0x33, 0x19, + 0x46, 0x49, 0xba, 0x7d, 0x43, 0x58, 0xa9, 0x53, 0x9c, 0xc7, 0x30, 0xbd, 0x17, 0x04, 0x4f, 0x47, + 0x43, 0x75, 0x14, 0x6d, 0x86, 0xf7, 0x77, 0xdd, 0xe8, 0xa4, 0x95, 0x9a, 0x85, 0xbd, 0x82, 0x4d, + 0xb5, 0x48, 0x53, 0x6b, 0x6a, 0xfd, 0xa3, 0xe4, 0x78, 0xec, 0x39, 0x71, 0x61, 0x4e, 0xc9, 0x40, + 0x35, 0xf0, 0x96, 0xd9, 0x8c, 0x21, 0xf9, 0xd2, 0x5d, 0x18, 0xd6, 0xb3, 0x1c, 0xed, 0x7a, 0x24, + 0xdb, 0xbc, 0x65, 0x91, 0x03, 0xa8, 0x6f, 0xd3, 0x4e, 0xd0, 0xa5, 0x22, 0x46, 0x3e, 0x9f, 0x0c, + 0x5c, 0x05, 0xd7, 0x5b, 0xd3, 0x06, 0xd0, 0xd4, 0x0b, 0x43, 0xf7, 0x3c, 0xa4, 0x1f, 0xae, 0x7f, + 0x24, 0xa2, 0xef, 0xcf, 0xa5, 0x5e, 0x90, 0xc7, 0x13, 0x86, 0x5e, 0x48, 0x9d, 0x67, 0x18, 0x7a, + 0x21, 0x73, 0x9e, 0x61, 0x2c, 0xb5, 0x3c, 0x1e, 0x21, 0x7d, 0x98, 0xcb, 0x1c, 0x81, 0x28, 0x95, + 0x30, 0xee, 0xe0, 0xa4, 0xb5, 0x32, 0x1e, 0xc1, 0xec, 0x6d, 0xcd, 0xec, 0xed, 0x10, 0xa6, 0xb7, + 0x29, 0x5f, 0x2c, 0x9e, 0x29, 0x97, 0xba, 0xa5, 0xa1, 0xe7, 0xe1, 0xa5, 0x05, 0x38, 0xd6, 0x99, + 0x8a, 0x1f, 0xd3, 0xd4, 0xc8, 0x57, 0xa1, 0x76, 0x9f, 0xc6, 0x32, 0x35, 0x4e, 0x99, 0x9e, 0xa9, + 0x5c, 0xb9, 0x56, 0x4e, 0x66, 0x9d, 0x49, 0x33, 0xd8, 0xda, 0x3a, 0xed, 0xf6, 0x28, 0x17, 0x4e, + 0x6d, 0xaf, 0xfb, 0x9c, 0xfc, 0x3f, 0x6c, 0x5c, 0x65, 0xf0, 0x2e, 0x69, 0x79, 0x51, 0x7a, 0xe3, + 0xb3, 0x29, 0x78, 0x5e, 0xcb, 0x7e, 0xd0, 0xa5, 0x9a, 0x09, 0xe4, 0x43, 0x4d, 0x4b, 0x3c, 0x57, + 0x0c, 0x94, 0xbd, 0x27, 0xa0, 0x18, 0x28, 0x27, 0x4f, 0xdd, 0x5e, 0xc5, 0x7e, 0x6c, 0xb2, 0x92, + 0xf4, 0xc3, 0x73, 0xd3, 0x93, 0x9e, 0xd6, 0x3f, 0x72, 0x07, 0xf1, 0x73, 0xf2, 0x04, 0x9f, 0x8a, + 0xd0, 0xd3, 0xff, 0x12, 0x5b, 0x3a, 0x9d, 0x29, 0xa8, 0x16, 0x4b, 0xab, 0x32, 0xed, 0x6b, 0xde, + 0x15, 0x5a, 0x4a, 0x9f, 0x05, 0x38, 0x8c, 0x83, 0xe1, 0xb6, 0x4b, 0x07, 0x81, 0x9f, 0xc8, 0xda, + 0x24, 0x51, 0x2d, 0x91, 0x5f, 0x5a, 0xb6, 0x1a, 0x79, 0xa2, 0x39, 0x1f, 0x46, 0xf6, 0xa4, 0x24, + 0xae, 0xb1, 0xb9, 0x6c, 0x6a, 0x41, 0x72, 0xf2, 0xd9, 0x6e, 0x59, 0x64, 0x03, 0x20, 0x39, 0x03, + 0x53, 0xae, 0x44, 0xe6, 0x78, 0x4d, 0x89, 0xbd, 0x9c, 0x03, 0xb3, 0x03, 0xa8, 0x26, 0x87, 0x2a, + 0xcb, 0xc9, 0xdd, 0x08, 0xe3, 0x08, 0x46, 0x69, 0xf0, 0xcc, 0x51, 0x87, 0xdd, 0xc0, 0xa5, 0x02, + 0x52, 0x61, 0x4b, 0x85, 0xe7, 0x17, 0x1e, 0xcc, 0xf3, 0x01, 0x2a, 0x73, 0x04, 0x53, 0xaf, 0xe4, + 0x4c, 0x72, 0x8e, 0x1b, 0x14, 0x37, 0xe7, 0x46, 0xeb, 0x8d, 0x88, 0x08, 0xa3, 0x56, 0x9e, 0xf6, + 0xc5, 0x44, 0xf3, 0x00, 0xe6, 0x32, 0xe1, 0x64, 0xc5, 0xd2, 0xe3, 0x22, 0xfc, 0x8a, 0xa5, 0xc7, + 0x46, 0xa2, 0xed, 0x45, 0xec, 0x72, 0xd6, 0x06, 0xf4, 0x80, 0xce, 0xbc, 0xb8, 0x73, 0xc2, 0xba, + 0xfb, 0x8e, 0x05, 0xf3, 0x39, 0xd1, 0x62, 0xf2, 0x9a, 0x74, 0xa6, 0xc7, 0x46, 0x92, 0x5b, 0xb9, + 0xc1, 0x44, 0xfb, 0x10, 0xfb, 0x79, 0x97, 0xbc, 0x63, 0x28, 0x36, 0x1e, 0xc7, 0x13, 0x9c, 0xf9, + 0x42, 0xa3, 0x22, 0xd7, 0xa2, 0xf8, 0x10, 0x96, 0xf9, 0x40, 0x36, 0xfa, 0xfd, 0x54, 0xa0, 0xf3, + 0x6a, 0xe6, 0xa5, 0x6f, 0x23, 0x80, 0xdb, 0x1a, 0xff, 0x12, 0xf8, 0x18, 0x73, 0x95, 0x0f, 0x95, + 0x8c, 0xa0, 0x91, 0x0e, 0x1e, 0x92, 0xf1, 0x6d, 0xb5, 0x5e, 0x35, 0xdc, 0xc2, 0x6c, 0xc0, 0xd1, + 0xfe, 0x24, 0x76, 0xf6, 0xaa, 0xdd, 0xca, 0x5b, 0x17, 0xee, 0x29, 0xb2, 0xfd, 0xf8, 0x69, 0x15, + 0xe9, 0x4c, 0xcd, 0x53, 0x76, 0x30, 0x2e, 0x34, 0xab, 0x1c, 0xd3, 0xfc, 0x40, 0xe9, 0x75, 0xec, + 0x7e, 0xc5, 0xbe, 0x9c, 0xd7, 0x7d, 0xc8, 0x3f, 0xe1, 0x2e, 0xea, 0x72, 0x9a, 0xaf, 0xe5, 0x08, + 0x56, 0xf2, 0xf6, 0x7b, 0xac, 0xaf, 0x91, 0x5a, 0xeb, 0x89, 0x5b, 0xd6, 0xe6, 0x8d, 0xf7, 0x3f, + 0xd9, 0xf3, 0xe2, 0x93, 0xd1, 0xd1, 0xcd, 0x4e, 0x30, 0x58, 0xef, 0xcb, 0x10, 0x99, 0x48, 0xf3, + 0x5d, 0xef, 0xfb, 0xdd, 0x75, 0xfc, 0xfe, 0x68, 0x12, 0xff, 0x71, 0xc0, 0xa7, 0xff, 0x2b, 0x00, + 0x00, 0xff, 0xff, 0x1d, 0xe9, 0x2d, 0xa2, 0x6a, 0x60, 0x00, 0x00, } diff --git a/lnrpc/rpc.proto b/lnrpc/rpc.proto index f9ce83f81..8a876f630 100644 --- a/lnrpc/rpc.proto +++ b/lnrpc/rpc.proto @@ -1257,6 +1257,26 @@ message Peer { /// Ping time to this peer int64 ping_time = 9 [json_name = "ping_time"]; + + enum SyncType { + /** + Denotes that we cannot determine the peer's current sync type. + */ + UNKNOWN_SYNC = 0; + + /** + Denotes that we are actively receiving new graph updates from the peer. + */ + ACTIVE_SYNC = 1; + + /** + Denotes that we are not receiving new graph updates from the peer. + */ + PASSIVE_SYNC = 2; + } + + // The type of sync we are currently performing with this peer. + SyncType sync_type = 10 [json_name = "sync_type"]; } message ListPeersRequest { diff --git a/lnrpc/rpc.swagger.json b/lnrpc/rpc.swagger.json index b87492826..6102ec857 100644 --- a/lnrpc/rpc.swagger.json +++ b/lnrpc/rpc.swagger.json @@ -1305,6 +1305,16 @@ ], "default": "OPEN" }, + "PeerSyncType": { + "type": "string", + "enum": [ + "UNKNOWN_SYNC", + "ACTIVE_SYNC", + "PASSIVE_SYNC" + ], + "default": "UNKNOWN_SYNC", + "description": " - UNKNOWN_SYNC: *\nDenotes that we cannot determine the peer's current sync type.\n - ACTIVE_SYNC: *\nDenotes that we are actively receiving new graph updates from the peer.\n - PASSIVE_SYNC: *\nDenotes that we are not receiving new graph updates from the peer." + }, "PendingChannelsResponseClosedChannel": { "type": "object", "properties": { @@ -2856,6 +2866,10 @@ "type": "string", "format": "int64", "title": "/ Ping time to this peer" + }, + "sync_type": { + "$ref": "#/definitions/PeerSyncType", + "description": "The type of sync we are currently performing with this peer." } } }, diff --git a/peer.go b/peer.go index 69b4cf082..97bf55415 100644 --- a/peer.go +++ b/peer.go @@ -396,19 +396,16 @@ func (p *peer) initGossipSync() { srvrLog.Infof("Negotiated chan series queries with %x", p.pubKeyBytes[:]) - // We'll only request channel updates from the remote peer if - // its enabled in the config, or we're already getting updates - // from enough peers. - // - // TODO(roasbeef): craft s.t. we only get updates from a few - // peers - recvUpdates := !cfg.NoChanUpdates - // Register the this peer's for gossip syncer with the gossiper. // This is blocks synchronously to ensure the gossip syncer is // registered with the gossiper before attempting to read // messages from the remote peer. - p.server.authGossiper.InitSyncState(p, recvUpdates) + // + // TODO(wilmer): Only sync updates from non-channel peers. This + // requires an improved version of the current network + // bootstrapper to ensure we can find and connect to non-channel + // peers. + p.server.authGossiper.InitSyncState(p) // If the remote peer has the initial sync feature bit set, then we'll // being the synchronization protocol to exchange authenticated channel diff --git a/rpcserver.go b/rpcserver.go index ea84f2cba..e79b4d674 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -34,6 +34,7 @@ import ( "github.com/lightningnetwork/lnd/chanbackup" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channelnotifier" + "github.com/lightningnetwork/lnd/discovery" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/invoices" @@ -2012,9 +2013,36 @@ func (r *rpcServer) ListPeers(ctx context.Context, satRecv += int64(c.TotalMSatReceived.ToSatoshis()) } - nodePub := serverPeer.addr.IdentityKey.SerializeCompressed() + nodePub := serverPeer.PubKey() + + // Retrieve the peer's sync type. If we don't currently have a + // syncer for the peer, then we'll default to a passive sync. + // This can happen if the RPC is called while a peer is + // initializing. + syncer, ok := r.server.authGossiper.SyncManager().GossipSyncer( + nodePub, + ) + + var lnrpcSyncType lnrpc.Peer_SyncType + if !ok { + rpcsLog.Warnf("Gossip syncer for peer=%x not found", + nodePub) + lnrpcSyncType = lnrpc.Peer_UNKNOWN_SYNC + } else { + syncType := syncer.SyncType() + switch syncType { + case discovery.ActiveSync: + lnrpcSyncType = lnrpc.Peer_ACTIVE_SYNC + case discovery.PassiveSync: + lnrpcSyncType = lnrpc.Peer_PASSIVE_SYNC + default: + return nil, fmt.Errorf("unhandled sync type %v", + syncType) + } + } + peer := &lnrpc.Peer{ - PubKey: hex.EncodeToString(nodePub), + PubKey: hex.EncodeToString(nodePub[:]), Address: serverPeer.conn.RemoteAddr().String(), Inbound: serverPeer.inbound, BytesRecv: atomic.LoadUint64(&serverPeer.bytesReceived), @@ -2022,6 +2050,7 @@ func (r *rpcServer) ListPeers(ctx context.Context, SatSent: satSent, SatRecv: satRecv, PingTime: serverPeer.PingTime(), + SyncType: lnrpcSyncType, } resp.Peers = append(resp.Peers, peer) diff --git a/server.go b/server.go index 56615d812..5a8f6d6aa 100644 --- a/server.go +++ b/server.go @@ -636,10 +636,7 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, cc *chainControl, return nil, fmt.Errorf("can't create router: %v", err) } - chanSeries := discovery.NewChanSeries( - s.chanDB.ChannelGraph(), - ) - + chanSeries := discovery.NewChanSeries(s.chanDB.ChannelGraph()) gossipMessageStore, err := discovery.NewMessageStore(s.chanDB) if err != nil { return nil, err @@ -650,19 +647,23 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, cc *chainControl, } s.authGossiper = discovery.New(discovery.Config{ - Router: s.chanRouter, - Notifier: s.cc.chainNotifier, - ChainHash: *activeNetParams.GenesisHash, - Broadcast: s.BroadcastMessage, - ChanSeries: chanSeries, - NotifyWhenOnline: s.NotifyWhenOnline, - NotifyWhenOffline: s.NotifyWhenOffline, - ProofMatureDelta: 0, - TrickleDelay: time.Millisecond * time.Duration(cfg.TrickleDelay), - RetransmitDelay: time.Minute * 30, - WaitingProofStore: waitingProofStore, - MessageStore: gossipMessageStore, - AnnSigner: s.nodeSigner, + Router: s.chanRouter, + Notifier: s.cc.chainNotifier, + ChainHash: *activeNetParams.GenesisHash, + Broadcast: s.BroadcastMessage, + ChanSeries: chanSeries, + NotifyWhenOnline: s.NotifyWhenOnline, + NotifyWhenOffline: s.NotifyWhenOffline, + ProofMatureDelta: 0, + TrickleDelay: time.Millisecond * time.Duration(cfg.TrickleDelay), + RetransmitDelay: time.Minute * 30, + WaitingProofStore: waitingProofStore, + MessageStore: gossipMessageStore, + AnnSigner: s.nodeSigner, + RotateTicker: ticker.New(discovery.DefaultSyncerRotationInterval), + HistoricalSyncTicker: ticker.New(discovery.DefaultHistoricalSyncInterval), + ActiveSyncerTimeoutTicker: ticker.New(discovery.DefaultActiveSyncerTimeout), + NumActiveSyncers: cfg.NumGraphSyncPeers, }, s.identityPriv.PubKey(), ) @@ -2622,7 +2623,7 @@ func (s *server) peerTerminationWatcher(p *peer, ready chan struct{}) { // We'll also inform the gossiper that this peer is no longer active, // so we don't need to maintain sync state for it any longer. - s.authGossiper.PruneSyncState(pubKey) + s.authGossiper.PruneSyncState(p.PubKey()) // Tell the switch to remove all links associated with this peer. // Passing nil as the target link indicates that all links associated