From c72db902f02574dfd0b2c6c5e3eb17ac50bdbd6e Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Fri, 22 Mar 2019 19:54:15 -0700 Subject: [PATCH 01/13] discovery: replace waitPredicate with lntest version --- discovery/reliable_sender_test.go | 42 ++++++------------------------- 1 file changed, 8 insertions(+), 34 deletions(-) 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. From d954cfc4baab1e4248f4d294f9ad4d196986bef1 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Fri, 22 Mar 2019 19:54:31 -0700 Subject: [PATCH 02/13] discovery: include peerPub in gossipSyncerCfg --- discovery/gossiper.go | 4 +-- discovery/syncer.go | 56 ++++++++++++++++++++-------------------- discovery/syncer_test.go | 2 +- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/discovery/gossiper.go b/discovery/gossiper.go index fb6cd7d29..ac954186b 100644 --- a/discovery/gossiper.go +++ b/discovery/gossiper.go @@ -1107,8 +1107,9 @@ func (d *AuthenticatedGossiper) InitSyncState(syncPeer lnpeer.Peer, log.Infof("Creating new gossipSyncer for peer=%x", nodeID[:]) encoding := lnwire.EncodingSortedPlain - syncer := newGossiperSyncer(gossipSyncerCfg{ + syncer := newGossipSyncer(gossipSyncerCfg{ chainHash: d.cfg.ChainHash, + peerPub: nodeID, syncChanUpdates: recvUpdates, channelSeries: d.cfg.ChanSeries, encodingType: encoding, @@ -1117,7 +1118,6 @@ func (d *AuthenticatedGossiper) InitSyncState(syncPeer lnpeer.Peer, return syncPeer.SendMessageLazy(false, msgs...) }, }) - copy(syncer.peerPub[:], nodeID[:]) d.peerSyncers[nodeID] = syncer syncer.Start() diff --git a/discovery/syncer.go b/discovery/syncer.go index f82c372e7..7a4f97766 100644 --- a/discovery/syncer.go +++ b/discovery/syncer.go @@ -113,6 +113,10 @@ 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 @@ -183,10 +187,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 @@ -201,9 +201,9 @@ type gossipSyncer 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 { @@ -240,7 +240,7 @@ func (g *gossipSyncer) Start() error { return nil } - log.Debugf("Starting gossipSyncer(%x)", g.peerPub[:]) + log.Debugf("Starting gossipSyncer(%x)", g.cfg.peerPub[:]) g.wg.Add(1) go g.channelGraphSyncer() @@ -274,7 +274,7 @@ func (g *gossipSyncer) channelGraphSyncer() { for { state := atomic.LoadUint32(&g.state) - log.Debugf("gossipSyncer(%x): state=%v", g.peerPub[:], + log.Debugf("gossipSyncer(%x): state=%v", g.cfg.peerPub[:], syncerState(state)) switch syncerState(state) { @@ -413,8 +413,8 @@ func (g *gossipSyncer) channelGraphSyncer() { // items. updateHorizon := time.Now().Add(-time.Hour * 1) log.Infof("gossipSyncer(%x): applying "+ - "gossipFilter(start=%v)", g.peerPub[:], - updateHorizon) + "gossipFilter(start=%v)", + g.cfg.peerPub[:], updateHorizon) g.localUpdateHorizon = &lnwire.GossipTimestampRange{ ChainHash: g.cfg.chainHash, @@ -457,7 +457,7 @@ func (g *gossipSyncer) synchronizeChanIDs() (bool, error) { // to signal that we're fully synchronized. if len(g.newChansToQuery) == 0 { log.Infof("gossipSyncer(%x): no more chans to query", - g.peerPub[:]) + g.cfg.peerPub[:]) return true, nil } @@ -480,7 +480,7 @@ func (g *gossipSyncer) synchronizeChanIDs() (bool, error) { } log.Infof("gossipSyncer(%x): querying for %v new channels", - g.peerPub[:], len(queryChunk)) + 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. @@ -502,7 +502,7 @@ func (g *gossipSyncer) processChanRangeReply(msg *lnwire.ReplyChannelRange) erro ) log.Infof("gossipSyncer(%x): buffering chan range reply of size=%v", - g.peerPub[:], len(msg.ShortChanIDs)) + 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 +510,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. @@ -531,7 +531,7 @@ func (g *gossipSyncer) processChanRangeReply(msg *lnwire.ReplyChannelRange) erro // switch straight to our terminal state. if len(newChans) == 0 { log.Infof("gossipSyncer(%x): remote peer has no new chans", - g.peerPub[:]) + g.cfg.peerPub[:]) atomic.StoreUint32(&g.state, uint32(chansSynced)) return nil @@ -543,7 +543,7 @@ func (g *gossipSyncer) processChanRangeReply(msg *lnwire.ReplyChannelRange) erro atomic.StoreUint32(&g.state, uint32(queryNewChannels)) log.Infof("gossipSyncer(%x): starting query for %v new chans", - g.peerPub[:], len(newChans)) + g.cfg.peerPub[:], len(newChans)) return nil } @@ -575,7 +575,7 @@ func (g *gossipSyncer) genChanRangeQuery() (*lnwire.QueryChannelRange, error) { } log.Infof("gossipSyncer(%x): requesting new chans from height=%v "+ - "and %v blocks after", g.peerPub[:], startHeight, + "and %v blocks after", g.cfg.peerPub[:], startHeight, math.MaxUint32-startHeight) // Finally, we'll craft the channel range query, using our starting @@ -599,7 +599,7 @@ func (g *gossipSyncer) replyPeerQueries(msg lnwire.Message) error { // where the remote peer spams us endlessly. if delay > 0 { log.Infof("gossipSyncer(%x): rate limiting gossip replies, "+ - "responding in %s", g.peerPub[:], delay) + "responding in %s", g.cfg.peerPub[:], delay) select { case <-time.After(delay): @@ -632,7 +632,7 @@ func (g *gossipSyncer) replyPeerQueries(msg lnwire.Message) error { // 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, + "num_blocks=%v", g.cfg.peerPub[:], query.FirstBlockHeight, query.NumBlocks) // Next, we'll consult the time series to obtain the set of known @@ -667,15 +667,15 @@ func (g *gossipSyncer) replyChanRangeQuery(query *lnwire.QueryChannelRange) erro channelChunk = channelRange[numChansSent:] log.Infof("gossipSyncer(%x): sending final chan "+ - "range chunk, size=%v", g.peerPub[:], len(channelChunk)) - + "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)) + "size=%v", g.cfg.peerPub[:], len(channelChunk)) } // With our chunk assembled, we'll now send to the remote peer @@ -725,12 +725,12 @@ 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[:]) + g.cfg.peerPub[:]) return nil } log.Infof("gossipSyncer(%x): fetching chan anns for %v chans", - g.peerPub[:], len(query.ShortChanIDs)) + 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 @@ -788,7 +788,7 @@ func (g *gossipSyncer) ApplyGossipFilter(filter *lnwire.GossipTimestampRange) er } log.Infof("gossipSyncer(%x): applying new update horizon: start=%v, "+ - "end=%v, backlog_size=%v", g.peerPub[:], startTime, endTime, + "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. @@ -866,7 +866,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 } @@ -921,7 +921,7 @@ func (g *gossipSyncer) FilterGossipMsgs(msgs ...msgWithSenders) { } log.Tracef("gossipSyncer(%x): filtered gossip msgs: set=%v, sent=%v", - g.peerPub[:], len(msgs), len(msgsToSend)) + g.cfg.peerPub[:], len(msgs), len(msgsToSend)) if len(msgsToSend) == 0 { return diff --git a/discovery/syncer_test.go b/discovery/syncer_test.go index 1887f0ba8..44acf4025 100644 --- a/discovery/syncer_test.go +++ b/discovery/syncer_test.go @@ -130,7 +130,7 @@ func newTestSyncer(hID lnwire.ShortChannelID, }, delayedQueryReplyInterval: 2 * time.Second, } - syncer := newGossiperSyncer(cfg) + syncer := newGossipSyncer(cfg) return msgChan, syncer, cfg.channelSeries.(*mockChannelGraphTimeSeries) } From 7e92b9a4e2a0ee990b76c2254c47a6a5ae8c0b4b Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Fri, 22 Mar 2019 19:54:46 -0700 Subject: [PATCH 03/13] discovery: export gossipSyncer --- discovery/gossiper.go | 24 +++---- discovery/syncer.go | 137 +++++++++++++++++++-------------------- discovery/syncer_test.go | 28 ++++---- 3 files changed, 91 insertions(+), 98 deletions(-) diff --git a/discovery/gossiper.go b/discovery/gossiper.go index ac954186b..994c447c2 100644 --- a/discovery/gossiper.go +++ b/discovery/gossiper.go @@ -75,7 +75,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 @@ -218,7 +218,7 @@ type AuthenticatedGossiper struct { // 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 + peerSyncers map[routing.Vertex]*GossipSyncer // reliableSender is a subsystem responsible for handling reliable // message send requests to peers. This should only be used for channels @@ -243,7 +243,7 @@ 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), + peerSyncers: make(map[routing.Vertex]*GossipSyncer), } gossiper.reliableSender = newReliableSender(&reliableSenderCfg{ @@ -463,7 +463,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, @@ -488,7 +488,7 @@ 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 { @@ -590,10 +590,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{}{} } @@ -817,7 +817,7 @@ func (d *deDupedAnnouncements) Emit() []msgWithSenders { // 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) { + *GossipSyncer, error) { target := routing.NewVertex(pub) @@ -1029,7 +1029,7 @@ func (d *AuthenticatedGossiper) networkHandler() { // 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) + syncerPeers := make(map[routing.Vertex]*GossipSyncer) for peerPub, syncer := range d.peerSyncers { syncerPeers[peerPub] = syncer } @@ -1104,7 +1104,7 @@ func (d *AuthenticatedGossiper) InitSyncState(syncPeer lnpeer.Peer, return } - log.Infof("Creating new gossipSyncer for peer=%x", nodeID[:]) + log.Infof("Creating new GossipSyncer for peer=%x", nodeID[:]) encoding := lnwire.EncodingSortedPlain syncer := newGossipSyncer(gossipSyncerCfg{ @@ -1125,12 +1125,12 @@ func (d *AuthenticatedGossiper) InitSyncState(syncPeer lnpeer.Peer, // 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. +// 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", + log.Infof("Removing GossipSyncer for peer=%x", peer.SerializeCompressed()) vertex := routing.NewVertex(peer) diff --git a/discovery/syncer.go b/discovery/syncer.go index 7a4f97766..a8eb63e5d 100644 --- a/discovery/syncer.go +++ b/discovery/syncer.go @@ -13,18 +13,18 @@ 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. +// 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 +33,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 @@ -107,7 +107,7 @@ const ( chanRangeQueryBuffer = 144 ) -// 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. @@ -148,8 +148,8 @@ 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 @@ -157,9 +157,9 @@ type gossipSyncerCfg struct { // 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 // remoteUpdateHorizon is the update horizon of the remote peer. We'll // use this to properly filter out any messages. @@ -169,7 +169,7 @@ type gossipSyncer struct { // determine if we've already sent out our update. localUpdateHorizon *lnwire.GossipTimestampRange - // state is the current state of the gossipSyncer. + // state is the current state of the GossipSyncer. // // NOTE: This variable MUST be used atomically. state uint32 @@ -201,9 +201,9 @@ type gossipSyncer struct { wg sync.WaitGroup } -// newGossipSyncer returns a new instance of the gossipSyncer populated using +// newGossipSyncer returns a new instance of the GossipSyncer populated using // the passed config. -func newGossipSyncer(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,7 +225,7 @@ func newGossipSyncer(cfg gossipSyncerCfg) *gossipSyncer { interval, cfg.maxUndelayedQueryReplies, ) - return &gossipSyncer{ + return &GossipSyncer{ cfg: cfg, rateLimiter: rateLimiter, gossipMsgs: make(chan lnwire.Message, 100), @@ -233,39 +233,30 @@ func newGossipSyncer(cfg gossipSyncerCfg) *gossipSyncer { } } -// 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.cfg.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 @@ -274,7 +265,7 @@ func (g *gossipSyncer) channelGraphSyncer() { for { state := atomic.LoadUint32(&g.state) - log.Debugf("gossipSyncer(%x): state=%v", g.cfg.peerPub[:], + log.Debugf("GossipSyncer(%x): state=%v", g.cfg.peerPub[:], syncerState(state)) switch syncerState(state) { @@ -412,7 +403,7 @@ func (g *gossipSyncer) channelGraphSyncer() { // horizon to ensure we don't miss any newer // items. updateHorizon := time.Now().Add(-time.Hour * 1) - log.Infof("gossipSyncer(%x): applying "+ + log.Infof("GossipSyncer(%x): applying "+ "gossipFilter(start=%v)", g.cfg.peerPub[:], updateHorizon) @@ -451,12 +442,12 @@ func (g *gossipSyncer) channelGraphSyncer() { // 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", + log.Infof("GossipSyncer(%x): no more chans to query", g.cfg.peerPub[:]) return true, nil } @@ -479,7 +470,7 @@ func (g *gossipSyncer) synchronizeChanIDs() (bool, error) { g.newChansToQuery = g.newChansToQuery[g.cfg.chunkSize:] } - log.Infof("gossipSyncer(%x): querying for %v new channels", + 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 @@ -493,15 +484,15 @@ 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", + 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 @@ -510,7 +501,7 @@ func (g *gossipSyncer) processChanRangeReply(msg *lnwire.ReplyChannelRange) erro return nil } - log.Infof("gossipSyncer(%x): filtering through %v chans", + 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 @@ -530,7 +521,7 @@ 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", + log.Infof("GossipSyncer(%x): remote peer has no new chans", g.cfg.peerPub[:]) atomic.StoreUint32(&g.state, uint32(chansSynced)) @@ -542,7 +533,7 @@ func (g *gossipSyncer) processChanRangeReply(msg *lnwire.ReplyChannelRange) erro g.newChansToQuery = newChans atomic.StoreUint32(&g.state, uint32(queryNewChannels)) - log.Infof("gossipSyncer(%x): starting query for %v new chans", + log.Infof("GossipSyncer(%x): starting query for %v new chans", g.cfg.peerPub[:], len(newChans)) return nil @@ -551,7 +542,7 @@ func (g *gossipSyncer) processChanRangeReply(msg *lnwire.ReplyChannelRange) erro // 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) { +func (g *GossipSyncer) genChanRangeQuery() (*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) @@ -574,7 +565,7 @@ func (g *gossipSyncer) genChanRangeQuery() (*lnwire.QueryChannelRange, error) { startHeight = uint32(newestChan.BlockHeight - chanRangeQueryBuffer) } - log.Infof("gossipSyncer(%x): requesting new chans from height=%v "+ + log.Infof("GossipSyncer(%x): requesting new chans from height=%v "+ "and %v blocks after", g.cfg.peerPub[:], startHeight, math.MaxUint32-startHeight) @@ -590,7 +581,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,7 +589,7 @@ 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, "+ + log.Infof("GossipSyncer(%x): rate limiting gossip replies, "+ "responding in %s", g.cfg.peerPub[:], delay) select { @@ -630,8 +621,8 @@ 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, "+ +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) @@ -666,7 +657,7 @@ func (g *gossipSyncer) replyChanRangeQuery(query *lnwire.QueryChannelRange) erro if isFinalChunk { channelChunk = channelRange[numChansSent:] - log.Infof("gossipSyncer(%x): sending final chan "+ + log.Infof("GossipSyncer(%x): sending final chan "+ "range chunk, size=%v", g.cfg.peerPub[:], len(channelChunk)) } else { @@ -674,7 +665,7 @@ func (g *gossipSyncer) replyChanRangeQuery(query *lnwire.QueryChannelRange) erro // sized to the proper chunk size. channelChunk = channelRange[numChansSent : numChansSent+g.cfg.chunkSize] - log.Infof("gossipSyncer(%x): sending range chunk of "+ + log.Infof("GossipSyncer(%x): sending range chunk of "+ "size=%v", g.cfg.peerPub[:], len(channelChunk)) } @@ -707,7 +698,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,12 +715,12 @@ 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", + 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", + 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 @@ -766,7 +757,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,7 +778,7 @@ func (g *gossipSyncer) ApplyGossipFilter(filter *lnwire.GossipTimestampRange) er return err } - log.Infof("gossipSyncer(%x): applying new update horizon: start=%v, "+ + log.Infof("GossipSyncer(%x): applying new update horizon: start=%v, "+ "end=%v, backlog_size=%v", g.cfg.peerPub[:], startTime, endTime, len(newUpdatestoSend)) @@ -813,17 +804,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 @@ -920,7 +913,7 @@ func (g *gossipSyncer) FilterGossipMsgs(msgs ...msgWithSenders) { } } - log.Tracef("gossipSyncer(%x): filtered gossip msgs: set=%v, sent=%v", + log.Tracef("GossipSyncer(%x): filtered gossip msgs: set=%v, sent=%v", g.cfg.peerPub[:], len(msgs), len(msgsToSend)) if len(msgsToSend) == 0 { @@ -932,7 +925,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 +933,7 @@ func (g *gossipSyncer) ProcessQueryMsg(msg lnwire.Message, peerQuit <-chan struc } } -// SyncerState returns the current syncerState of the target gossipSyncer. -func (g *gossipSyncer) SyncState() syncerState { +// syncState returns the current syncerState of the target GossipSyncer. +func (g *GossipSyncer) syncState() syncerState { return syncerState(atomic.LoadUint32(&g.state)) } diff --git a/discovery/syncer_test.go b/discovery/syncer_test.go index 44acf4025..e42e3b4a6 100644 --- a/discovery/syncer_test.go +++ b/discovery/syncer_test.go @@ -116,7 +116,7 @@ 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{ @@ -140,7 +140,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 +185,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 +315,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 +413,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 +464,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 +718,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( @@ -753,7 +753,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 +827,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 +860,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 +878,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 +997,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 +1390,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 +1730,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, From 8d7c0a98998b8489cd525f603485641325cfdc1d Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Fri, 22 Mar 2019 19:55:01 -0700 Subject: [PATCH 04/13] discovery: replace GossipSyncer syncChanUpdates flag with SyncerType In this commit, we introduce a new type: SyncerType. This type denotes the type of sync a GossipSyncer is currently under. We only introduce the two possible entry states, ActiveSync and PassiveSync. An ActiveSync GossipSyncer will exchange channels with the remote peer and receive new graph updates from them, while a PassiveSync GossipSyncer will not and will only response to the remote peer's queries. This commit does not modify the behavior and is only meant to be a refactor. --- discovery/gossiper.go | 16 +++++--- discovery/syncer.go | 86 +++++++++++++++++++++++++++++++++------- discovery/syncer_test.go | 7 ++-- 3 files changed, 84 insertions(+), 25 deletions(-) diff --git a/discovery/gossiper.go b/discovery/gossiper.go index 994c447c2..b1d8c1463 100644 --- a/discovery/gossiper.go +++ b/discovery/gossiper.go @@ -1108,16 +1108,20 @@ func (d *AuthenticatedGossiper) InitSyncState(syncPeer lnpeer.Peer, encoding := lnwire.EncodingSortedPlain syncer := newGossipSyncer(gossipSyncerCfg{ - chainHash: d.cfg.ChainHash, - peerPub: nodeID, - syncChanUpdates: recvUpdates, - channelSeries: d.cfg.ChanSeries, - encodingType: encoding, - chunkSize: encodingTypeToChunkSize[encoding], + chainHash: d.cfg.ChainHash, + peerPub: nodeID, + channelSeries: d.cfg.ChanSeries, + encodingType: encoding, + chunkSize: encodingTypeToChunkSize[encoding], sendToPeer: func(msgs ...lnwire.Message) error { return syncPeer.SendMessageLazy(false, msgs...) }, }) + + if !recvUpdates { + syncer.syncType = uint32(PassiveSync) + } + d.peerSyncers[nodeID] = syncer syncer.Start() diff --git a/discovery/syncer.go b/discovery/syncer.go index a8eb63e5d..295be2ac5 100644 --- a/discovery/syncer.go +++ b/discovery/syncer.go @@ -13,6 +13,39 @@ import ( "golang.org/x/time/rate" ) +// 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. @@ -161,6 +194,17 @@ 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. remoteUpdateHorizon *lnwire.GossipTimestampRange @@ -169,11 +213,6 @@ 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 - // gossipMsgs is a channel that all messages from the target peer will // be sent over. gossipMsgs chan lnwire.Message @@ -264,9 +303,11 @@ func (g *GossipSyncer) channelGraphSyncer() { // * needed if we want to sync chan state very few blocks? for { - state := atomic.LoadUint32(&g.state) - log.Debugf("GossipSyncer(%x): state=%v", g.cfg.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 @@ -293,7 +334,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 @@ -350,13 +391,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 @@ -373,7 +414,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 } @@ -395,7 +436,7 @@ func (g *GossipSyncer) channelGraphSyncer() { // 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 { + if g.localUpdateHorizon == nil && syncType == ActiveSync { // TODO(roasbeef): query DB for most recent // update? @@ -524,14 +565,14 @@ func (g *GossipSyncer) processChanRangeReply(msg *lnwire.ReplyChannelRange) erro 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.cfg.peerPub[:], len(newChans)) @@ -933,7 +974,22 @@ func (g *GossipSyncer) ProcessQueryMsg(msg lnwire.Message, peerQuit <-chan struc } } +// 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)) } + +// 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)) +} diff --git a/discovery/syncer_test.go b/discovery/syncer_test.go index e42e3b4a6..3f01c8b06 100644 --- a/discovery/syncer_test.go +++ b/discovery/syncer_test.go @@ -120,10 +120,9 @@ func newTestSyncer(hID lnwire.ShortChannelID, 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 From acc42c1b6875de0464b20f402dd123bef8b67f41 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Fri, 22 Mar 2019 19:55:16 -0700 Subject: [PATCH 05/13] discovery: set GossipSyncer update horizon to current time With the introduction of the gossip sync manager in a later commit, retrieving the backlog of updates within the last hour is no longer necessary as we'll be forcing full syncs periodically. --- discovery/syncer.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/discovery/syncer.go b/discovery/syncer.go index 295be2ac5..6a5c3cb1e 100644 --- a/discovery/syncer.go +++ b/discovery/syncer.go @@ -437,13 +437,7 @@ func (g *GossipSyncer) channelGraphSyncer() { // we want to receive real-time channel updates, we'll // do so now. if g.localUpdateHorizon == nil && syncType == ActiveSync { - // 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) + updateHorizon := time.Now() log.Infof("GossipSyncer(%x): applying "+ "gossipFilter(start=%v)", g.cfg.peerPub[:], updateHorizon) From ca4fbd598cb9358366656fb9fc612fdce6053281 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Fri, 22 Mar 2019 19:55:32 -0700 Subject: [PATCH 06/13] discovery: introduce GossipSyncer sync transitions In this commit, we introduce the ability for GossipSyncer's to transition their sync type. This allows us to be more flexible with our gossip syncers, as we can now prevent them from receiving new graph updates at any time. It's now possible to transition between the different sync types, as long as the GossipSyncer has reached its terminal chansSynced sync state. Certain transitions require some additional wire messages to be sent, like in the case of an ActiveSync GossipSyncer transitioning to a PassiveSync type. --- discovery/syncer.go | 205 +++++++++++++++++++++++++++++++-------- discovery/syncer_test.go | 144 ++++++++++++++++++++++++++- 2 files changed, 309 insertions(+), 40 deletions(-) diff --git a/discovery/syncer.go b/discovery/syncer.go index 6a5c3cb1e..7ef81e838 100644 --- a/discovery/syncer.go +++ b/discovery/syncer.go @@ -87,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 { @@ -121,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 @@ -131,14 +140,22 @@ 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 // needs to carry out its duties. @@ -213,6 +230,12 @@ type GossipSyncer struct { // determine if we've already sent out our update. localUpdateHorizon *lnwire.GossipTimestampRange + // 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 + // gossipMsgs is a channel that all messages from the target peer will // be sent over. gossipMsgs chan lnwire.Message @@ -265,10 +288,11 @@ func newGossipSyncer(cfg gossipSyncerCfg) *GossipSyncer { ) return &GossipSyncer{ - cfg: cfg, - rateLimiter: rateLimiter, - gossipMsgs: make(chan lnwire.Message, 100), - quit: make(chan struct{}), + cfg: cfg, + rateLimiter: rateLimiter, + syncTransitionReqs: make(chan *syncTransitionReq), + gossipMsgs: make(chan lnwire.Message, 100), + quit: make(chan struct{}), } } @@ -298,10 +322,6 @@ func (g *GossipSyncer) Stop() { 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 := g.syncState() syncType := g.SyncType() @@ -437,25 +457,19 @@ func (g *GossipSyncer) channelGraphSyncer() { // we want to receive real-time channel updates, we'll // do so now. if g.localUpdateHorizon == nil && syncType == ActiveSync { - updateHorizon := time.Now() - log.Infof("GossipSyncer(%x): applying "+ - "gossipFilter(start=%v)", - g.cfg.peerPub[:], updateHorizon) - - g.localUpdateHorizon = &lnwire.GossipTimestampRange{ - ChainHash: g.cfg.chainHash, - FirstTimestamp: uint32(updateHorizon.Unix()), - TimestampRange: math.MaxUint32, - } - err := g.cfg.sendToPeer(g.localUpdateHorizon) + 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) @@ -464,6 +478,9 @@ func (g *GossipSyncer) channelGraphSyncer() { "query: %v", err) } + case req := <-g.syncTransitionReqs: + req.errChan <- g.handleSyncTransition(req) + case <-g.quit: return } @@ -471,6 +488,37 @@ 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 @@ -978,6 +1026,85 @@ func (g *GossipSyncer) syncState() syncerState { return syncerState(atomic.LoadUint32(&g.state)) } +// 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 + + // 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)) diff --git a/discovery/syncer_test.go b/discovery/syncer_test.go index 3f01c8b06..f0c6f59ba 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 ( @@ -1940,3 +1942,143 @@ 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) + }) + } +} From 042241dc48b99e81e44d7673e092ab59872195d6 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Fri, 29 Mar 2019 12:46:11 -0700 Subject: [PATCH 07/13] discovery: allow gossip syncer to perform historical syncs In this commit, we introduce the ability for gossip syncers to perform historical syncs. This allows us to reconcile any channels we're missing that the remote peer has starting from the genesis block of the chain. This commit serves as a prerequisite to the SyncManager, introduced in a later commit, where we'll be able to make spot checks by performing historical syncs with peers to ensure we have as much of the graph as possible. --- discovery/syncer.go | 73 ++++++++++++++++++++++++++++++++++------ discovery/syncer_test.go | 62 ++++++++++++++++++++++++++++++++-- 2 files changed, 123 insertions(+), 12 deletions(-) diff --git a/discovery/syncer.go b/discovery/syncer.go index 7ef81e838..a9818ae0b 100644 --- a/discovery/syncer.go +++ b/discovery/syncer.go @@ -236,6 +236,20 @@ type GossipSyncer struct { // 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. gossipMsgs chan lnwire.Message @@ -291,6 +305,7 @@ func newGossipSyncer(cfg gossipSyncerCfg) *GossipSyncer { cfg: cfg, rateLimiter: rateLimiter, syncTransitionReqs: make(chan *syncTransitionReq), + historicalSyncReqs: make(chan struct{}), gossipMsgs: make(chan lnwire.Message, 100), quit: make(chan struct{}), } @@ -338,7 +353,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) @@ -481,6 +498,9 @@ func (g *GossipSyncer) channelGraphSyncer() { case req := <-g.syncTransitionReqs: req.errChan <- g.handleSyncTransition(req) + case <-g.historicalSyncReqs: + g.handleHistoricalSync() + case <-g.quit: return } @@ -624,8 +644,11 @@ func (g *GossipSyncer) processChanRangeReply(msg *lnwire.ReplyChannelRange) erro // 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) @@ -633,17 +656,17 @@ 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) } @@ -1080,6 +1103,10 @@ func (g *GossipSyncer) handleSyncTransition(req *syncTransitionReq) error { 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 @@ -1114,3 +1141,29 @@ func (g *GossipSyncer) setSyncType(syncType SyncerType) { 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 f0c6f59ba..3850e3641 100644 --- a/discovery/syncer_test.go +++ b/discovery/syncer_test.go @@ -730,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) } @@ -743,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) } } @@ -2082,3 +2097,46 @@ func TestGossipSyncerSyncTransitions(t *testing.T) { }) } } + +// 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") + } +} From e075817e443e03b99effa0d7e558daf242946a7d Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Fri, 22 Mar 2019 19:55:47 -0700 Subject: [PATCH 08/13] discovery: introduce GossipSyncer signal delivery of chansSynced state In this commit, we introduce another feature to the GossipSyncer in which it can deliver a signal to an external caller once it reaches its terminal chansSynced state. This is yet to be used, but will serve useful with a round-robin sync mechanism, where we wait for to finish syncing with a specific peer before moving on to the next. --- discovery/syncer.go | 29 +++++++++++++++++++++++ discovery/syncer_test.go | 50 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/discovery/syncer.go b/discovery/syncer.go index a9818ae0b..8ec322a58 100644 --- a/discovery/syncer.go +++ b/discovery/syncer.go @@ -271,6 +271,10 @@ 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{} @@ -470,6 +474,13 @@ 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. @@ -1049,6 +1060,24 @@ 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. // diff --git a/discovery/syncer_test.go b/discovery/syncer_test.go index 3850e3641..ee5719d80 100644 --- a/discovery/syncer_test.go +++ b/discovery/syncer_test.go @@ -2140,3 +2140,53 @@ func TestGossipSyncerHistoricalSync(t *testing.T) { 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") + } +} From a188657b2f5b3a1ae629fbdd03d87bf4594aa989 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Fri, 22 Mar 2019 19:56:03 -0700 Subject: [PATCH 09/13] discovery: introduce gossiper SyncManager subsystem In this commit, we introduce a new subsystem for the gossiper: the SyncManager. This subsystem is a major overhaul on the way the daemon performs the graph query sync state machine with peers. Along with this subsystem, we also introduce the concept of an active syncer. An active syncer is simply a GossipSyncer currently operating under an ActiveSync sync type. Before this commit, all GossipSyncer's would act as active syncers, which means that we were receiving new graph updates from all of them. This isn't necessary, as it greatly increases bandwidth usage as the network grows. The SyncManager changes this by requiring a specific number of active syncers. Once we reach this specified number, any future peers will have a GossipSyncer with a PassiveSync sync type. It is responsible for three main things: 1. Choosing different peers randomly to receive graph updates from to ensure we don't only receive them from the same set of peers. 2. Choosing different peers to force a historical sync with to ensure we have as much of the public network as possible. The first syncer registered with the manager will also attempt a historical sync. 3. Managing an in-order queue of active syncers where the next cannot be started until the current one has completed its state machine to ensure they don't overlap and request the same set of channels, which significantly reduces bandwidth usage and addresses a number of issues. --- discovery/sync_manager.go | 735 +++++++++++++++++++++++++++++++++ discovery/sync_manager_test.go | 549 ++++++++++++++++++++++++ 2 files changed, 1284 insertions(+) create mode 100644 discovery/sync_manager.go create mode 100644 discovery/sync_manager_test.go 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) +} From 80b84eef9cc7b33b112dcde597fe68ca136a9f40 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Fri, 22 Mar 2019 19:56:18 -0700 Subject: [PATCH 10/13] config+peer: replace NoChanUpdates flag with NumGraphSyncPeers In this commit, we replace the NoChanUpdates flag with a flag that allows us to specify the number of peers we want to actively receive new graph updates from. This will be required when integrating the new gossiper SyncManager subsystem with the rest of lnd. --- config.go | 3 ++- peer.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) 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/peer.go b/peer.go index 69b4cf082..f4f029fa0 100644 --- a/peer.go +++ b/peer.go @@ -402,7 +402,7 @@ func (p *peer) initGossipSync() { // // TODO(roasbeef): craft s.t. we only get updates from a few // peers - recvUpdates := !cfg.NoChanUpdates + recvUpdates := cfg.NumGraphSyncPeers != 0 // Register the this peer's for gossip syncer with the gossiper. // This is blocks synchronously to ensure the gossip syncer is From 70be81274725983bc60239df2fc90991b54a73a9 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Fri, 22 Mar 2019 19:56:33 -0700 Subject: [PATCH 11/13] discovery+server: use new gossiper's SyncManager subsystem --- discovery/gossiper.go | 177 ++++++++++++++----------------------- discovery/gossiper_test.go | 41 +++++---- discovery/syncer.go | 2 - peer.go | 15 ++-- server.go | 37 ++++---- 5 files changed, 116 insertions(+), 156 deletions(-) diff --git a/discovery/gossiper.go b/discovery/gossiper.go index b1d8c1463..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 ( @@ -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() @@ -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 } @@ -490,22 +519,20 @@ func (d *AuthenticatedGossiper) ProcessRemoteAnnouncement(msg lnwire.Message, // If a peer is updating its current update horizon, then we'll dispatch // 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 @@ -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,66 +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 := newGossipSyncer(gossipSyncerCfg{ - chainHash: d.cfg.ChainHash, - peerPub: nodeID, - channelSeries: d.cfg.ChanSeries, - encodingType: encoding, - chunkSize: encodingTypeToChunkSize[encoding], - sendToPeer: func(msgs ...lnwire.Message) error { - return syncPeer.SendMessageLazy(false, msgs...) - }, - }) - - if !recvUpdates { - syncer.syncType = uint32(PassiveSync) - } - - 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 +func (d *AuthenticatedGossiper) PruneSyncState(peer routing.Vertex) { + d.syncMgr.PruneSyncState(peer) } // isRecentlyRejectedMsg returns true if we recently rejected a message, and @@ -2518,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/syncer.go b/discovery/syncer.go index 8ec322a58..1157cd4bb 100644 --- a/discovery/syncer.go +++ b/discovery/syncer.go @@ -205,8 +205,6 @@ type gossipSyncerCfg struct { // 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 sync.Once stopped sync.Once diff --git a/peer.go b/peer.go index f4f029fa0..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.NumGraphSyncPeers != 0 - // 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/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 From 8b6a9bb5d352f66d53a8358ca2d871bee8894b86 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Fri, 22 Mar 2019 19:56:48 -0700 Subject: [PATCH 12/13] discovery: make timestamp range check inclusive within FilterGossipMsgs As required by the spec: > SHOULD send all gossip messages whose timestamp is greater or equal to first_timestamp, and less than first_timestamp plus timestamp_range. --- discovery/syncer.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/discovery/syncer.go b/discovery/syncer.go index 1157cd4bb..791e31c97 100644 --- a/discovery/syncer.go +++ b/discovery/syncer.go @@ -966,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)) From ca016953301e95c832a4216ca87b304ec5bb6c70 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Fri, 22 Mar 2019 19:57:03 -0700 Subject: [PATCH 13/13] rpc: expose peer's GossipSyncer sync type --- lnrpc/rpc.pb.go | 1294 +++++++++++++++++++++------------------- lnrpc/rpc.proto | 20 + lnrpc/rpc.swagger.json | 14 + rpcserver.go | 33 +- 4 files changed, 735 insertions(+), 626 deletions(-) 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/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)