From 3347431923f88d3bbb1551edb5876a88ac8fb1bf Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Wed, 18 Apr 2018 14:19:58 +0200 Subject: [PATCH 01/31] lntest/harness: handle predicate error --- lntest/harness.go | 1 + 1 file changed, 1 insertion(+) diff --git a/lntest/harness.go b/lntest/harness.go index a582894d..e6e68580 100644 --- a/lntest/harness.go +++ b/lntest/harness.go @@ -770,6 +770,7 @@ func (n *NetworkHarness) CloseChannel(ctx context.Context, return func() bool { channel, err := filterChannel(node, chanPoint) if err != nil { + return false } return channel.Active From 9b9fada6751e22cd0391174b346cc9c5daf64cf7 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Tue, 13 Mar 2018 17:10:58 +0100 Subject: [PATCH 02/31] contractcourt: remove unecessary contractRes check This commit removes a short circuit checking if the contract resolver after a unilateral close is empty. After removing this, the state machine will advance the state from StateDefault->ContractClosed, in which the stateCallback will be called, logging the state needed to advance. Since this logged state is empty, the state machine will go directly to StateFullyResolved, which will trigger the MarkChannelResolved call. This means the behaviour is kept. --- contractcourt/channel_arbitrator.go | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/contractcourt/channel_arbitrator.go b/contractcourt/channel_arbitrator.go index 5cc9d9fe..42a31344 100644 --- a/contractcourt/channel_arbitrator.go +++ b/contractcourt/channel_arbitrator.go @@ -1378,17 +1378,6 @@ func (c *ChannelArbitrator) channelAttendant(bestHeight int32, CommitResolution: uniClosure.CommitResolution, HtlcResolutions: *uniClosure.HtlcResolutions, } - if contractRes.IsEmpty() { - log.Infof("ChannelArbitrator(%v): contract "+ - "resolutions empty, exiting", c.cfg.ChanPoint) - - err := c.cfg.MarkChannelResolved() - if err != nil { - log.Errorf("unable to resolve "+ - "contract: %v", err) - } - return - } // TODO(roasbeef): modify signal to also detect // cooperative closures? From 0b2e6d17761641ace657c0f7d48db5bb324eef0b Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Wed, 11 Apr 2018 16:09:38 +0200 Subject: [PATCH 03/31] contractcourt/channel_arbitrator: removed unused bestHash parameter --- contractcourt/channel_arbitrator.go | 31 +++++++++++++---------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/contractcourt/channel_arbitrator.go b/contractcourt/channel_arbitrator.go index 42a31344..d3e9d047 100644 --- a/contractcourt/channel_arbitrator.go +++ b/contractcourt/channel_arbitrator.go @@ -10,7 +10,6 @@ import ( "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" - "github.com/roasbeef/btcd/chaincfg/chainhash" "github.com/roasbeef/btcd/wire" "github.com/roasbeef/btcutil" ) @@ -239,7 +238,7 @@ func (c *ChannelArbitrator) Start() error { log.Infof("ChannelArbitrator(%v): starting state=%v", c.cfg.ChanPoint, c.state) - bestHash, bestHeight, err := c.cfg.ChainIO.GetBestBlock() + _, bestHeight, err := c.cfg.ChainIO.GetBestBlock() if err != nil { return err } @@ -248,7 +247,7 @@ func (c *ChannelArbitrator) Start() error { // on-chain state, and our set of active contracts. startingState := c.state nextState, _, err := c.advanceState( - uint32(bestHeight), bestHash, chainTrigger, nil, + uint32(bestHeight), chainTrigger, nil, ) if err != nil { return err @@ -280,7 +279,7 @@ func (c *ChannelArbitrator) Start() error { // TODO(roasbeef): cancel if breached c.wg.Add(1) - go c.channelAttendant(bestHeight, bestHash) + go c.channelAttendant(bestHeight) return nil } @@ -352,7 +351,7 @@ func (t transitionTrigger) String() string { // the appropriate state transition if necessary. The next state we transition // to is returned, Additionally, if the next transition results in a commitment // broadcast, the commitment transaction itself is returned. -func (c *ChannelArbitrator) stateStep(bestHeight uint32, bestHash *chainhash.Hash, +func (c *ChannelArbitrator) stateStep(bestHeight uint32, trigger transitionTrigger) (ArbitratorState, *wire.MsgTx, error) { var ( @@ -364,9 +363,9 @@ func (c *ChannelArbitrator) stateStep(bestHeight uint32, bestHash *chainhash.Has // If we're in the default state, then we'll check our set of actions // to see if while we were down, conditions have changed. case StateDefault: - log.Debugf("ChannelArbitrator(%v): new block (height=%v, "+ - "hash=%v) examining active HTLC's", - c.cfg.ChanPoint, bestHeight, bestHash) + log.Debugf("ChannelArbitrator(%v): new block (height=%v) "+ + "examining active HTLC's", c.cfg.ChanPoint, + bestHeight) // As a new block has been connected to the end of the main // chain, we'll check to see if we need to make any on-chain @@ -623,8 +622,8 @@ func (c *ChannelArbitrator) stateStep(bestHeight uint32, bestHash *chainhash.Has // param is a callback that allows the caller to execute an arbitrary action // after each state transition. func (c *ChannelArbitrator) advanceState(currentHeight uint32, - bestHash *chainhash.Hash, trigger transitionTrigger, - stateCallback func(ArbitratorState) error) (ArbitratorState, *wire.MsgTx, error) { + trigger transitionTrigger, stateCallback func(ArbitratorState) error) ( + ArbitratorState, *wire.MsgTx, error) { var ( priorState ArbitratorState @@ -640,7 +639,7 @@ func (c *ChannelArbitrator) advanceState(currentHeight uint32, priorState = c.state nextState, closeTx, err := c.stateStep( - currentHeight, bestHash, trigger, + currentHeight, trigger, ) if err != nil { log.Errorf("unable to advance state: %v", err) @@ -1278,8 +1277,7 @@ func (c *ChannelArbitrator) UpdateContractSignals(newSignals *ContractSignals) { // Nursery for incubation, and ultimate sweeping. // // NOTE: This MUST be run as a goroutine. -func (c *ChannelArbitrator) channelAttendant(bestHeight int32, - bestHash *chainhash.Hash) { +func (c *ChannelArbitrator) channelAttendant(bestHeight int32) { // TODO(roasbeef): tell top chain arb we're done defer c.wg.Done() @@ -1295,7 +1293,6 @@ func (c *ChannelArbitrator) channelAttendant(bestHeight int32, return } bestHeight = blockEpoch.Height - bestHash = blockEpoch.Hash // If we're not in the default state, then we can // ignore this signal as we're waiting for contract @@ -1307,7 +1304,7 @@ func (c *ChannelArbitrator) channelAttendant(bestHeight int32, // Now that a new block has arrived, we'll attempt to // advance our state forward. nextState, _, err := c.advanceState( - uint32(bestHeight), bestHash, chainTrigger, nil, + uint32(bestHeight), chainTrigger, nil, ) if err != nil { log.Errorf("unable to advance state: %v", err) @@ -1409,7 +1406,7 @@ func (c *ChannelArbitrator) channelAttendant(bestHeight int32, // We'll now advance our state machine until it reaches // a terminal state. _, _, err := c.advanceState( - uint32(bestHeight), bestHash, + uint32(bestHeight), remotePeerTrigger, stateCb, ) if err != nil { @@ -1455,7 +1452,7 @@ func (c *ChannelArbitrator) channelAttendant(bestHeight int32, } nextState, closeTx, err := c.advanceState( - uint32(bestHeight), bestHash, userTrigger, nil, + uint32(bestHeight), userTrigger, nil, ) if err != nil { log.Errorf("unable to advance state: %v", err) From 24e8bace9ceb29e23c47d288ddaa66828cb5dbbd Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Tue, 3 Apr 2018 14:46:52 +0200 Subject: [PATCH 04/31] contractcourt/channel_arbitrator: add missing return after forceClose request --- contractcourt/channel_arbitrator.go | 1 + 1 file changed, 1 insertion(+) diff --git a/contractcourt/channel_arbitrator.go b/contractcourt/channel_arbitrator.go index d3e9d047..949fe5e1 100644 --- a/contractcourt/channel_arbitrator.go +++ b/contractcourt/channel_arbitrator.go @@ -1476,6 +1476,7 @@ func (c *ChannelArbitrator) channelAttendant(bestHeight int32) { log.Infof("ChannelArbitrator(%v): all "+ "contracts resolved, exiting", c.cfg.ChanPoint) + return } case <-c.quit: From 3d0cb166776024fd7893545051b15c7fe20680f8 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Wed, 11 Apr 2018 15:16:50 +0200 Subject: [PATCH 05/31] lnwallet/channel: clearify that the spendDetails is spending funding outpoint --- lnwallet/channel.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 4042f40e..3a32fcfe 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -4650,7 +4650,7 @@ type CommitOutputResolution struct { // had any outgoing HTLC's within the commitment transaction, then an // OutgoingHtlcResolution for each output will included. type UnilateralCloseSummary struct { - // SpendDetail is a struct that describes how and when the commitment + // SpendDetail is a struct that describes how and when the funding // output was spent. *chainntnfs.SpendDetail From 7bacdd65dc363e4d87cb29dc1970ab31816d2f59 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 16 Mar 2018 13:58:34 +0100 Subject: [PATCH 06/31] contractcourt: define StateCommitmentBroadcasted --- contractcourt/briefcase.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/contractcourt/briefcase.go b/contractcourt/briefcase.go index e9c8acc3..d0b8db97 100644 --- a/contractcourt/briefcase.go +++ b/contractcourt/briefcase.go @@ -112,13 +112,19 @@ const ( // so yet. StateBroadcastCommit ArbitratorState = 1 + // StateCommitmentBroadcasted is a state that indicates that the + // attendant has broadcasted the commitment transaction, and is now + // waiting for it to confirm. + StateCommitmentBroadcasted ArbitratorState = 6 + // StateContractClosed is a state that indicates the contract has - // already been "closed". At this point, we can now examine our active - // contracts, in order to create the proper resolver for each one. + // already been "closed", meaning the commitment is confirmed on chain. + // At this point, we can now examine our active contracts, in order to + // create the proper resolver for each one. StateContractClosed ArbitratorState = 2 // StateWaitingFullResolution is a state that indicates that the - // commitment transaction has been broadcast, and the attendant is now + // commitment transaction has been confirmed, and the attendant is now // waiting for all unresolved contracts to be fully resolved. StateWaitingFullResolution ArbitratorState = 3 @@ -142,6 +148,9 @@ func (a ArbitratorState) String() string { case StateBroadcastCommit: return "StateBroadcastCommit" + case StateCommitmentBroadcasted: + return "StateCommitmentBroadcasted" + case StateContractClosed: return "StateContractClosed" From 49f4a70e5a84ac9000bae910dd772f695d57eb70 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 16 Mar 2018 14:08:37 +0100 Subject: [PATCH 07/31] contractcourt/chain_watcher: add dispatchLocalForceClose This commit adds a new method dispatchLocalClose, which will be called in case our commitment is detected to spend the funding transaction. In this case LocalUnilateralCloseInfo will be sent on the LocalUnilateralClosure channel to all subscribers. The UnilateralClosure channel is renamed to RemoteUnilateralClosure for consistency. --- contractcourt/chain_watcher.go | 79 ++++++++++++++++++++++++++++------ 1 file changed, 65 insertions(+), 14 deletions(-) diff --git a/contractcourt/chain_watcher.go b/contractcourt/chain_watcher.go index b3dfd929..1fa32218 100644 --- a/contractcourt/chain_watcher.go +++ b/contractcourt/chain_watcher.go @@ -16,6 +16,13 @@ import ( "github.com/roasbeef/btcutil" ) +// LocalUnilateralCloseInfo encapsulates all the informnation we need to act +// on a local force close that gets confirmed. +type LocalUnilateralCloseInfo struct { + *chainntnfs.SpendDetail + *lnwallet.LocalForceCloseSummary +} + // ChainEventSubscription is a struct that houses a subscription to be notified // for any on-chain events related to a channel. There are three types of // possible on-chain events: a cooperative channel closure, a unilateral @@ -25,13 +32,16 @@ type ChainEventSubscription struct { // ChanPoint is that channel that chain events will be dispatched for. ChanPoint wire.OutPoint - // UnilateralClosure is a channel that will be sent upon in the event that - // the remote party broadcasts their latest version of the commitment - // transaction. - UnilateralClosure chan *lnwallet.UnilateralCloseSummary + // RemoteUnilateralClosure is a channel that will be sent upon in the + // event that the remote party's commitment transaction is confirmed. + RemoteUnilateralClosure chan *lnwallet.UnilateralCloseSummary - // CooperativeClosure is a signal that will be sent upon once a cooperative - // channel closure has been detected. + // LocalUnilateralClosure is a channel that will be sent upon in the + // event that our commitment transaction is confirmed. + LocalUnilateralClosure chan *LocalUnilateralCloseInfo + + // CooperativeClosure is a signal that will be sent upon once a + // cooperative channel closure has been detected confirmed. // // TODO(roasbeef): or something else CooperativeClosure chan struct{} @@ -226,10 +236,11 @@ func (c *chainWatcher) SubscribeChannelEvents(syncDispatch bool) *ChainEventSubs clientID, c.chanState.FundingOutpoint) sub := &ChainEventSubscription{ - ChanPoint: c.chanState.FundingOutpoint, - UnilateralClosure: make(chan *lnwallet.UnilateralCloseSummary, 1), - CooperativeClosure: make(chan struct{}, 1), - ContractBreach: make(chan *lnwallet.BreachRetribution, 1), + ChanPoint: c.chanState.FundingOutpoint, + RemoteUnilateralClosure: make(chan *lnwallet.UnilateralCloseSummary, 1), + LocalUnilateralClosure: make(chan *LocalUnilateralCloseInfo, 1), + CooperativeClosure: make(chan struct{}, 1), + ContractBreach: make(chan *lnwallet.BreachRetribution, 1), Cancel: func() { c.Lock() delete(c.clientSubscriptions, clientID) @@ -307,6 +318,13 @@ func (c *chainWatcher) closeObserver(spendNtfn *chainntnfs.SpendEvent) { &commitmentHash, ) if isOurCommitment { + if err := c.dispatchLocalForceClose( + commitSpend, *localCommit, + ); err != nil { + log.Errorf("unable to handle local"+ + "close for chan_point=%v: %v", + c.chanState.FundingOutpoint, err) + } return } @@ -349,7 +367,7 @@ func (c *chainWatcher) closeObserver(spendNtfn *chainntnfs.SpendEvent) { // has a fail crash _after_ accepting the new state, // but _before_ sending their signature to us. case broadcastStateNum >= remoteStateNum: - if err := c.dispatchRemoteClose( + if err := c.dispatchRemoteForceClose( commitSpend, *remoteCommit, ); err != nil { log.Errorf("unable to handle remote "+ @@ -500,12 +518,45 @@ func (c *chainWatcher) dispatchCooperativeClose(commitSpend *chainntnfs.SpendDet } -// dispatchRemoteClose processes a detected unilateral channel closure by the +// dispatchLocalForceClose processes a unilateral close by us being confirmed. +func (c *chainWatcher) dispatchLocalForceClose( + commitSpend *chainntnfs.SpendDetail, + localCommit channeldb.ChannelCommitment) error { + + log.Infof("Local unilateral close of ChannelPoint(%v) "+ + "detected", c.chanState.FundingOutpoint) + + forceClose, err := lnwallet.NewLocalForceCloseSummary( + c.chanState, c.signer, c.pCache, commitSpend.SpendingTx, + localCommit, + ) + if err != nil { + return err + } + + // With the event processed, we'll now notify all subscribers of the + // event. + closeInfo := &LocalUnilateralCloseInfo{commitSpend, forceClose} + c.Lock() + for _, sub := range c.clientSubscriptions { + select { + case sub.LocalUnilateralClosure <- closeInfo: + case <-c.quit: + c.Unlock() + return fmt.Errorf("exiting") + } + } + c.Unlock() + + return nil +} + +// dispatchRemoteForceClose processes a detected unilateral channel closure by the // remote party. This function will prepare a UnilateralCloseSummary which will // then be sent to any subscribers allowing them to resolve all our funds in // the channel on chain. Once this close summary is prepared, all registered // subscribers will receive a notification of this event. -func (c *chainWatcher) dispatchRemoteClose(commitSpend *chainntnfs.SpendDetail, +func (c *chainWatcher) dispatchRemoteForceClose(commitSpend *chainntnfs.SpendDetail, remoteCommit channeldb.ChannelCommitment) error { log.Infof("Unilateral close of ChannelPoint(%v) "+ @@ -538,7 +589,7 @@ func (c *chainWatcher) dispatchRemoteClose(commitSpend *chainntnfs.SpendDetail, // * get ACK from the consumer of the ntfn before writing to disk? // * no harm in repeated ntfns: at least once semantics select { - case sub.UnilateralClosure <- uniClose: + case sub.RemoteUnilateralClosure <- uniClose: case <-c.quit: c.Unlock() return fmt.Errorf("exiting") From 2fdc5992a4339a5e289984e218381b031ba65456 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 16 Mar 2018 15:18:50 +0100 Subject: [PATCH 08/31] breacharbiter: rename UnilateralClose -> RemoteUnilateralClose Also ensure we exit on LocalUnilateralClose. --- breacharbiter.go | 48 +++++++++++++++++++++--------------------------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/breacharbiter.go b/breacharbiter.go index 7663a9bd..33e1e3e7 100644 --- a/breacharbiter.go +++ b/breacharbiter.go @@ -696,6 +696,22 @@ func (b *breachArbiter) breachObserver( brarLog.Debugf("Breach observer for ChannelPoint(%v) started ", chanPoint) + gracefullyExit := func() { + // Launch a goroutine to cancel out this contract within the + // breachArbiter's main goroutine. + b.wg.Add(1) + go func() { + defer b.wg.Done() + + select { + case b.settledContracts <- chanPoint: + case <-b.quit: + } + }() + + b.cfg.CloseLink(&chanPoint, htlcswitch.CloseBreach) + } + select { // A read from this channel indicates that the contract has been // settled cooperatively so we exit as our duties are no longer needed. @@ -704,36 +720,14 @@ func (b *breachArbiter) breachObserver( // The channel has been closed cooperatively, so we're done here. case <-chainEvents.CooperativeClosure: - // Launch a goroutine to cancel out this contract within the - // breachArbiter's main goroutine. - b.wg.Add(1) - go func() { - defer b.wg.Done() - - select { - case b.settledContracts <- chanPoint: - case <-b.quit: - } - }() - - b.cfg.CloseLink(&chanPoint, htlcswitch.CloseBreach) + gracefullyExit() // The channel has been closed by a normal means: force closing with // the latest commitment transaction. - case <-chainEvents.UnilateralClosure: - // Launch a goroutine to cancel out this contract within the - // breachArbiter's main goroutine. - b.wg.Add(1) - go func() { - defer b.wg.Done() - - select { - case b.settledContracts <- chanPoint: - case <-b.quit: - } - }() - - b.cfg.CloseLink(&chanPoint, htlcswitch.CloseBreach) + case <-chainEvents.LocalUnilateralClosure: + gracefullyExit() + case <-chainEvents.RemoteUnilateralClosure: + gracefullyExit() // A read from this channel indicates that a channel breach has been // detected! So we notify the main coordination goroutine with the From bc6b8a7eeb93121029d41ca61174c3032736e4f3 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 16 Mar 2018 15:19:11 +0100 Subject: [PATCH 09/31] htlcswitch/link: rename UnilateralClose -> RemoteUnilateralClose --- htlcswitch/link.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htlcswitch/link.go b/htlcswitch/link.go index c18d3332..01b5f2d5 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -832,7 +832,7 @@ out: // the contract as fully settled. Afterwards we can exit. // // TODO(roasbeef): add force closure? also breach? - case <-l.cfg.ChainEvents.UnilateralClosure: + case <-l.cfg.ChainEvents.RemoteUnilateralClosure: log.Warnf("Remote peer has closed ChannelPoint(%v) on-chain", l.channel.ChannelPoint()) From 40b8cf0adba15e3d47a6af0537025e540975fb09 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Tue, 3 Apr 2018 13:32:46 +0200 Subject: [PATCH 10/31] contractcourt/channel_arbitrator: rename UnilateralClose -> RemoteUnilateralClose --- contractcourt/channel_arbitrator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contractcourt/channel_arbitrator.go b/contractcourt/channel_arbitrator.go index 949fe5e1..58028833 100644 --- a/contractcourt/channel_arbitrator.go +++ b/contractcourt/channel_arbitrator.go @@ -1359,7 +1359,7 @@ func (c *ChannelArbitrator) channelAttendant(bestHeight int32) { // The remote party has broadcast the commitment on-chain. // We'll examine our state to determine if we need to act at // all. - case uniClosure := <-c.cfg.ChainEvents.UnilateralClosure: + case uniClosure := <-c.cfg.ChainEvents.RemoteUnilateralClosure: if c.state != StateDefault { continue } From 634e23720ebb7c40586cca94046fbdb2004f5fbb Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Tue, 3 Apr 2018 21:12:16 +0200 Subject: [PATCH 11/31] breacharbiter_test: use RemoteUnilateralCloseSummary and LocalForceCloseSummary --- breacharbiter_test.go | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/breacharbiter_test.go b/breacharbiter_test.go index 5ac4efa6..a4e7ba0f 100644 --- a/breacharbiter_test.go +++ b/breacharbiter_test.go @@ -951,11 +951,12 @@ func TestBreachHandoffSuccess(t *testing.T) { // Instantiate a breach arbiter to handle the breach of alice's channel. alicePoint := alice.ChannelPoint() spendEvents := contractcourt.ChainEventSubscription{ - UnilateralClosure: make(chan *lnwallet.UnilateralCloseSummary, 1), - CooperativeClosure: make(chan struct{}, 1), - ContractBreach: make(chan *lnwallet.BreachRetribution, 1), - ProcessACK: make(chan error, 1), - ChanPoint: *alicePoint, + RemoteUnilateralClosure: make(chan *lnwallet.UnilateralCloseSummary, 1), + LocalUnilateralClosure: make(chan *contractcourt.LocalUnilateralCloseInfo, 1), + CooperativeClosure: make(chan struct{}, 1), + ContractBreach: make(chan *lnwallet.BreachRetribution, 1), + ProcessACK: make(chan error, 1), + ChanPoint: *alicePoint, Cancel: func() { }, } @@ -1039,11 +1040,12 @@ func TestBreachHandoffFail(t *testing.T) { // Instantiate a breach arbiter to handle the breach of alice's channel. alicePoint := alice.ChannelPoint() spendEvents := contractcourt.ChainEventSubscription{ - UnilateralClosure: make(chan *lnwallet.UnilateralCloseSummary, 1), - CooperativeClosure: make(chan struct{}, 1), - ContractBreach: make(chan *lnwallet.BreachRetribution, 1), - ProcessACK: make(chan error, 1), - ChanPoint: *alicePoint, + RemoteUnilateralClosure: make(chan *lnwallet.UnilateralCloseSummary, 1), + LocalUnilateralClosure: make(chan *contractcourt.LocalUnilateralCloseInfo, 1), + CooperativeClosure: make(chan struct{}, 1), + ContractBreach: make(chan *lnwallet.BreachRetribution, 1), + ProcessACK: make(chan error, 1), + ChanPoint: *alicePoint, Cancel: func() { }, } From a60e621b5f4c53930e63634b1fd2bb3a5dbcfac6 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Thu, 12 Apr 2018 09:54:06 +0200 Subject: [PATCH 12/31] contractcourt/channel_arbitrator: specify that height is triggerHeight, advance from height of event not current height --- contractcourt/channel_arbitrator.go | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/contractcourt/channel_arbitrator.go b/contractcourt/channel_arbitrator.go index 58028833..75657d46 100644 --- a/contractcourt/channel_arbitrator.go +++ b/contractcourt/channel_arbitrator.go @@ -351,7 +351,7 @@ func (t transitionTrigger) String() string { // the appropriate state transition if necessary. The next state we transition // to is returned, Additionally, if the next transition results in a commitment // broadcast, the commitment transaction itself is returned. -func (c *ChannelArbitrator) stateStep(bestHeight uint32, +func (c *ChannelArbitrator) stateStep(triggerHeight uint32, trigger transitionTrigger) (ArbitratorState, *wire.MsgTx, error) { var ( @@ -365,14 +365,13 @@ func (c *ChannelArbitrator) stateStep(bestHeight uint32, case StateDefault: log.Debugf("ChannelArbitrator(%v): new block (height=%v) "+ "examining active HTLC's", c.cfg.ChanPoint, - bestHeight) + triggerHeight) // As a new block has been connected to the end of the main // chain, we'll check to see if we need to make any on-chain // claims on behalf of the channel contract that we're // arbitrating for. - chainActions := c.checkChainActions(uint32(bestHeight), - trigger) + chainActions := c.checkChainActions(triggerHeight, trigger) // If there are no actions to be made, then we'll remain in the // default state. If this isn't a self initiated event (we're @@ -542,7 +541,7 @@ func (c *ChannelArbitrator) stateStep(bestHeight uint32, // actions, wen create the structures we need to resolve all // outstanding contracts. htlcResolvers, pktsToSend, err := c.prepContractResolutions( - chainActions, contractResolutions, uint32(bestHeight), + chainActions, contractResolutions, triggerHeight, trigger, ) if err != nil { @@ -600,7 +599,7 @@ func (c *ChannelArbitrator) stateStep(bestHeight uint32, nextState = StateFullyResolved log.Infof("ChannelPoint(%v) has been fully resolved "+ - "on-chain at height=%v", c.cfg.ChanPoint, bestHeight) + "on-chain at height=%v", c.cfg.ChanPoint, triggerHeight) return nextState, closeTx, c.cfg.MarkChannelResolved() } @@ -621,7 +620,7 @@ func (c *ChannelArbitrator) stateStep(bestHeight uint32, // redundant transition, meaning that the state transition is a noop. The final // param is a callback that allows the caller to execute an arbitrary action // after each state transition. -func (c *ChannelArbitrator) advanceState(currentHeight uint32, +func (c *ChannelArbitrator) advanceState(triggerHeight uint32, trigger transitionTrigger, stateCallback func(ArbitratorState) error) ( ArbitratorState, *wire.MsgTx, error) { @@ -639,7 +638,7 @@ func (c *ChannelArbitrator) advanceState(currentHeight uint32, priorState = c.state nextState, closeTx, err := c.stateStep( - currentHeight, trigger, + triggerHeight, trigger, ) if err != nil { log.Errorf("unable to advance state: %v", err) @@ -1406,7 +1405,7 @@ func (c *ChannelArbitrator) channelAttendant(bestHeight int32) { // We'll now advance our state machine until it reaches // a terminal state. _, _, err := c.advanceState( - uint32(bestHeight), + uint32(uniClosure.SpendingHeight), remotePeerTrigger, stateCb, ) if err != nil { From d2d87758f775769c01a3de06e9c2ab3e382b31de Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Tue, 3 Apr 2018 13:34:07 +0200 Subject: [PATCH 13/31] contractcourt/channel_arbitrator: only act on close signal from chain_watcher This commit changes the channel arbitrator state machine to only care about commitment transactions that are being confirmed on-chain according to the chain_watcher. This is meant to handles the cases where we would broadcast our commitment, expecting it to get confirmed, but instead a competing transaction was confirmed. This commit readies the ChannelArbitrator state machine for the change that will make the ChainWatcher only notify on confirmed commitments. The state machine has gotten a new state, StateCommitmentBroadcasted, which we'll transition to after we have broadcasted our own commitment. From this state we'll go to the StateContractClosed state regardless of which commitment the ChainWatcher notifies about, unifying the contract resolution betweee the local and remote force close. --- contractcourt/channel_arbitrator.go | 204 +++++++++++++++++++--------- 1 file changed, 138 insertions(+), 66 deletions(-) diff --git a/contractcourt/channel_arbitrator.go b/contractcourt/channel_arbitrator.go index 75657d46..0278558b 100644 --- a/contractcourt/channel_arbitrator.go +++ b/contractcourt/channel_arbitrator.go @@ -319,14 +319,18 @@ const ( // being attached. chainTrigger transitionTrigger = iota - // remotePeerTrigger is a transition trigger driven by actions of the - // remote peer. - remotePeerTrigger - // userTrigger is a transition trigger driven by user action. Examples - // of such a trigger include a user requesting a forgive closure of the + // of such a trigger include a user requesting a force closure of the // channel. userTrigger + + // remoteCloseTrigger is a transition trigger driven by the remote + // peer's commitment being confirmed. + remoteCloseTrigger + + // localCloseTrigger is a transition trigger driven by our commitment + // being confirmed. + localCloseTrigger ) // String returns a human readable string describing the passed @@ -336,12 +340,15 @@ func (t transitionTrigger) String() string { case chainTrigger: return "chainTrigger" - case remotePeerTrigger: - return "remotePeerTrigger" + case remoteCloseTrigger: + return "remoteCloseTrigger" case userTrigger: return "userTrigger" + case localCloseTrigger: + return "localCloseTrigger" + default: return "unknown trigger" } @@ -407,10 +414,16 @@ func (c *ChannelArbitrator) stateStep(triggerHeight uint32, case userTrigger: nextState = StateBroadcastCommit - // Otherwise, if this state advance was triggered by the remote - // peer, then we'll jump straight to the state where the - // contract has already been closed. - case remotePeerTrigger: + // Otherwise, if this state advance was triggered by a + // commitment being confirmed on chain, then we'll jump + // straight to the state where the contract has already been + // closed. + case localCloseTrigger: + log.Errorf("ChannelArbitrator(%v): unexpected local "+ + "commitment confirmed while in StateDefault", + c.cfg.ChanPoint) + fallthrough + case remoteCloseTrigger: nextState = StateContractClosed } @@ -454,26 +467,6 @@ func (c *ChannelArbitrator) stateStep(triggerHeight uint32, } } - // As we've have broadcast the commitment transaction, we send - // out commitment output for incubation, but only if it wasn't - // trimmed. We'll need to wait for a CSV timeout before we can - // reclaim the funds. - if closeSummary.CommitResolution != nil { - log.Infof("ChannelArbitrator(%v): sending commit "+ - "output for incubation", c.cfg.ChanPoint) - - err = c.cfg.IncubateOutputs( - c.cfg.ChanPoint, closeSummary.CommitResolution, - nil, nil, - ) - if err != nil { - // TODO(roasbeef): check for AlreadyExists errors - log.Errorf("unable to incubate commitment "+ - "output: %v", err) - return StateError, closeTx, err - } - } - contractRes := ContractResolutions{ CommitHash: closeTx.TxHash(), CommitResolution: closeSummary.CommitResolution, @@ -491,23 +484,32 @@ func (c *ChannelArbitrator) stateStep(triggerHeight uint32, return StateError, closeTx, err } - // With the channel force closed, we'll now log our - // resolutions, then advance our state forward. - log.Infof("ChannelArbitrator(%v): logging contract "+ - "resolutions: commit=%v, num_htlcs=%v", - c.cfg.ChanPoint, - closeSummary.CommitResolution != nil, - len(closeSummary.HtlcResolutions.IncomingHTLCs)+ - len(closeSummary.HtlcResolutions.OutgoingHTLCs)) + // We go to the StateCommitmentBroadcasted state, where we'll + // be waiting for the commitment to be confirmed. + nextState = StateCommitmentBroadcasted - err = c.log.LogContractResolutions(&contractRes) - if err != nil { - log.Errorf("unable to write resolutions: %v", err) - return StateError, closeTx, err + // In this state we have broadcasted our own commitment, and will need + // to wait for a commitment (not necessarily the one we broadcasted!) + // to be confirmed. + case StateCommitmentBroadcasted: + switch trigger { + // We are waiting for a commitment to be confirmed, so any + // other trigger will be ignored. + case chainTrigger, userTrigger: + log.Infof("ChannelArbitrator(%v): noop state %v", + c.cfg.ChanPoint, trigger) + nextState = StateCommitmentBroadcasted + + // If this state advance was triggered by any of the + // commitments being confirmed, then we'll jump to the state + // where the contract has been closed. + case localCloseTrigger, remoteCloseTrigger: + log.Infof("ChannelArbitrator(%v): state %v, "+ + " going to StateContractClosed", + c.cfg.ChanPoint, trigger) + nextState = StateContractClosed } - nextState = StateContractClosed - // If we're in this state, then the contract has been fully closed to // outside sub-systems, so we'll process the prior set of on-chain // contract actions and launch a set of resolvers. @@ -537,6 +539,27 @@ func (c *ChannelArbitrator) stateStep(triggerHeight uint32, break } + // If we've have broadcast the commitment transaction, we send + // our commitment output for incubation, but only if it wasn't + // trimmed. We'll need to wait for a CSV timeout before we can + // reclaim the funds. + commitRes := contractResolutions.CommitResolution + if commitRes != nil && commitRes.MaturityDelay > 0 { + log.Infof("ChannelArbitrator(%v): sending commit "+ + "output for incubation", c.cfg.ChanPoint) + + err = c.cfg.IncubateOutputs( + c.cfg.ChanPoint, commitRes, + nil, nil, + ) + if err != nil { + // TODO(roasbeef): check for AlreadyExists errors + log.Errorf("unable to incubate commitment "+ + "output: %v", err) + return StateError, closeTx, err + } + } + // Now that we know we'll need to act, we'll process the htlc // actions, wen create the structures we need to resolve all // outstanding contracts. @@ -629,13 +652,13 @@ func (c *ChannelArbitrator) advanceState(triggerHeight uint32, forceCloseTx *wire.MsgTx ) - log.Tracef("ChannelArbitrator(%v): attempting state step with "+ - "trigger=%v", c.cfg.ChanPoint, trigger) - // We'll continue to advance our state forward until the state we // transition to is that same state that we started at. for { priorState = c.state + log.Tracef("ChannelArbitrator(%v): attempting state step with "+ + "trigger=%v from state=%v", c.cfg.ChanPoint, trigger, + priorState) nextState, closeTx, err := c.stateStep( triggerHeight, trigger, @@ -1348,20 +1371,67 @@ func (c *ChannelArbitrator) channelAttendant(bestHeight int32) { }), ) - // We've cooperatively closed the channel, so we're no longer - // needed. + // We've cooperatively closed the channel, so we're no longer + // needed. case <-c.cfg.ChainEvents.CooperativeClosure: log.Infof("ChannelArbitrator(%v) closing due to co-op "+ "closure", c.cfg.ChanPoint) return + // We have broadcasted our commitment, and it is now confirmed + // on-chain. + case closeInfo := <-c.cfg.ChainEvents.LocalUnilateralClosure: + log.Infof("ChannelArbitrator(%v): local on-chain "+ + "channel close", c.cfg.ChanPoint) + + if c.state != StateCommitmentBroadcasted { + log.Errorf("ChannelArbitrator(%v): unexpected "+ + "local on-chain channel close", + c.cfg.ChanPoint) + } + closeTx := closeInfo.CloseTx + + contractRes := &ContractResolutions{ + CommitHash: closeTx.TxHash(), + CommitResolution: closeInfo.CommitResolution, + HtlcResolutions: *closeInfo.HtlcResolutions, + } + + // When processing a unilateral close event, we'll + // transition directly to the ContractClosed state. + // When the state machine reaches that state, we'll log + // out the set of resolutions. + stateCb := func(nextState ArbitratorState) error { + if nextState != StateContractClosed { + return nil + } + + err := c.log.LogContractResolutions( + contractRes, + ) + if err != nil { + return fmt.Errorf("unable to "+ + "write resolutions: %v", + err) + } + + return nil + } + + // We'll now advance our state machine until it reaches + // a terminal state. + _, _, err := c.advanceState( + uint32(closeInfo.SpendingHeight), + localCloseTrigger, stateCb, + ) + if err != nil { + log.Errorf("unable to advance state: %v", err) + } + // The remote party has broadcast the commitment on-chain. // We'll examine our state to determine if we need to act at // all. case uniClosure := <-c.cfg.ChainEvents.RemoteUnilateralClosure: - if c.state != StateDefault { - continue - } log.Infof("ChannelArbitrator(%v): remote party has "+ "closed channel out on-chain", c.cfg.ChanPoint) @@ -1384,19 +1454,21 @@ func (c *ChannelArbitrator) channelAttendant(bestHeight int32) { // present on their commitment. c.activeHTLCs = newHtlcSet(uniClosure.RemoteCommit.Htlcs) - // When processing a remote party initiated event, - // we'll skip the BroadcastCommit state, and transition - // directly to the ContractClosed state. As a result, - // we'll now manually log out set of resolutions. + // When processing a unilateral close event, we'll + // transition directly to the ContractClosed state. + // When the state machine reaches that state, we'll log + // out the set of resolutions. stateCb := func(nextState ArbitratorState) error { - if nextState == StateContractClosed { - err := c.log.LogContractResolutions( - contractRes, - ) - if err != nil { - return fmt.Errorf("unable to write "+ - "resolutions: %v", err) - } + if nextState != StateContractClosed { + return nil + } + + err := c.log.LogContractResolutions( + contractRes, + ) + if err != nil { + return fmt.Errorf("unable to write "+ + "resolutions: %v", err) } return nil @@ -1406,7 +1478,7 @@ func (c *ChannelArbitrator) channelAttendant(bestHeight int32) { // a terminal state. _, _, err := c.advanceState( uint32(uniClosure.SpendingHeight), - remotePeerTrigger, stateCb, + remoteCloseTrigger, stateCb, ) if err != nil { log.Errorf("unable to advance state: %v", err) From b7bb53a8b809f725a8b3b962d8bdf15c3befceea Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Tue, 27 Mar 2018 13:57:57 +0200 Subject: [PATCH 14/31] channeldb/channel: add property ChanStatus in place of IsBorked to OpenChannel This commit changes the bool `IsBorked` in OpenChannel to a `ChanStatus` struct, of type ChannelStatus. This is used to indicated that a channel that is technically still open, is either borked, or has had a commitment broadcasted, but is not confirmed on-chain yet. The ChannelStatus type has the value 1 for the status Borked, meaning it is backwards compatible with the old database format. --- channeldb/channel.go | 89 ++++++++++++++++++++++++++++++++++---------- lnwallet/channel.go | 2 +- 2 files changed, 71 insertions(+), 20 deletions(-) diff --git a/channeldb/channel.go b/channeldb/channel.go index a2435c24..b15943a2 100644 --- a/channeldb/channel.go +++ b/channeldb/channel.go @@ -285,6 +285,38 @@ type ChannelCommitment struct { // * lets just walk through } +// ChannelStatus is used to indicate whether an OpenChannel is in the default +// usable state, or a state where it shouldn't be used. +type ChannelStatus uint8 + +var ( + // Default is the normal state of an open channel. + Default ChannelStatus = 0 + + // Borked indicates that the channel has entered an irreconcilable + // state, triggered by a state desynchronization or channel breach. + // Channels in this state should never be added to the htlc switch. + Borked ChannelStatus = 1 + + // CommitmentBroadcasted indicates that a commitment for this channel + // has been broadcasted. + CommitmentBroadcasted ChannelStatus = 2 +) + +// String returns a human-readable representation of the ChannelStatus. +func (c ChannelStatus) String() string { + switch c { + case Default: + return "Default" + case Borked: + return "Borked" + case CommitmentBroadcasted: + return "CommitmentBroadcasted" + default: + return "Unknown" + } +} + // OpenChannel encapsulates the persistent and dynamic state of an open channel // with a remote node. An open channel supports several options for on-disk // serialization depending on the exact context. Full (upon channel creation) @@ -322,10 +354,9 @@ type OpenChannel struct { // negotiate fees, or close the channel. IsInitiator bool - // IsBorked indicates that the channel has entered an irreconcilable - // state, triggered by a state desynchronization or channel breach. - // Channels in this state should never be added to the htlc switch. - IsBorked bool + // ChanStatus is the current status of this channel. If it is not in + // the state Default, it should not be used for forwarding payments. + ChanStatus ChannelStatus // FundingBroadcastHeight is the height in which the funding // transaction was broadcast. This value can be used by higher level @@ -571,6 +602,20 @@ func (c *OpenChannel) MarkBorked() error { c.Lock() defer c.Unlock() + return c.putChanStatus(Borked) +} + +// MarkCommitmentBroadcasted marks the channel as a commitment transaction has +// been broadcast, either our own or the remote, and we should watch the chain +// for it to confirm before taking any further action. +func (c *OpenChannel) MarkCommitmentBroadcasted() error { + c.Lock() + defer c.Unlock() + + return c.putChanStatus(CommitmentBroadcasted) +} + +func (c *OpenChannel) putChanStatus(status ChannelStatus) error { if err := c.Db.Update(func(tx *bolt.Tx) error { chanBucket, err := updateChanBucket(tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash) @@ -583,14 +628,15 @@ func (c *OpenChannel) MarkBorked() error { return err } - channel.IsBorked = true + channel.ChanStatus = status return putOpenChannel(chanBucket, channel) }); err != nil { return err } - c.IsBorked = true + // Update the in-memory representation to keep it in sync with the DB. + c.ChanStatus = status return nil } @@ -1478,8 +1524,8 @@ func (c *OpenChannel) FindPreviousState(updateNum uint64) (*ChannelCommitment, e } // ClosureType is an enum like structure that details exactly _how_ a channel -// was closed. Three closure types are currently possible: cooperative, force, -// and breach. +// was closed. Three closure types are currently possible: none, cooperative, +// local force close, remote force close, and (remote) breach. type ClosureType uint8 const ( @@ -1487,21 +1533,25 @@ const ( // cooperatively. This means that both channel peers were online and // signed a new transaction paying out the settled balance of the // contract. - CooperativeClose ClosureType = iota + CooperativeClose ClosureType = 0 - // ForceClose indicates that one peer unilaterally broadcast their + // LocalForceClose indicates that we have unilaterally broadcast our // current commitment state on-chain. - ForceClose + LocalForceClose ClosureType = 1 - // BreachClose indicates that one peer attempted to broadcast a prior - // _revoked_ channel state. - BreachClose + // RemoteForceClose indicates that the remote peer has unilaterally + // broadcast their current commitment state on-chain. + RemoteForceClose ClosureType = 4 + + // BreachClose indicates that the remote peer attempted to broadcast a + // prior _revoked_ channel state. + BreachClose ClosureType = 2 // FundingCanceled indicates that the channel never was fully opened // before it was marked as closed in the database. This can happen if // we or the remote fail at some point during the opening workflow, or // we timeout waiting for the funding transaction to be confirmed. - FundingCanceled + FundingCanceled ClosureType = 3 ) // ChannelCloseSummary contains the final state of a channel at the point it @@ -1549,8 +1599,9 @@ type ChannelCloseSummary struct { // outstanding outgoing HTLC's at the time of channel closure. TimeLockedBalance btcutil.Amount - // CloseType details exactly _how_ the channel was closed. Three - // closure types are possible: cooperative, force, and breach. + // CloseType details exactly _how_ the channel was closed. Five closure + // types are possible: cooperative, local force, remote force, breach + // and funding canceled. CloseType ClosureType // IsPending indicates whether this channel is in the 'pending close' @@ -1804,7 +1855,7 @@ func putChanInfo(chanBucket *bolt.Bucket, channel *OpenChannel) error { if err := writeElements(&w, channel.ChanType, channel.ChainHash, channel.FundingOutpoint, channel.ShortChanID, channel.IsPending, channel.IsInitiator, - channel.IsBorked, channel.FundingBroadcastHeight, + channel.ChanStatus, channel.FundingBroadcastHeight, channel.NumConfsRequired, channel.ChannelFlags, channel.IdentityPub, channel.Capacity, channel.TotalMSatSent, channel.TotalMSatReceived, @@ -1912,7 +1963,7 @@ func fetchChanInfo(chanBucket *bolt.Bucket, channel *OpenChannel) error { if err := readElements(r, &channel.ChanType, &channel.ChainHash, &channel.FundingOutpoint, &channel.ShortChanID, &channel.IsPending, &channel.IsInitiator, - &channel.IsBorked, &channel.FundingBroadcastHeight, + &channel.ChanStatus, &channel.FundingBroadcastHeight, &channel.NumConfsRequired, &channel.ChannelFlags, &channel.IdentityPub, &channel.Capacity, &channel.TotalMSatSent, &channel.TotalMSatReceived, diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 3a32fcfe..a17dd791 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -4754,7 +4754,7 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer Signer, RemotePub: chanState.IdentityPub, Capacity: chanState.Capacity, SettledBalance: localBalance, - CloseType: channeldb.ForceClose, + CloseType: channeldb.RemoteForceClose, IsPending: true, } From cde862e7e0bed5742ed8f87d47102a645cae2f0f Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Thu, 12 Apr 2018 13:08:01 +0200 Subject: [PATCH 15/31] channeldb/codec: add ChannelStatus --- channeldb/codec.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/channeldb/codec.go b/channeldb/codec.go index 14665e0c..086c2533 100644 --- a/channeldb/codec.go +++ b/channeldb/codec.go @@ -148,10 +148,16 @@ func writeElement(w io.Writer, element interface{}) error { return err } + case ChannelStatus: + if err := binary.Write(w, byteOrder, e); err != nil { + return err + } + case ClosureType: if err := binary.Write(w, byteOrder, e); err != nil { return err } + case lnwire.FundingFlag: if err := binary.Write(w, byteOrder, e); err != nil { return err @@ -321,10 +327,16 @@ func readElement(r io.Reader, element interface{}) error { *e = msg + case *ChannelStatus: + if err := binary.Read(r, byteOrder, e); err != nil { + return err + } + case *ClosureType: if err := binary.Read(r, byteOrder, e); err != nil { return err } + case *lnwire.FundingFlag: if err := binary.Read(r, byteOrder, e); err != nil { return err From c51f6352c302479b184aab3f7df6d833dd013717 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Wed, 4 Apr 2018 08:57:55 +0200 Subject: [PATCH 16/31] channeldb test: rename ForceClose->RemoteForceClose --- channeldb/channel_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/channeldb/channel_test.go b/channeldb/channel_test.go index 66e9e597..cb734b8d 100644 --- a/channeldb/channel_test.go +++ b/channeldb/channel_test.go @@ -606,7 +606,7 @@ func TestChannelStateTransition(t *testing.T) { SettledBalance: btcutil.Amount(500), TimeLockedBalance: btcutil.Amount(10000), IsPending: false, - CloseType: ForceClose, + CloseType: RemoteForceClose, } if err := updatedChannel[0].CloseChannel(closeSummary); err != nil { t.Fatalf("unable to delete updated channel: %v", err) @@ -770,7 +770,7 @@ func TestFetchClosedChannels(t *testing.T) { Capacity: state.Capacity, SettledBalance: state.LocalCommitment.LocalBalance.ToSatoshis(), TimeLockedBalance: state.RemoteCommitment.LocalBalance.ToSatoshis() + 10000, - CloseType: ForceClose, + CloseType: RemoteForceClose, IsPending: true, } if err := state.CloseChannel(summary); err != nil { From 1265c9cc9948ac78ee08df379aea08b1ce724351 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Tue, 3 Apr 2018 14:04:30 +0200 Subject: [PATCH 17/31] contractcourt/chain_arbitrator: add MarkCommitmentBroadcasted to ChanArb cfg --- contractcourt/chain_arbitrator.go | 1 + 1 file changed, 1 insertion(+) diff --git a/contractcourt/chain_arbitrator.go b/contractcourt/chain_arbitrator.go index 82ab1add..31917452 100644 --- a/contractcourt/chain_arbitrator.go +++ b/contractcourt/chain_arbitrator.go @@ -231,6 +231,7 @@ func newActiveChannelArbitrator(channel *channeldb.OpenChannel, return chanMachine.ForceClose() }, + MarkCommitmentBroadcasted: channel.MarkCommitmentBroadcasted, CloseChannel: func(summary *channeldb.ChannelCloseSummary) error { log.Tracef("ChannelArbitrator(%v): closing "+ "channel", chanPoint) From 854e73b8e0c444bac365f041e707370a1b0dbf5f Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Tue, 27 Mar 2018 14:06:34 +0200 Subject: [PATCH 18/31] contractcourt/chain_watcher: register for confirmed spend, mark closed when detected This commit changes the ChainWatcher to only send a chain event in case the various spends are _confirmed_ on-chain, not only seen on the network. A consequence of this is that we now give the ChainWatcher the responsibility of marking the channel closed when the closing tx is confirmed, instead of the ChannelArbitrator. --- contractcourt/chain_watcher.go | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/contractcourt/chain_watcher.go b/contractcourt/chain_watcher.go index 1fa32218..e4d1ac40 100644 --- a/contractcourt/chain_watcher.go +++ b/contractcourt/chain_watcher.go @@ -190,7 +190,7 @@ func (c *chainWatcher) Start() error { } spendNtfn, err := c.notifier.RegisterSpendNtfn( - fundingOut, heightHint, true, + fundingOut, heightHint, false, ) if err != nil { return err @@ -534,6 +534,38 @@ func (c *chainWatcher) dispatchLocalForceClose( return err } + // As we've detected that the channel has been closed, immediately + // delete the state from disk, creating a close summary for future + // usage by related sub-systems. + chanSnapshot := forceClose.ChanSnapshot + closeSummary := &channeldb.ChannelCloseSummary{ + ChanPoint: chanSnapshot.ChannelPoint, + ChainHash: chanSnapshot.ChainHash, + ClosingTXID: forceClose.CloseTx.TxHash(), + RemotePub: &chanSnapshot.RemoteIdentity, + Capacity: chanSnapshot.Capacity, + CloseType: channeldb.LocalForceClose, + IsPending: true, + ShortChanID: c.chanState.ShortChanID, + CloseHeight: uint32(commitSpend.SpendingHeight), + } + + // If our commitment output isn't dust or we have active HTLC's on the + // commitment transaction, then we'll populate the balances on the + // close channel summary. + if forceClose.CommitResolution != nil { + closeSummary.SettledBalance = chanSnapshot.LocalBalance.ToSatoshis() + closeSummary.TimeLockedBalance = chanSnapshot.LocalBalance.ToSatoshis() + } + for _, htlc := range forceClose.HtlcResolutions.OutgoingHTLCs { + htlcValue := btcutil.Amount(htlc.SweepSignDesc.Output.Value) + closeSummary.TimeLockedBalance += htlcValue + } + err = c.chanState.CloseChannel(closeSummary) + if err != nil { + return fmt.Errorf("unable to delete channel state: %v", err) + } + // With the event processed, we'll now notify all subscribers of the // event. closeInfo := &LocalUnilateralCloseInfo{commitSpend, forceClose} From 70e808773130a91d5e8cd392c291d47a356d6e73 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Tue, 3 Apr 2018 14:05:07 +0200 Subject: [PATCH 19/31] contractcourt/channel_arbitrator: mark channel commitment broadcasted instead of closed after broadcast --- contractcourt/channel_arbitrator.go | 60 ++++------------------------- 1 file changed, 8 insertions(+), 52 deletions(-) diff --git a/contractcourt/channel_arbitrator.go b/contractcourt/channel_arbitrator.go index 0278558b..c1d37093 100644 --- a/contractcourt/channel_arbitrator.go +++ b/contractcourt/channel_arbitrator.go @@ -11,7 +11,6 @@ import ( "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" "github.com/roasbeef/btcd/wire" - "github.com/roasbeef/btcutil" ) const ( @@ -90,6 +89,10 @@ type ChannelArbitratorConfig struct { // eventually resolve all outputs on chain. ForceCloseChan func() (*lnwallet.LocalForceCloseSummary, error) + // MarkCommitmentBroadcasted should mark the channel as the commitment + // being broadcast, and we are waiting for the commitment to confirm. + MarkCommitmentBroadcasted func() error + // CloseChannel is a function closure that marks a channel under watch // as "closing". In this phase, we will no longer accept any updates to // the channel as the commitment transaction has been broadcast, and @@ -467,21 +470,10 @@ func (c *ChannelArbitrator) stateStep(triggerHeight uint32, } } - contractRes := ContractResolutions{ - CommitHash: closeTx.TxHash(), - CommitResolution: closeSummary.CommitResolution, - HtlcResolutions: *closeSummary.HtlcResolutions, - } - - // Now that the transaction has been broadcast, we can mark - // that it has been closed to outside sub-systems. - err = c.markContractClosed( - closeTx, closeSummary.ChanSnapshot, &contractRes, - bestHeight, - ) - if err != nil { - log.Errorf("unable to close contract: %v", err) - return StateError, closeTx, err + if err := c.cfg.MarkCommitmentBroadcasted(); err != nil { + log.Errorf("ChannelArbitrator(%v): unable to "+ + "mark commitment broadcasted: %v", + c.cfg.ChanPoint, err) } // We go to the StateCommitmentBroadcasted state, where we'll @@ -1555,39 +1547,3 @@ func (c *ChannelArbitrator) channelAttendant(bestHeight int32) { } } } - -// markContractClosed marks a contract as "pending closed". After this state, -// upon restart, we'll no longer watch for updates to the set of contracts as -// the channel cannot be updated any longer. -func (c *ChannelArbitrator) markContractClosed(closeTx *wire.MsgTx, - chanSnapshot channeldb.ChannelSnapshot, - contractResolution *ContractResolutions, - closeHeight uint32) error { - - // TODO(roasbeef): also need height info? - closeInfo := &channeldb.ChannelCloseSummary{ - ChanPoint: chanSnapshot.ChannelPoint, - ChainHash: chanSnapshot.ChainHash, - ClosingTXID: closeTx.TxHash(), - RemotePub: &chanSnapshot.RemoteIdentity, - Capacity: chanSnapshot.Capacity, - CloseType: channeldb.ForceClose, - IsPending: true, - ShortChanID: c.cfg.ShortChanID, - CloseHeight: closeHeight, - } - - // If our commitment output isn't dust or we have active HTLC's on the - // commitment transaction, then we'll populate the balances on the - // close channel summary. - if contractResolution.CommitResolution != nil { - closeInfo.SettledBalance = chanSnapshot.LocalBalance.ToSatoshis() - closeInfo.TimeLockedBalance = chanSnapshot.LocalBalance.ToSatoshis() - } - for _, htlc := range contractResolution.HtlcResolutions.OutgoingHTLCs { - htlcValue := btcutil.Amount(htlc.SweepSignDesc.Output.Value) - closeInfo.TimeLockedBalance += htlcValue - } - - return c.cfg.CloseChannel(closeInfo) -} From bd4e71797134c58a1f643cd0cbeb6322b6a82f24 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Tue, 3 Apr 2018 21:53:22 +0200 Subject: [PATCH 20/31] peer: don't load channels that have had commitment broadcasted --- peer.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/peer.go b/peer.go index dfa4e0f7..248e5830 100644 --- a/peer.go +++ b/peer.go @@ -329,9 +329,9 @@ func (p *peer) loadActiveChannels(chans []*channeldb.OpenChannel) error { // Skip adding any permanently irreconcilable channels to the // htlcswitch. - if dbChan.IsBorked { - peerLog.Warnf("ChannelPoint(%v) is borked, won't "+ - "start.", chanPoint) + if dbChan.ChanStatus != channeldb.Default { + peerLog.Warnf("ChannelPoint(%v) has status %v, won't "+ + "start.", chanPoint, dbChan.ChanStatus) lnChan.Stop() continue } From 5f1da5b5f4813bda02824a3923485271fdfd2c6c Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Tue, 3 Apr 2018 14:53:18 +0200 Subject: [PATCH 21/31] contractcourt/channel_arbitrator: remove unused CloseChannel method from config --- contractcourt/channel_arbitrator.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/contractcourt/channel_arbitrator.go b/contractcourt/channel_arbitrator.go index c1d37093..47448e0f 100644 --- a/contractcourt/channel_arbitrator.go +++ b/contractcourt/channel_arbitrator.go @@ -93,12 +93,6 @@ type ChannelArbitratorConfig struct { // being broadcast, and we are waiting for the commitment to confirm. MarkCommitmentBroadcasted func() error - // CloseChannel is a function closure that marks a channel under watch - // as "closing". In this phase, we will no longer accept any updates to - // the channel as the commitment transaction has been broadcast, and - // possibly fully confirmed. - CloseChannel func(*channeldb.ChannelCloseSummary) error - // MarkChannelResolved is a function closure that serves to mark a // channel as "fully resolved". A channel itself can be considered // fully resolved once all active contracts have individually been From 84f06959f3dd57a5bb608ea4190a64b177d5d604 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Tue, 3 Apr 2018 14:53:51 +0200 Subject: [PATCH 22/31] contractcourt/chain_arbitrator: remove CloseChannel method from ChannelArbitrator config --- contractcourt/chain_arbitrator.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/contractcourt/chain_arbitrator.go b/contractcourt/chain_arbitrator.go index 31917452..95bc071c 100644 --- a/contractcourt/chain_arbitrator.go +++ b/contractcourt/chain_arbitrator.go @@ -232,14 +232,8 @@ func newActiveChannelArbitrator(channel *channeldb.OpenChannel, return chanMachine.ForceClose() }, MarkCommitmentBroadcasted: channel.MarkCommitmentBroadcasted, - CloseChannel: func(summary *channeldb.ChannelCloseSummary) error { - log.Tracef("ChannelArbitrator(%v): closing "+ - "channel", chanPoint) - - return channel.CloseChannel(summary) - }, - ChainArbitratorConfig: c.cfg, - ChainEvents: chanEvents, + ChainArbitratorConfig: c.cfg, + ChainEvents: chanEvents, } // The final component needed is an arbitrator log that the arbitrator From b2949bd72837642f7b473bbf35f6c4ffd0a8fd34 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Mon, 19 Mar 2018 14:11:13 +0100 Subject: [PATCH 23/31] contractcourt/channel_arbitrator test: add unit tests This commit adds MVP unit tests for the following scenarios in the ChannelArbitrator: 1) A cooperative close is confirmed. 2) A remote force close is confirmed. 3) A local force close is requested and confirmed. 4) A local force close is requested, but a remote force close gets confirmed. --- contractcourt/channel_arbitrator_test.go | 340 +++++++++++++++++++++++ 1 file changed, 340 insertions(+) diff --git a/contractcourt/channel_arbitrator_test.go b/contractcourt/channel_arbitrator_test.go index 236a3336..76579643 100644 --- a/contractcourt/channel_arbitrator_test.go +++ b/contractcourt/channel_arbitrator_test.go @@ -1 +1,341 @@ package contractcourt + +import ( + "fmt" + "testing" + "time" + + "github.com/lightningnetwork/lnd/chainntnfs" + "github.com/lightningnetwork/lnd/lnwallet" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/roasbeef/btcd/chaincfg/chainhash" + "github.com/roasbeef/btcd/wire" +) + +type mockChainIO struct{} + +func (*mockChainIO) GetBestBlock() (*chainhash.Hash, int32, error) { + return nil, 0, nil +} + +func (*mockChainIO) GetUtxo(op *wire.OutPoint, + heightHint uint32) (*wire.TxOut, error) { + return nil, nil +} + +func (*mockChainIO) GetBlockHash(blockHeight int64) (*chainhash.Hash, error) { + return nil, nil +} + +func (*mockChainIO) GetBlock(blockHash *chainhash.Hash) (*wire.MsgBlock, error) { + return nil, nil +} + +func createTestChannelArbitrator() (*ChannelArbitrator, chan struct{}, func(), error) { + blockEpoch := &chainntnfs.BlockEpochEvent{ + Cancel: func() {}, + } + + chanPoint := wire.OutPoint{} + shortChanID := lnwire.ShortChannelID{} + chanEvents := &ChainEventSubscription{ + RemoteUnilateralClosure: make(chan *lnwallet.UnilateralCloseSummary, 1), + LocalUnilateralClosure: make(chan *LocalUnilateralCloseInfo, 1), + CooperativeClosure: make(chan struct{}, 1), + ContractBreach: make(chan *lnwallet.BreachRetribution, 1), + } + + chainIO := &mockChainIO{} + chainArbCfg := ChainArbitratorConfig{ + ChainIO: chainIO, + PublishTx: func(*wire.MsgTx) error { + return nil + }, + } + + // We'll use the resolvedChan to synchronize on call to + // MarkChannelResolved. + resolvedChan := make(chan struct{}, 1) + + // Next we'll create the matching configuration struct that contains + // all interfaces and methods the arbitrator needs to do its job. + arbCfg := ChannelArbitratorConfig{ + ChanPoint: chanPoint, + ShortChanID: shortChanID, + BlockEpochs: blockEpoch, + MarkChannelResolved: func() error { + resolvedChan <- struct{}{} + return nil + }, + ForceCloseChan: func() (*lnwallet.LocalForceCloseSummary, error) { + summary := &lnwallet.LocalForceCloseSummary{ + CloseTx: &wire.MsgTx{}, + HtlcResolutions: &lnwallet.HtlcResolutions{}, + } + return summary, nil + }, + MarkCommitmentBroadcasted: func() error { + return nil + }, + + ChainArbitratorConfig: chainArbCfg, + ChainEvents: chanEvents, + } + testLog, cleanUp, err := newTestBoltArbLog( + testChainHash, testChanPoint1, + ) + if err != nil { + return nil, nil, nil, fmt.Errorf("unable to create test log: %v", + err) + } + + return NewChannelArbitrator(arbCfg, nil, testLog), + resolvedChan, cleanUp, nil +} + +// assertState checks that the ChannelArbitrator is in the state we expect it +// to be. +func assertState(t *testing.T, c *ChannelArbitrator, expected ArbitratorState) { + if c.state != expected { + t.Fatalf("expected state %v, was %v", expected, c.state) + } +} + +// TestChannelArbitratorCooperativeClose tests that the ChannelArbitertor +// correctly does nothing in case a cooperative close is confirmed. +func TestChannelArbitratorCooperativeClose(t *testing.T) { + chanArb, _, cleanUp, err := createTestChannelArbitrator() + if err != nil { + t.Fatalf("unable to create ChannelArbitrator: %v", err) + } + defer cleanUp() + + if err := chanArb.Start(); err != nil { + t.Fatalf("unable to start ChannelArbitrator: %v", err) + } + defer chanArb.Stop() + + // It should start out in the default state. + assertState(t, chanArb, StateDefault) + + // Cooperative close should do nothing. + // TODO: this will change? + chanArb.cfg.ChainEvents.CooperativeClosure <- struct{}{} + assertState(t, chanArb, StateDefault) +} + +// TestChannelArbitratorRemoteForceClose checks that the ChannelArbitrotor goes +// through the expected states if a remote force close is observed in the +// chain. +func TestChannelArbitratorRemoteForceClose(t *testing.T) { + chanArb, resolved, cleanUp, err := createTestChannelArbitrator() + if err != nil { + t.Fatalf("unable to create ChannelArbitrator: %v", err) + } + defer cleanUp() + + if err := chanArb.Start(); err != nil { + t.Fatalf("unable to start ChannelArbitrator: %v", err) + } + defer chanArb.Stop() + + // It should start out in the default state. + assertState(t, chanArb, StateDefault) + + // Send a remote force close event. + commitSpend := &chainntnfs.SpendDetail{ + SpenderTxHash: &chainhash.Hash{}, + } + + uniClose := &lnwallet.UnilateralCloseSummary{ + SpendDetail: commitSpend, + HtlcResolutions: &lnwallet.HtlcResolutions{}, + } + chanArb.cfg.ChainEvents.RemoteUnilateralClosure <- uniClose + + // It should mark the channel as resolved. + select { + case <-resolved: + // Expected. + case <-time.After(5 * time.Second): + t.Fatalf("contract was not resolved") + } + + // TODO: intermediate states. + // We expect the ChannelArbitrator to end up in the the resolved state. + assertState(t, chanArb, StateFullyResolved) +} + +// TestChannelArbitratorLocalForceClose tests that the ChannelArbitrator goes +// through the expected states in case we request it to force close the channel, +// and the local force close event is observed in chain. +func TestChannelArbitratorLocalForceClose(t *testing.T) { + chanArb, resolved, cleanUp, err := createTestChannelArbitrator() + if err != nil { + t.Fatalf("unable to create ChannelArbitrator: %v", err) + } + defer cleanUp() + + if err := chanArb.Start(); err != nil { + t.Fatalf("unable to start ChannelArbitrator: %v", err) + } + defer chanArb.Stop() + + // It should start out in the default state. + assertState(t, chanArb, StateDefault) + + // We create a channel we can use to pause the ChannelArbitrator at the + // point where it broadcasts the close tx, and check its state. + stateChan := make(chan ArbitratorState) + chanArb.cfg.PublishTx = func(*wire.MsgTx) error { + // When the force close tx is being broadcasted, check that the + // state is correct at that point. + select { + case stateChan <- chanArb.state: + case <-chanArb.quit: + return fmt.Errorf("exiting") + } + return nil + } + + errChan := make(chan error, 1) + respChan := make(chan *wire.MsgTx, 1) + + // With the channel found, and the request crafted, we'll send over a + // force close request to the arbitrator that watches this channel. + chanArb.forceCloseReqs <- &forceCloseReq{ + errResp: errChan, + closeTx: respChan, + } + + // When it is broadcasting the force close, its state should be + // StateBroadcastCommit. + select { + case state := <-stateChan: + if state != StateBroadcastCommit { + t.Fatalf("state during PublishTx was %v", state) + } + case <-time.After(15 * time.Second): + t.Fatalf("did not get state update") + } + + select { + case <-respChan: + case err := <-errChan: + t.Fatalf("error force closing channel: %v", err) + case <-time.After(15 * time.Second): + t.Fatalf("did not receive reponse") + } + + // After broadcasting the close tx, it should be in state + // StateCommitmentBroadcasted. + assertState(t, chanArb, StateCommitmentBroadcasted) + + // Now notify about the local force close getting confirmed. + chanArb.cfg.ChainEvents.LocalUnilateralClosure <- &LocalUnilateralCloseInfo{ + &chainntnfs.SpendDetail{}, + &lnwallet.LocalForceCloseSummary{ + CloseTx: &wire.MsgTx{}, + HtlcResolutions: &lnwallet.HtlcResolutions{}, + }, + } + // It should mark the channel as resolved. + select { + case <-resolved: + // Expected. + case <-time.After(5 * time.Second): + t.Fatalf("contract was not resolved") + } + + // And end up in the StateFullyResolved state. + // TODO: intermediate states as well. + assertState(t, chanArb, StateFullyResolved) +} + +// TestChannelArbitratorLocalForceCloseRemoteConfiremd tests that the +// ChannelArbitrator behaves as expected in the case where we request a local +// force close, but a remote commitment ends up being confirmed in chain. +func TestChannelArbitratorLocalForceCloseRemoteConfirmed(t *testing.T) { + chanArb, resolved, cleanUp, err := createTestChannelArbitrator() + if err != nil { + t.Fatalf("unable to create ChannelArbitrator: %v", err) + } + defer cleanUp() + + if err := chanArb.Start(); err != nil { + t.Fatalf("unable to start ChannelArbitrator: %v", err) + } + defer chanArb.Stop() + + // It should start out in the default state. + assertState(t, chanArb, StateDefault) + + // Create a channel we can use to assert the state when it publishes + // the close tx. + stateChan := make(chan ArbitratorState) + chanArb.cfg.PublishTx = func(*wire.MsgTx) error { + // When the force close tx is being broadcasted, check that the + // state is correct at that point. + select { + case stateChan <- chanArb.state: + case <-chanArb.quit: + return fmt.Errorf("exiting") + } + return nil + } + + errChan := make(chan error, 1) + respChan := make(chan *wire.MsgTx, 1) + + // With the channel found, and the request crafted, we'll send over a + // force close request to the arbitrator that watches this channel. + chanArb.forceCloseReqs <- &forceCloseReq{ + errResp: errChan, + closeTx: respChan, + } + + // We expect it to be in state StateBroadcastCommit when publishing + // the force close. + select { + case state := <-stateChan: + if state != StateBroadcastCommit { + t.Fatalf("state during PublishTx was %v", state) + } + case <-time.After(15 * time.Second): + t.Fatalf("no state update received") + } + + // Wait for a response to the force close. + select { + case <-respChan: + case err := <-errChan: + t.Fatalf("error force closing channel: %v", err) + case <-time.After(15 * time.Second): + t.Fatalf("no response received") + } + + // The state should be StateCommitmentBroadcasted. + assertState(t, chanArb, StateCommitmentBroadcasted) + + // Now notify about the _REMOTE_ commitment getting confirmed. + commitSpend := &chainntnfs.SpendDetail{ + SpenderTxHash: &chainhash.Hash{}, + } + uniClose := &lnwallet.UnilateralCloseSummary{ + SpendDetail: commitSpend, + HtlcResolutions: &lnwallet.HtlcResolutions{}, + } + chanArb.cfg.ChainEvents.RemoteUnilateralClosure <- uniClose + + // It should resolve. + select { + case <-resolved: + // Expected. + case <-time.After(15 * time.Second): + t.Fatalf("contract was not resolved") + } + + // And we expect it to end up in StateFullyResolved. + // TODO: intermediate states as well. + assertState(t, chanArb, StateFullyResolved) +} From 0735b8e0b756483156f1346314b169c86c20c455 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Thu, 12 Apr 2018 12:49:19 +0200 Subject: [PATCH 24/31] channeldb: add method for fetching channels waiting for closing tx This commit adds a new method FetchWaitingCloseChannels to the database, used for fetching OpenChannels that have a ChanStatus != Default. These are channels that are borked, or have had a commitment broadcasted, and is now waiting for it to confirm. The fetchChannels method is rewritten to return channels exclusively based on wheter they are pending or waitingClose. --- channeldb/db.go | 82 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 66 insertions(+), 16 deletions(-) diff --git a/channeldb/db.go b/channeldb/db.go index fd198720..8292febb 100644 --- a/channeldb/db.go +++ b/channeldb/db.go @@ -312,23 +312,60 @@ func (d *DB) fetchNodeChannels(chainBucket *bolt.Bucket) ([]*OpenChannel, error) } // FetchAllChannels attempts to retrieve all open channels currently stored -// within the database. +// within the database, including pending open, fully open and channels waiting +// for a closing transaction to confirm. func (d *DB) FetchAllChannels() ([]*OpenChannel, error) { - return fetchChannels(d, false) + var channels []*OpenChannel + + // TODO(halseth): fetch all in one db tx. + openChannels, err := d.FetchAllOpenChannels() + if err != nil { + return nil, err + } + channels = append(channels, openChannels...) + + pendingChannels, err := d.FetchPendingChannels() + if err != nil { + return nil, err + } + channels = append(channels, pendingChannels...) + + waitingClose, err := d.FetchWaitingCloseChannels() + if err != nil { + return nil, err + } + channels = append(channels, waitingClose...) + + return channels, nil +} + +// FetchAllOpenChannels will return all channels that have the funding +// transaction confirmed, and is not waiting for a closing transaction to be +// confirmed. +func (d *DB) FetchAllOpenChannels() ([]*OpenChannel, error) { + return fetchChannels(d, false, false) } // FetchPendingChannels will return channels that have completed the process of // generating and broadcasting funding transactions, but whose funding // transactions have yet to be confirmed on the blockchain. func (d *DB) FetchPendingChannels() ([]*OpenChannel, error) { - return fetchChannels(d, true) + return fetchChannels(d, true, false) +} + +// FetchWaitingCloseChannels will return all channels that have been opened, +// but now is waiting for a closing transaction to be confirmed. +func (d *DB) FetchWaitingCloseChannels() ([]*OpenChannel, error) { + return fetchChannels(d, false, true) } // fetchChannels attempts to retrieve channels currently stored in the -// database. The pendingOnly parameter determines whether only pending channels -// will be returned. If no active channels exist within the network, then -// ErrNoActiveChannels is returned. -func fetchChannels(d *DB, pendingOnly bool) ([]*OpenChannel, error) { +// database. The pending parameter determines whether only pending channels +// will be returned, or only open channels will be returned. The waitingClose +// parameter determines wheter only channels waiting for a closing transaction +// to be confirmed should be returned. If no active channels exist within the +// network, then ErrNoActiveChannels is returned. +func fetchChannels(d *DB, pending, waitingClose bool) ([]*OpenChannel, error) { var channels []*OpenChannel err := d.View(func(tx *bolt.Tx) error { @@ -377,23 +414,36 @@ func fetchChannels(d *DB, pendingOnly bool) ([]*OpenChannel, error) { "channel for chain_hash=%x, "+ "node_key=%x: %v", chainHash[:], k, err) } - // TODO(roasbeef): simplify - if pendingOnly { - for _, channel := range nodeChans { - if channel.IsPending { - channels = append(channels, channel) - } + for _, channel := range nodeChans { + if channel.IsPending != pending { + continue } - } else { - channels = append(channels, nodeChans...) + + // If the channel is in any other state + // than Default, then it means it is + // waiting to be closed. + channelWaitingClose := + channel.ChanStatus != Default + + // Only include it if we requested + // channels with the same waitingClose + // status. + if channelWaitingClose != waitingClose { + continue + } + + channels = append(channels, channel) } return nil }) }) }) + if err != nil { + return nil, err + } - return channels, err + return channels, nil } // FetchClosedChannels attempts to fetch all closed channels from the database. From 5cdc7550b52c828a27047a6b5056e47462770f72 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Thu, 12 Apr 2018 12:53:33 +0200 Subject: [PATCH 25/31] htlcswitch: use FetchAllOpenChannels This commit changes from using FetchAllChannels to FetchAllOpenChannels, making the check for whether a channel is pending unnecessary. --- htlcswitch/circuit_map.go | 6 +----- htlcswitch/switch.go | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/htlcswitch/circuit_map.go b/htlcswitch/circuit_map.go index e5076544..844a9316 100644 --- a/htlcswitch/circuit_map.go +++ b/htlcswitch/circuit_map.go @@ -350,16 +350,12 @@ func (cm *circuitMap) decodeCircuit(v []byte) (*PaymentCircuit, error) { // channels. Therefore, it must be called before any links are created to avoid // interfering with normal operation. func (cm *circuitMap) trimAllOpenCircuits() error { - activeChannels, err := cm.cfg.DB.FetchAllChannels() + activeChannels, err := cm.cfg.DB.FetchAllOpenChannels() if err != nil { return err } for _, activeChannel := range activeChannels { - if activeChannel.IsPending { - continue - } - chanID := activeChannel.ShortChanID start := activeChannel.LocalCommitment.LocalHtlcIndex if err := cm.TrimOpenCircuits(chanID, start); err != nil { diff --git a/htlcswitch/switch.go b/htlcswitch/switch.go index 11b4efed..cf1750c8 100644 --- a/htlcswitch/switch.go +++ b/htlcswitch/switch.go @@ -1513,16 +1513,12 @@ func (s *Switch) Start() error { // forwarding packages and reforwards any Settle or Fail HTLCs found. This is // used to resurrect the switch's mailboxes after a restart. func (s *Switch) reforwardResponses() error { - activeChannels, err := s.cfg.DB.FetchAllChannels() + activeChannels, err := s.cfg.DB.FetchAllOpenChannels() if err != nil { return err } for _, activeChannel := range activeChannels { - if activeChannel.IsPending { - continue - } - shortChanID := activeChannel.ShortChanID fwdPkgs, err := s.loadChannelFwdPkgs(shortChanID) if err != nil { From 8b670d783a35d53532e7dfbac9089b748c4f5d05 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Thu, 12 Apr 2018 12:58:21 +0200 Subject: [PATCH 26/31] lnrpc: add WaitingCloseChannel to PendingChannelResponse --- lnrpc/rpc.pb.go | 763 ++++++++++++++++++++++------------------- lnrpc/rpc.proto | 11 + lnrpc/rpc.swagger.json | 21 ++ 3 files changed, 440 insertions(+), 355 deletions(-) diff --git a/lnrpc/rpc.pb.go b/lnrpc/rpc.pb.go index d78e9f28..50c2ebac 100644 --- a/lnrpc/rpc.pb.go +++ b/lnrpc/rpc.pb.go @@ -521,7 +521,9 @@ func (m *ChannelPoint) String() string { return proto.CompactTextStri func (*ChannelPoint) ProtoMessage() {} func (*ChannelPoint) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{11} } -type isChannelPoint_FundingTxid interface{ isChannelPoint_FundingTxid() } +type isChannelPoint_FundingTxid interface { + isChannelPoint_FundingTxid() +} type ChannelPoint_FundingTxidBytes struct { FundingTxidBytes []byte `protobuf:"bytes,1,opt,name=funding_txid_bytes,proto3,oneof"` @@ -1608,7 +1610,9 @@ func (m *CloseStatusUpdate) String() string { return proto.CompactTex func (*CloseStatusUpdate) ProtoMessage() {} func (*CloseStatusUpdate) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{41} } -type isCloseStatusUpdate_Update interface{ isCloseStatusUpdate_Update() } +type isCloseStatusUpdate_Update interface { + isCloseStatusUpdate_Update() +} type CloseStatusUpdate_ClosePending struct { ClosePending *PendingUpdate `protobuf:"bytes,1,opt,name=close_pending,oneof"` @@ -1871,7 +1875,9 @@ func (m *OpenStatusUpdate) String() string { return proto.CompactText func (*OpenStatusUpdate) ProtoMessage() {} func (*OpenStatusUpdate) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{44} } -type isOpenStatusUpdate_Update interface{ isOpenStatusUpdate_Update() } +type isOpenStatusUpdate_Update interface { + isOpenStatusUpdate_Update() +} type OpenStatusUpdate_ChanPending struct { ChanPending *PendingUpdate `protobuf:"bytes,1,opt,name=chan_pending,oneof"` @@ -2090,6 +2096,8 @@ type PendingChannelsResponse struct { PendingClosingChannels []*PendingChannelsResponse_ClosedChannel `protobuf:"bytes,3,rep,name=pending_closing_channels" json:"pending_closing_channels,omitempty"` // / Channels pending force closing PendingForceClosingChannels []*PendingChannelsResponse_ForceClosedChannel `protobuf:"bytes,4,rep,name=pending_force_closing_channels" json:"pending_force_closing_channels,omitempty"` + // / Channels waiting for closing tx to confirm + WaitingCloseChannels []*PendingChannelsResponse_WaitingCloseChannel `protobuf:"bytes,5,rep,name=waiting_close_channels" json:"waiting_close_channels,omitempty"` } func (m *PendingChannelsResponse) Reset() { *m = PendingChannelsResponse{} } @@ -2125,6 +2133,13 @@ func (m *PendingChannelsResponse) GetPendingForceClosingChannels() []*PendingCha return nil } +func (m *PendingChannelsResponse) GetWaitingCloseChannels() []*PendingChannelsResponse_WaitingCloseChannel { + if m != nil { + return m.WaitingCloseChannels + } + return nil +} + type PendingChannelsResponse_PendingChannel struct { RemoteNodePub string `protobuf:"bytes,1,opt,name=remote_node_pub" json:"remote_node_pub,omitempty"` ChannelPoint string `protobuf:"bytes,2,opt,name=channel_point" json:"channel_point,omitempty"` @@ -2244,6 +2259,38 @@ func (m *PendingChannelsResponse_PendingOpenChannel) GetFeePerKw() int64 { return 0 } +type PendingChannelsResponse_WaitingCloseChannel struct { + // / The pending channel waiting for closing tx to confirm + Channel *PendingChannelsResponse_PendingChannel `protobuf:"bytes,1,opt,name=channel" json:"channel,omitempty"` + // / The balance in satoshis encumbered in this channel + LimboBalance int64 `protobuf:"varint,2,opt,name=limbo_balance" json:"limbo_balance,omitempty"` +} + +func (m *PendingChannelsResponse_WaitingCloseChannel) Reset() { + *m = PendingChannelsResponse_WaitingCloseChannel{} +} +func (m *PendingChannelsResponse_WaitingCloseChannel) String() string { + return proto.CompactTextString(m) +} +func (*PendingChannelsResponse_WaitingCloseChannel) ProtoMessage() {} +func (*PendingChannelsResponse_WaitingCloseChannel) Descriptor() ([]byte, []int) { + return fileDescriptor0, []int{47, 2} +} + +func (m *PendingChannelsResponse_WaitingCloseChannel) GetChannel() *PendingChannelsResponse_PendingChannel { + if m != nil { + return m.Channel + } + return nil +} + +func (m *PendingChannelsResponse_WaitingCloseChannel) GetLimboBalance() int64 { + if m != nil { + return m.LimboBalance + } + return 0 +} + type PendingChannelsResponse_ClosedChannel struct { // / The pending channel to be closed Channel *PendingChannelsResponse_PendingChannel `protobuf:"bytes,1,opt,name=channel" json:"channel,omitempty"` @@ -2255,7 +2302,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 fileDescriptor0, []int{47, 2} + return fileDescriptor0, []int{47, 3} } func (m *PendingChannelsResponse_ClosedChannel) GetChannel() *PendingChannelsResponse_PendingChannel { @@ -2299,7 +2346,7 @@ func (m *PendingChannelsResponse_ForceClosedChannel) String() string { } func (*PendingChannelsResponse_ForceClosedChannel) ProtoMessage() {} func (*PendingChannelsResponse_ForceClosedChannel) Descriptor() ([]byte, []int) { - return fileDescriptor0, []int{47, 3} + return fileDescriptor0, []int{47, 4} } func (m *PendingChannelsResponse_ForceClosedChannel) GetChannel() *PendingChannelsResponse_PendingChannel { @@ -3916,7 +3963,9 @@ func (m *PolicyUpdateRequest) String() string { return proto.CompactT func (*PolicyUpdateRequest) ProtoMessage() {} func (*PolicyUpdateRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{94} } -type isPolicyUpdateRequest_Scope interface{ isPolicyUpdateRequest_Scope() } +type isPolicyUpdateRequest_Scope interface { + isPolicyUpdateRequest_Scope() +} type PolicyUpdateRequest_Global struct { Global bool `protobuf:"varint,1,opt,name=global,oneof"` @@ -4234,6 +4283,7 @@ func init() { proto.RegisterType((*PendingChannelsResponse)(nil), "lnrpc.PendingChannelsResponse") proto.RegisterType((*PendingChannelsResponse_PendingChannel)(nil), "lnrpc.PendingChannelsResponse.PendingChannel") proto.RegisterType((*PendingChannelsResponse_PendingOpenChannel)(nil), "lnrpc.PendingChannelsResponse.PendingOpenChannel") + proto.RegisterType((*PendingChannelsResponse_WaitingCloseChannel)(nil), "lnrpc.PendingChannelsResponse.WaitingCloseChannel") proto.RegisterType((*PendingChannelsResponse_ClosedChannel)(nil), "lnrpc.PendingChannelsResponse.ClosedChannel") proto.RegisterType((*PendingChannelsResponse_ForceClosedChannel)(nil), "lnrpc.PendingChannelsResponse.ForceClosedChannel") proto.RegisterType((*WalletBalanceRequest)(nil), "lnrpc.WalletBalanceRequest") @@ -6301,353 +6351,356 @@ var _Lightning_serviceDesc = grpc.ServiceDesc{ func init() { proto.RegisterFile("rpc.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ - // 5566 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x5c, 0xcd, 0x93, 0x1c, 0xc9, - 0x55, 0x57, 0xf5, 0xf4, 0x7c, 0xf4, 0xeb, 0x9e, 0x9e, 0x99, 0x9c, 0xd1, 0xa8, 0xd5, 0xd2, 0x6a, - 0xb5, 0xe5, 0x8d, 0x95, 0x18, 0x16, 0x8d, 0x76, 0x6c, 0x2f, 0xeb, 0x15, 0xac, 0xd1, 0xf7, 0xac, - 0xad, 0x95, 0xc7, 0x35, 0x92, 0x17, 0xbc, 0x40, 0xbb, 0xa6, 0x3b, 0xa7, 0xa7, 0xac, 0xea, 0xaa, - 0xda, 0xaa, 0xea, 0x19, 0xf5, 0x2e, 0x8a, 0xe0, 0x23, 0x82, 0x13, 0x0e, 0x0e, 0x70, 0x31, 0x04, - 0x41, 0x84, 0x7d, 0x81, 0x03, 0x47, 0x4e, 0xe6, 0x2f, 0x70, 0x04, 0xc1, 0x61, 0x4f, 0x0e, 0x6e, - 0x7c, 0x1c, 0xc0, 0xc1, 0x85, 0x08, 0x2e, 0x1c, 0x08, 0xe2, 0xbd, 0xfc, 0xa8, 0xcc, 0xaa, 0x1a, - 0x49, 0xb6, 0xc1, 0xb7, 0xce, 0x5f, 0xbe, 0x7a, 0xf9, 0xf5, 0xde, 0xcb, 0xf7, 0x5e, 0x66, 0x36, - 0xb4, 0xd2, 0x64, 0x78, 0x2d, 0x49, 0xe3, 0x3c, 0x66, 0xf3, 0x61, 0x94, 0x26, 0xc3, 0xfe, 0xc5, - 0x71, 0x1c, 0x8f, 0x43, 0xbe, 0xed, 0x27, 0xc1, 0xb6, 0x1f, 0x45, 0x71, 0xee, 0xe7, 0x41, 0x1c, - 0x65, 0x82, 0xc8, 0xfd, 0x16, 0x74, 0xef, 0xf3, 0x68, 0x9f, 0xf3, 0x91, 0xc7, 0x3f, 0x9e, 0xf2, - 0x2c, 0x67, 0xbf, 0x08, 0x6b, 0x3e, 0xff, 0x84, 0xf3, 0xd1, 0x20, 0xf1, 0xb3, 0x2c, 0x39, 0x4a, - 0xfd, 0x8c, 0xf7, 0x9c, 0xcb, 0xce, 0xd5, 0x8e, 0xb7, 0x2a, 0x2a, 0xf6, 0x34, 0xce, 0x5e, 0x83, - 0x4e, 0x86, 0xa4, 0x3c, 0xca, 0xd3, 0x38, 0x99, 0xf5, 0x1a, 0x44, 0xd7, 0x46, 0xec, 0xae, 0x80, - 0xdc, 0x10, 0x56, 0x74, 0x0b, 0x59, 0x12, 0x47, 0x19, 0x67, 0xd7, 0x61, 0x63, 0x18, 0x24, 0x47, - 0x3c, 0x1d, 0xd0, 0xc7, 0x93, 0x88, 0x4f, 0xe2, 0x28, 0x18, 0xf6, 0x9c, 0xcb, 0x73, 0x57, 0x5b, - 0x1e, 0x13, 0x75, 0xf8, 0xc5, 0x07, 0xb2, 0x86, 0x5d, 0x81, 0x15, 0x1e, 0x09, 0x9c, 0x8f, 0xe8, - 0x2b, 0xd9, 0x54, 0xb7, 0x80, 0xf1, 0x03, 0xf7, 0xcf, 0x1d, 0x58, 0x7b, 0x3f, 0x0a, 0xf2, 0x0f, - 0xfd, 0x30, 0xe4, 0xb9, 0x1a, 0xd3, 0x15, 0x58, 0x39, 0x21, 0x80, 0xc6, 0x74, 0x12, 0xa7, 0x23, - 0x39, 0xa2, 0xae, 0x80, 0xf7, 0x24, 0x7a, 0x6a, 0xcf, 0x1a, 0xa7, 0xf6, 0xac, 0x76, 0xba, 0xe6, - 0xea, 0xa7, 0xcb, 0xdd, 0x00, 0x66, 0x76, 0x4e, 0x4c, 0x87, 0xfb, 0x1e, 0xac, 0x3f, 0x8e, 0xc2, - 0x78, 0xf8, 0xe4, 0xa7, 0xeb, 0xb4, 0xbb, 0x09, 0x1b, 0xf6, 0xf7, 0x92, 0xef, 0x77, 0x1b, 0xd0, - 0x7e, 0x94, 0xfa, 0x51, 0xe6, 0x0f, 0x71, 0xc9, 0x59, 0x0f, 0x16, 0xf3, 0xa7, 0x83, 0x23, 0x3f, - 0x3b, 0x22, 0x46, 0x2d, 0x4f, 0x15, 0xd9, 0x26, 0x2c, 0xf8, 0x93, 0x78, 0x1a, 0xe5, 0x34, 0xab, - 0x73, 0x9e, 0x2c, 0xb1, 0x37, 0x61, 0x2d, 0x9a, 0x4e, 0x06, 0xc3, 0x38, 0x3a, 0x0c, 0xd2, 0x89, - 0x10, 0x1c, 0x1a, 0xdc, 0xbc, 0x57, 0xad, 0x60, 0x97, 0x00, 0x0e, 0xb0, 0x1b, 0xa2, 0x89, 0x26, - 0x35, 0x61, 0x20, 0xcc, 0x85, 0x8e, 0x2c, 0xf1, 0x60, 0x7c, 0x94, 0xf7, 0xe6, 0x89, 0x91, 0x85, - 0x21, 0x8f, 0x3c, 0x98, 0xf0, 0x41, 0x96, 0xfb, 0x93, 0xa4, 0xb7, 0x40, 0xbd, 0x31, 0x10, 0xaa, - 0x8f, 0x73, 0x3f, 0x1c, 0x1c, 0x72, 0x9e, 0xf5, 0x16, 0x65, 0xbd, 0x46, 0xd8, 0x1b, 0xd0, 0x1d, - 0xf1, 0x2c, 0x1f, 0xf8, 0xa3, 0x51, 0xca, 0xb3, 0x8c, 0x67, 0xbd, 0x25, 0x5a, 0xba, 0x12, 0xea, - 0xf6, 0x60, 0xf3, 0x3e, 0xcf, 0x8d, 0xd9, 0xc9, 0xe4, 0xb4, 0xbb, 0x0f, 0x80, 0x19, 0xf0, 0x1d, - 0x9e, 0xfb, 0x41, 0x98, 0xb1, 0xb7, 0xa1, 0x93, 0x1b, 0xc4, 0x24, 0xaa, 0xed, 0x1d, 0x76, 0x8d, - 0x74, 0xec, 0x9a, 0xf1, 0x81, 0x67, 0xd1, 0xb9, 0xff, 0xed, 0x40, 0x7b, 0x9f, 0x47, 0x5a, 0xbb, - 0x18, 0x34, 0xb1, 0x27, 0x72, 0x25, 0xe9, 0x37, 0x7b, 0x15, 0xda, 0xd4, 0xbb, 0x2c, 0x4f, 0x83, - 0x68, 0x4c, 0x4b, 0xd0, 0xf2, 0x00, 0xa1, 0x7d, 0x42, 0xd8, 0x2a, 0xcc, 0xf9, 0x93, 0x9c, 0x26, - 0x7e, 0xce, 0xc3, 0x9f, 0xa8, 0x77, 0x89, 0x3f, 0x9b, 0xf0, 0x28, 0x2f, 0x26, 0xbb, 0xe3, 0xb5, - 0x25, 0xb6, 0x8b, 0xb3, 0x7d, 0x0d, 0xd6, 0x4d, 0x12, 0xc5, 0x7d, 0x9e, 0xb8, 0xaf, 0x19, 0x94, - 0xb2, 0x91, 0x2b, 0xb0, 0xa2, 0xe8, 0x53, 0xd1, 0x59, 0x9a, 0xfe, 0x96, 0xd7, 0x95, 0xb0, 0x1a, - 0xc2, 0x55, 0x58, 0x3d, 0x0c, 0x22, 0x3f, 0x1c, 0x0c, 0xc3, 0xfc, 0x78, 0x30, 0xe2, 0x61, 0xee, - 0xd3, 0x42, 0xcc, 0x7b, 0x5d, 0xc2, 0x6f, 0x87, 0xf9, 0xf1, 0x1d, 0x44, 0xdd, 0x3f, 0x75, 0xa0, - 0x23, 0x06, 0x2f, 0x15, 0xff, 0x75, 0x58, 0x56, 0x6d, 0xf0, 0x34, 0x8d, 0x53, 0x29, 0x87, 0x36, - 0xc8, 0xb6, 0x60, 0x55, 0x01, 0x49, 0xca, 0x83, 0x89, 0x3f, 0xe6, 0x52, 0xdb, 0x2b, 0x38, 0xdb, - 0x29, 0x38, 0xa6, 0xf1, 0x34, 0x17, 0xaa, 0xd7, 0xde, 0xe9, 0xc8, 0x85, 0xf1, 0x10, 0xf3, 0x6c, - 0x12, 0xf7, 0x7b, 0x0e, 0x74, 0x6e, 0x1f, 0xf9, 0x51, 0xc4, 0xc3, 0xbd, 0x38, 0x88, 0x72, 0x76, - 0x1d, 0xd8, 0xe1, 0x34, 0x1a, 0x05, 0xd1, 0x78, 0x90, 0x3f, 0x0d, 0x46, 0x83, 0x83, 0x59, 0xce, - 0x33, 0xb1, 0x44, 0xbb, 0x67, 0xbc, 0x9a, 0x3a, 0xf6, 0x26, 0xac, 0x5a, 0x68, 0x96, 0xa7, 0x62, - 0xdd, 0x76, 0xcf, 0x78, 0x95, 0x1a, 0x14, 0xfc, 0x78, 0x9a, 0x27, 0xd3, 0x7c, 0x10, 0x44, 0x23, - 0xfe, 0x94, 0xfa, 0xb8, 0xec, 0x59, 0xd8, 0xad, 0x2e, 0x74, 0xcc, 0xef, 0xdc, 0xf7, 0x60, 0xf5, - 0x01, 0x6a, 0x44, 0x14, 0x44, 0xe3, 0x9b, 0x42, 0x6c, 0x51, 0x4d, 0x93, 0xe9, 0xc1, 0x13, 0x3e, - 0x93, 0xf3, 0x26, 0x4b, 0x28, 0x54, 0x47, 0x71, 0x96, 0x4b, 0xc9, 0xa1, 0xdf, 0xee, 0x3f, 0x3b, - 0xb0, 0x82, 0x73, 0xff, 0x81, 0x1f, 0xcd, 0xd4, 0xca, 0x3d, 0x80, 0x0e, 0xb2, 0x7a, 0x14, 0xdf, - 0x14, 0xca, 0x2e, 0x84, 0xf8, 0xaa, 0x9c, 0xab, 0x12, 0xf5, 0x35, 0x93, 0x14, 0x8d, 0xf9, 0xcc, - 0xb3, 0xbe, 0x46, 0xb1, 0xcd, 0xfd, 0x74, 0xcc, 0x73, 0x32, 0x03, 0xd2, 0x2c, 0x80, 0x80, 0x6e, - 0xc7, 0xd1, 0x21, 0xbb, 0x0c, 0x9d, 0xcc, 0xcf, 0x07, 0x09, 0x4f, 0x69, 0xd6, 0x48, 0xf4, 0xe6, - 0x3c, 0xc8, 0xfc, 0x7c, 0x8f, 0xa7, 0xb7, 0x66, 0x39, 0xef, 0x7f, 0x19, 0xd6, 0x2a, 0xad, 0xa0, - 0xb4, 0x17, 0x43, 0xc4, 0x9f, 0x6c, 0x03, 0xe6, 0x8f, 0xfd, 0x70, 0xca, 0xa5, 0x75, 0x12, 0x85, - 0x77, 0x1b, 0xef, 0x38, 0xee, 0x1b, 0xb0, 0x5a, 0x74, 0x5b, 0x0a, 0x19, 0x83, 0x26, 0xce, 0xa0, - 0x64, 0x40, 0xbf, 0xdd, 0xdf, 0x73, 0x04, 0xe1, 0xed, 0x38, 0xd0, 0x9a, 0x8e, 0x84, 0x68, 0x10, - 0x14, 0x21, 0xfe, 0x3e, 0xd5, 0x12, 0xfe, 0xec, 0x83, 0x75, 0xaf, 0xc0, 0x9a, 0xd1, 0x85, 0xe7, - 0x74, 0xf6, 0x3b, 0x0e, 0xac, 0x3d, 0xe4, 0x27, 0x72, 0xd5, 0x55, 0x6f, 0xdf, 0x81, 0x66, 0x3e, - 0x4b, 0xc4, 0x56, 0xdc, 0xdd, 0x79, 0x5d, 0x2e, 0x5a, 0x85, 0xee, 0x9a, 0x2c, 0x3e, 0x9a, 0x25, - 0xdc, 0xa3, 0x2f, 0xdc, 0xf7, 0xa0, 0x6d, 0x80, 0xec, 0x1c, 0xac, 0x7f, 0xf8, 0xfe, 0xa3, 0x87, - 0x77, 0xf7, 0xf7, 0x07, 0x7b, 0x8f, 0x6f, 0x7d, 0xf5, 0xee, 0x6f, 0x0c, 0x76, 0x6f, 0xee, 0xef, - 0xae, 0x9e, 0x61, 0x9b, 0xc0, 0x1e, 0xde, 0xdd, 0x7f, 0x74, 0xf7, 0x8e, 0x85, 0x3b, 0x6e, 0x1f, - 0x7a, 0x0f, 0xf9, 0xc9, 0x87, 0x41, 0x1e, 0xf1, 0x2c, 0xb3, 0x5b, 0x73, 0xaf, 0x01, 0x33, 0xbb, - 0x20, 0x47, 0xd5, 0x83, 0x45, 0x69, 0x6a, 0xd5, 0x4e, 0x23, 0x8b, 0xee, 0x1b, 0xc0, 0xf6, 0x83, - 0x71, 0xf4, 0x01, 0xcf, 0x32, 0x7f, 0xcc, 0xd5, 0xd8, 0x56, 0x61, 0x6e, 0x92, 0x8d, 0xa5, 0x51, - 0xc4, 0x9f, 0xee, 0xe7, 0x61, 0xdd, 0xa2, 0x93, 0x8c, 0x2f, 0x42, 0x2b, 0x0b, 0xc6, 0x91, 0x9f, - 0x4f, 0x53, 0x2e, 0x59, 0x17, 0x80, 0x7b, 0x0f, 0x36, 0xbe, 0xc1, 0xd3, 0xe0, 0x70, 0xf6, 0x22, - 0xf6, 0x36, 0x9f, 0x46, 0x99, 0xcf, 0x5d, 0x38, 0x5b, 0xe2, 0x23, 0x9b, 0x17, 0x82, 0x28, 0x97, - 0x6b, 0xc9, 0x13, 0x05, 0x43, 0x2d, 0x1b, 0xa6, 0x5a, 0xba, 0x8f, 0x81, 0xdd, 0x8e, 0xa3, 0x88, - 0x0f, 0xf3, 0x3d, 0xce, 0xd3, 0xc2, 0xbf, 0x2a, 0xa4, 0xae, 0xbd, 0x73, 0x4e, 0xae, 0x63, 0x59, - 0xd7, 0xa5, 0x38, 0x32, 0x68, 0x26, 0x3c, 0x9d, 0x10, 0xe3, 0x25, 0x8f, 0x7e, 0xbb, 0x67, 0x61, - 0xdd, 0x62, 0x2b, 0x77, 0xfb, 0xb7, 0xe0, 0xec, 0x9d, 0x20, 0x1b, 0x56, 0x1b, 0xec, 0xc1, 0x62, - 0x32, 0x3d, 0x18, 0x14, 0x3a, 0xa5, 0x8a, 0xb8, 0x09, 0x96, 0x3f, 0x91, 0xcc, 0xfe, 0xd0, 0x81, - 0xe6, 0xee, 0xa3, 0x07, 0xb7, 0x59, 0x1f, 0x96, 0x82, 0x68, 0x18, 0x4f, 0x70, 0xeb, 0x10, 0x83, - 0xd6, 0xe5, 0x53, 0x75, 0xe5, 0x22, 0xb4, 0x68, 0xc7, 0xc1, 0x7d, 0x5d, 0xba, 0x42, 0x05, 0x80, - 0x3e, 0x05, 0x7f, 0x9a, 0x04, 0x29, 0x39, 0x0d, 0xca, 0x15, 0x68, 0x92, 0x45, 0xac, 0x56, 0xb8, - 0xff, 0xd3, 0x84, 0x45, 0x69, 0xab, 0xa9, 0xbd, 0x61, 0x1e, 0x1c, 0x73, 0xd9, 0x13, 0x59, 0xc2, - 0x5d, 0x25, 0xe5, 0x93, 0x38, 0xe7, 0x03, 0x6b, 0x19, 0x6c, 0x10, 0xa9, 0x86, 0x82, 0xd1, 0x20, - 0x41, 0xab, 0x4f, 0x3d, 0x6b, 0x79, 0x36, 0x88, 0x93, 0x85, 0xc0, 0x20, 0x18, 0x51, 0x9f, 0x9a, - 0x9e, 0x2a, 0xe2, 0x4c, 0x0c, 0xfd, 0xc4, 0x1f, 0x06, 0xf9, 0x4c, 0x2a, 0xb7, 0x2e, 0x23, 0xef, - 0x30, 0x1e, 0xfa, 0xe1, 0xe0, 0xc0, 0x0f, 0xfd, 0x68, 0xc8, 0xa5, 0xe3, 0x62, 0x83, 0xe8, 0x9b, - 0xc8, 0x2e, 0x29, 0x32, 0xe1, 0xbf, 0x94, 0x50, 0xf4, 0x71, 0x86, 0xf1, 0x64, 0x12, 0xe4, 0xe8, - 0xd2, 0xf4, 0x96, 0x84, 0x21, 0x29, 0x10, 0x1a, 0x89, 0x28, 0x9d, 0x88, 0xd9, 0x6b, 0x89, 0xd6, - 0x2c, 0x10, 0xb9, 0x1c, 0x72, 0x4e, 0x06, 0xe9, 0xc9, 0x49, 0x0f, 0x04, 0x97, 0x02, 0xc1, 0x75, - 0x98, 0x46, 0x19, 0xcf, 0xf3, 0x90, 0x8f, 0x74, 0x87, 0xda, 0x44, 0x56, 0xad, 0x60, 0xd7, 0x61, - 0x5d, 0x78, 0x59, 0x99, 0x9f, 0xc7, 0xd9, 0x51, 0x90, 0x0d, 0x32, 0x1e, 0xe5, 0xbd, 0x0e, 0xd1, - 0xd7, 0x55, 0xb1, 0x77, 0xe0, 0x5c, 0x09, 0x4e, 0xf9, 0x90, 0x07, 0xc7, 0x7c, 0xd4, 0x5b, 0xa6, - 0xaf, 0x4e, 0xab, 0x66, 0x97, 0xa1, 0x8d, 0xce, 0xe5, 0x34, 0x19, 0xf9, 0xb8, 0x0f, 0x77, 0x69, - 0x1d, 0x4c, 0x88, 0xbd, 0x05, 0xcb, 0x09, 0x17, 0x9b, 0xe5, 0x51, 0x1e, 0x0e, 0xb3, 0xde, 0x0a, - 0xed, 0x64, 0x6d, 0xa9, 0x4c, 0x28, 0xb9, 0x9e, 0x4d, 0x81, 0x42, 0x39, 0xcc, 0xc8, 0x5d, 0xf1, - 0x67, 0xbd, 0x55, 0x12, 0xb7, 0x02, 0x20, 0x1d, 0x49, 0x83, 0x63, 0x3f, 0xe7, 0xbd, 0x35, 0x92, - 0x2d, 0x55, 0x74, 0xff, 0xd2, 0x81, 0xf5, 0x07, 0x41, 0x96, 0x4b, 0x21, 0xd4, 0xe6, 0xf8, 0x55, - 0x68, 0x0b, 0xf1, 0x1b, 0xc4, 0x51, 0x38, 0x93, 0x12, 0x09, 0x02, 0xfa, 0x5a, 0x14, 0xce, 0xd8, - 0xe7, 0x60, 0x39, 0x88, 0x4c, 0x12, 0xa1, 0xc3, 0x1d, 0x05, 0x12, 0xd1, 0xab, 0xd0, 0x4e, 0xa6, - 0x07, 0x61, 0x30, 0x14, 0x24, 0x73, 0x82, 0x8b, 0x80, 0x88, 0x00, 0x1d, 0x3d, 0xd1, 0x13, 0x41, - 0xd1, 0x24, 0x8a, 0xb6, 0xc4, 0x90, 0xc4, 0xbd, 0x05, 0x1b, 0x76, 0x07, 0xa5, 0xb1, 0xda, 0x82, - 0x25, 0x29, 0xdb, 0x59, 0xaf, 0x4d, 0xf3, 0xd3, 0x95, 0xf3, 0x23, 0x49, 0x3d, 0x5d, 0xef, 0xfe, - 0xbb, 0x03, 0x4d, 0x34, 0x00, 0xa7, 0x1b, 0x0b, 0xd3, 0xa6, 0xcf, 0x59, 0x36, 0x9d, 0xfc, 0x7e, - 0xf4, 0x8a, 0x84, 0x48, 0x08, 0xb5, 0x31, 0x90, 0xa2, 0x3e, 0xe5, 0xc3, 0x63, 0xd2, 0x1d, 0x5d, - 0x8f, 0x08, 0x6a, 0x16, 0x6e, 0x9d, 0xf4, 0xb5, 0x50, 0x1c, 0x5d, 0x56, 0x75, 0xf4, 0xe5, 0x62, - 0x51, 0x47, 0xdf, 0xf5, 0x60, 0x31, 0x88, 0x0e, 0xe2, 0x69, 0x34, 0x22, 0x25, 0x59, 0xf2, 0x54, - 0x11, 0x17, 0x3b, 0x21, 0x4f, 0x2a, 0x98, 0x70, 0xa9, 0x1d, 0x05, 0xe0, 0x32, 0x74, 0xad, 0x32, - 0x32, 0x78, 0x7a, 0x1f, 0x7b, 0x1b, 0xd6, 0x0c, 0x4c, 0xce, 0xe0, 0x6b, 0x30, 0x9f, 0x20, 0x20, - 0x1d, 0x25, 0x25, 0x5e, 0x64, 0x29, 0x45, 0x8d, 0xbb, 0x8a, 0xf1, 0x73, 0xfe, 0x7e, 0x74, 0x18, - 0x2b, 0x4e, 0x3f, 0x9a, 0xc3, 0x80, 0x57, 0x42, 0x92, 0xd1, 0x55, 0x58, 0x09, 0x46, 0x3c, 0xca, - 0x83, 0x7c, 0x36, 0xb0, 0x3c, 0xb8, 0x32, 0x8c, 0x3b, 0x8c, 0x1f, 0x06, 0x7e, 0x26, 0x6d, 0x98, - 0x28, 0xb0, 0x1d, 0xd8, 0x40, 0xf1, 0x57, 0x12, 0xad, 0x97, 0x55, 0x38, 0x92, 0xb5, 0x75, 0xa8, - 0xb1, 0x88, 0x4b, 0x09, 0xd4, 0x9f, 0x08, 0x4b, 0x5b, 0x57, 0x85, 0xb3, 0x26, 0x38, 0xe1, 0x90, - 0xe7, 0x85, 0x8a, 0x68, 0xa0, 0x12, 0xbd, 0x2d, 0x08, 0x27, 0xb6, 0x1c, 0xbd, 0x19, 0x11, 0xe0, - 0x52, 0x25, 0x02, 0xbc, 0x0a, 0x2b, 0xd9, 0x2c, 0x1a, 0xf2, 0xd1, 0x20, 0x8f, 0xb1, 0xdd, 0x20, - 0xa2, 0xd5, 0x59, 0xf2, 0xca, 0x30, 0xc5, 0xaa, 0x3c, 0xcb, 0x23, 0x9e, 0x93, 0xe9, 0x5a, 0xf2, - 0x54, 0x11, 0x77, 0x01, 0x22, 0x11, 0x42, 0xdd, 0xf2, 0x64, 0x09, 0xb7, 0xca, 0x69, 0x1a, 0x64, - 0xbd, 0x0e, 0xa1, 0xf4, 0x9b, 0x7d, 0x01, 0xce, 0x1e, 0x60, 0x64, 0x75, 0xc4, 0xfd, 0x11, 0x4f, - 0x69, 0xf5, 0x45, 0x60, 0x29, 0x2c, 0x50, 0x7d, 0x25, 0xb6, 0x7d, 0xcc, 0xd3, 0x2c, 0x88, 0x23, - 0xb2, 0x3d, 0x2d, 0x4f, 0x15, 0xdd, 0x4f, 0x68, 0x47, 0xd7, 0x21, 0xef, 0x63, 0x32, 0x47, 0xec, - 0x02, 0xb4, 0xc4, 0x18, 0xb3, 0x23, 0x5f, 0x3a, 0x19, 0x4b, 0x04, 0xec, 0x1f, 0xf9, 0xa8, 0xc0, - 0xd6, 0xb4, 0x35, 0xc8, 0x73, 0x6c, 0x13, 0xb6, 0x2b, 0x66, 0xed, 0x75, 0xe8, 0xaa, 0x60, 0x3a, - 0x1b, 0x84, 0xfc, 0x30, 0x57, 0x01, 0x42, 0x34, 0x9d, 0x60, 0x73, 0xd9, 0x03, 0x7e, 0x98, 0xbb, - 0x0f, 0x61, 0x4d, 0xea, 0xed, 0xd7, 0x12, 0xae, 0x9a, 0xfe, 0x52, 0x79, 0x53, 0x13, 0x5e, 0xc5, - 0xba, 0xad, 0xe8, 0x14, 0xe5, 0x94, 0x76, 0x3a, 0xd7, 0x03, 0x26, 0xab, 0x6f, 0x87, 0x71, 0xc6, - 0x25, 0x43, 0x17, 0x3a, 0xc3, 0x30, 0xce, 0x54, 0x18, 0x22, 0x87, 0x63, 0x61, 0x38, 0x3f, 0xd9, - 0x74, 0x38, 0x44, 0x4b, 0x20, 0x6c, 0x9a, 0x2a, 0xba, 0x7f, 0xe5, 0xc0, 0x3a, 0x71, 0x53, 0x16, - 0x46, 0xfb, 0xae, 0x2f, 0xdf, 0xcd, 0xce, 0xd0, 0x0c, 0xcd, 0x36, 0x60, 0xfe, 0x30, 0x4e, 0x87, - 0x5c, 0xb6, 0x24, 0x0a, 0x3f, 0xb9, 0x37, 0xde, 0xac, 0x78, 0xe3, 0x3f, 0x72, 0x60, 0x8d, 0xba, - 0xba, 0x9f, 0xfb, 0xf9, 0x34, 0x93, 0xc3, 0xff, 0x15, 0x58, 0xc6, 0xa1, 0x72, 0xa5, 0x4e, 0xb2, - 0xa3, 0x1b, 0x5a, 0xf3, 0x09, 0x15, 0xc4, 0xbb, 0x67, 0x3c, 0x9b, 0x98, 0x7d, 0x19, 0x3a, 0x66, - 0x46, 0x84, 0xfa, 0xdc, 0xde, 0x39, 0xaf, 0x46, 0x59, 0x91, 0x9c, 0xdd, 0x33, 0x9e, 0xf5, 0x01, - 0xbb, 0x01, 0x40, 0xee, 0x06, 0xb1, 0x95, 0xa1, 0xec, 0x79, 0x7b, 0x92, 0x8c, 0xc5, 0xda, 0x3d, - 0xe3, 0x19, 0xe4, 0xb7, 0x96, 0x60, 0x41, 0xec, 0x8f, 0xee, 0x7d, 0x58, 0xb6, 0x7a, 0x6a, 0x45, - 0x19, 0x1d, 0x11, 0x65, 0x54, 0x82, 0xd2, 0x46, 0x35, 0x28, 0x75, 0xff, 0xb5, 0x01, 0x0c, 0xa5, - 0xad, 0xb4, 0x9c, 0xb8, 0x41, 0xc7, 0x23, 0xcb, 0xdd, 0xea, 0x78, 0x26, 0xc4, 0xae, 0x01, 0x33, - 0x8a, 0x2a, 0xf7, 0x20, 0xf6, 0x8d, 0x9a, 0x1a, 0x34, 0x70, 0xc2, 0x57, 0x52, 0x31, 0xb0, 0x74, - 0x2c, 0xc5, 0xba, 0xd5, 0xd6, 0xe1, 0xd6, 0x90, 0x4c, 0xb3, 0x23, 0x74, 0x20, 0x94, 0x43, 0xa6, - 0xca, 0x65, 0x01, 0x59, 0x78, 0xa1, 0x80, 0x2c, 0x96, 0x05, 0xc4, 0x74, 0x09, 0x96, 0x2c, 0x97, - 0x00, 0xfd, 0xaf, 0x49, 0x10, 0x91, 0x5f, 0x31, 0x98, 0x60, 0xeb, 0xd2, 0xff, 0xb2, 0x40, 0xb6, - 0x05, 0xab, 0xd2, 0xaf, 0x2b, 0xfc, 0x0e, 0xa0, 0x39, 0xae, 0xe0, 0xee, 0x67, 0x0e, 0xac, 0xe2, - 0x3c, 0x5b, 0xb2, 0xf8, 0x2e, 0x90, 0x2a, 0xbc, 0xa4, 0x28, 0x5a, 0xb4, 0x3f, 0xbb, 0x24, 0xbe, - 0x03, 0x2d, 0x62, 0x18, 0x27, 0x3c, 0x92, 0x82, 0xd8, 0xb3, 0x05, 0xb1, 0xb0, 0x42, 0xbb, 0x67, - 0xbc, 0x82, 0xd8, 0x10, 0xc3, 0x7f, 0x70, 0xa0, 0x2d, 0xbb, 0xf9, 0x53, 0xc7, 0x12, 0x7d, 0x58, - 0x42, 0x89, 0x34, 0x1c, 0x76, 0x5d, 0xc6, 0xdd, 0x64, 0x82, 0x01, 0x1b, 0x6e, 0x9f, 0x56, 0x1c, - 0x51, 0x86, 0x71, 0x2f, 0x24, 0x83, 0x9b, 0x0d, 0xf2, 0x20, 0x1c, 0xa8, 0x5a, 0x99, 0x80, 0xac, - 0xab, 0x42, 0xbb, 0x93, 0xe5, 0xfe, 0x98, 0xcb, 0x6d, 0x4e, 0x14, 0x30, 0x60, 0x92, 0x03, 0x2a, - 0xb9, 0x83, 0xee, 0x0f, 0x01, 0xce, 0x55, 0xaa, 0x74, 0xba, 0x5b, 0x3a, 0xc8, 0x61, 0x30, 0x39, - 0x88, 0xb5, 0xaf, 0xed, 0x98, 0xbe, 0xb3, 0x55, 0xc5, 0xc6, 0x70, 0x56, 0xed, 0xe7, 0x38, 0xa7, - 0xc5, 0xee, 0xdd, 0x20, 0x47, 0xe4, 0x2d, 0x5b, 0x06, 0xca, 0x0d, 0x2a, 0xdc, 0xd4, 0xdc, 0x7a, - 0x7e, 0xec, 0x08, 0x7a, 0xda, 0x71, 0x90, 0x26, 0xde, 0x70, 0x2e, 0xb0, 0xad, 0x37, 0x5f, 0xd0, - 0x16, 0xd9, 0xa3, 0x91, 0x6a, 0xe6, 0x54, 0x6e, 0x6c, 0x06, 0x97, 0x54, 0x1d, 0xd9, 0xf0, 0x6a, - 0x7b, 0xcd, 0x97, 0x1a, 0xdb, 0x3d, 0xfc, 0xd8, 0x6e, 0xf4, 0x05, 0x8c, 0xfb, 0x3f, 0x74, 0xa0, - 0x6b, 0xb3, 0x43, 0xd1, 0x91, 0x4a, 0xa8, 0x8c, 0x91, 0x72, 0xc8, 0x4a, 0x70, 0x35, 0x6c, 0x6c, - 0xd4, 0x85, 0x8d, 0x66, 0x70, 0x38, 0xf7, 0xa2, 0xe0, 0xb0, 0xf9, 0x72, 0xc1, 0xe1, 0x7c, 0x5d, - 0x70, 0xd8, 0xff, 0x2f, 0x07, 0x58, 0x75, 0x7d, 0xd9, 0x7d, 0x11, 0xb7, 0x46, 0x3c, 0x94, 0x76, - 0xe2, 0x97, 0x5e, 0x4e, 0x46, 0xd4, 0x1c, 0xaa, 0xaf, 0x51, 0x58, 0x4d, 0x43, 0x60, 0xba, 0x2d, - 0xcb, 0x5e, 0x5d, 0x55, 0x29, 0x5c, 0x6d, 0xbe, 0x38, 0x5c, 0x9d, 0x7f, 0x71, 0xb8, 0xba, 0x50, - 0x0e, 0x57, 0xfb, 0xbf, 0x03, 0xcb, 0xd6, 0xaa, 0xff, 0xdf, 0x8d, 0xb8, 0xec, 0xf2, 0x88, 0x05, - 0xb6, 0xb0, 0xfe, 0x8f, 0x1b, 0xc0, 0xaa, 0x92, 0xf7, 0x73, 0xed, 0x03, 0xc9, 0x91, 0x65, 0x40, - 0xe6, 0xa4, 0x1c, 0x59, 0xa6, 0xe3, 0xff, 0xd3, 0x28, 0xbe, 0x09, 0x6b, 0x29, 0x1f, 0xc6, 0xc7, - 0x74, 0x08, 0x67, 0xa7, 0x3a, 0xaa, 0x15, 0xe8, 0xf4, 0xd9, 0x41, 0xfa, 0x92, 0x75, 0x66, 0x62, - 0xec, 0x0c, 0xa5, 0x58, 0xdd, 0xdd, 0x84, 0x0d, 0x71, 0x94, 0x75, 0x4b, 0xb0, 0x52, 0x46, 0xf6, - 0x2f, 0x1c, 0x38, 0x5b, 0xaa, 0x28, 0x0e, 0x16, 0x84, 0x1d, 0xb5, 0x8d, 0xab, 0x0d, 0x62, 0xff, - 0xa5, 0x00, 0x1b, 0xfd, 0x17, 0xfb, 0x4d, 0xb5, 0x02, 0xe7, 0x67, 0x1a, 0x55, 0xe9, 0xc5, 0xac, - 0xd7, 0x55, 0xb9, 0xe7, 0xe0, 0xac, 0x5c, 0xd9, 0x52, 0xc7, 0x0f, 0x61, 0xb3, 0x5c, 0x51, 0x64, - 0x4a, 0xed, 0x2e, 0xab, 0x22, 0xba, 0x44, 0x96, 0xcd, 0xb6, 0xfb, 0x5b, 0x5b, 0xe7, 0xfe, 0x36, - 0xb0, 0xaf, 0x4f, 0x79, 0x3a, 0xa3, 0x63, 0x0f, 0x9d, 0xaa, 0x38, 0x57, 0x8e, 0xe9, 0x17, 0x92, - 0xe9, 0xc1, 0x57, 0xf9, 0x4c, 0x9d, 0x2b, 0x35, 0x8a, 0x73, 0xa5, 0x57, 0x00, 0x30, 0x14, 0xa1, - 0x73, 0x12, 0x75, 0xd2, 0x87, 0x31, 0xa0, 0x60, 0xe8, 0xde, 0x80, 0x75, 0x8b, 0xbf, 0x9e, 0xfd, - 0x05, 0xf9, 0x85, 0x08, 0x94, 0xed, 0xd3, 0x17, 0x59, 0xe7, 0xfe, 0x87, 0x03, 0x73, 0xbb, 0x71, - 0x62, 0xa6, 0xd8, 0x1c, 0x3b, 0xc5, 0x26, 0x6d, 0xed, 0x40, 0x9b, 0xd2, 0x86, 0xb4, 0x14, 0x26, - 0xc8, 0xb6, 0xa0, 0xeb, 0x4f, 0x72, 0x0c, 0x15, 0x0f, 0xe3, 0xf4, 0xc4, 0x4f, 0x47, 0x62, 0x49, - 0x6e, 0x35, 0x7a, 0x8e, 0x57, 0xaa, 0x61, 0x1b, 0x30, 0xa7, 0x8d, 0x12, 0x11, 0x60, 0x11, 0x9d, - 0x0d, 0xca, 0x34, 0xce, 0x64, 0x94, 0x2b, 0x4b, 0xb8, 0xe2, 0xf6, 0xf7, 0xc2, 0xbd, 0x13, 0x12, - 0x5e, 0x57, 0x85, 0x76, 0x1f, 0x6d, 0x14, 0x91, 0xc9, 0xf4, 0x84, 0x2a, 0xbb, 0xff, 0xe6, 0xc0, - 0x3c, 0xcd, 0x00, 0xea, 0xa4, 0x10, 0x44, 0x3a, 0xc8, 0xa4, 0xb4, 0xa8, 0x23, 0x74, 0xb2, 0x04, - 0x33, 0xd7, 0x3a, 0xde, 0x6c, 0xe8, 0x6e, 0x9b, 0x47, 0x9c, 0x97, 0xa1, 0x25, 0x4a, 0xfa, 0x4c, - 0x90, 0x48, 0x0a, 0x90, 0x5d, 0x82, 0xe6, 0x51, 0x9c, 0xa8, 0x1d, 0x15, 0x54, 0x56, 0x2c, 0x4e, - 0x3c, 0xc2, 0x8b, 0xfe, 0x20, 0x3f, 0xd1, 0x79, 0x61, 0x93, 0xcb, 0x30, 0xee, 0x4a, 0x9a, 0xad, - 0x39, 0x19, 0x25, 0xd4, 0xdd, 0x82, 0x95, 0x87, 0xf1, 0x88, 0x1b, 0x79, 0x90, 0x53, 0xa5, 0xce, - 0xfd, 0x5d, 0x07, 0x96, 0x14, 0x31, 0xbb, 0x0a, 0x4d, 0xdc, 0x6a, 0x4b, 0xce, 0xad, 0xce, 0x86, - 0x23, 0x9d, 0x47, 0x14, 0x68, 0x22, 0x29, 0x4a, 0x2e, 0x5c, 0x21, 0x15, 0x23, 0x17, 0x4e, 0x86, - 0xee, 0x6e, 0x69, 0x33, 0x2e, 0xa1, 0xee, 0x5f, 0x3b, 0xb0, 0x6c, 0xb5, 0x81, 0x21, 0x4d, 0xe8, - 0x67, 0xb9, 0xcc, 0x30, 0xca, 0xe5, 0x31, 0x21, 0x33, 0x33, 0xd6, 0xb0, 0x33, 0x63, 0x3a, 0x67, - 0x33, 0x67, 0xe6, 0x6c, 0xae, 0x43, 0xab, 0x38, 0x84, 0x6e, 0x5a, 0xa6, 0x0f, 0x5b, 0x54, 0x79, - 0xfe, 0x82, 0x08, 0xf9, 0x0c, 0xe3, 0x30, 0x4e, 0xe5, 0x19, 0xad, 0x28, 0xb8, 0x37, 0xa0, 0x6d, - 0xd0, 0x63, 0x37, 0x22, 0x9e, 0x9f, 0xc4, 0xe9, 0x13, 0x95, 0xa0, 0x93, 0x45, 0x7d, 0x9c, 0xd5, - 0x28, 0x8e, 0xb3, 0xdc, 0xbf, 0x71, 0x60, 0x19, 0x65, 0x30, 0x88, 0xc6, 0x7b, 0x71, 0x18, 0x0c, - 0x67, 0xb4, 0xf6, 0x4a, 0xdc, 0xe4, 0xe1, 0xad, 0x92, 0x45, 0x1b, 0x46, 0xd9, 0x56, 0x11, 0x8d, - 0x54, 0x44, 0x5d, 0x46, 0x4d, 0x45, 0x39, 0x3f, 0xf0, 0x33, 0x29, 0xfc, 0x72, 0x2f, 0xb2, 0x40, - 0xd4, 0x27, 0x04, 0x52, 0x3f, 0xe7, 0x83, 0x49, 0x10, 0x86, 0x81, 0xa0, 0x15, 0x2e, 0x42, 0x5d, - 0x95, 0xfb, 0x83, 0x06, 0xb4, 0xa5, 0xa5, 0xbc, 0x3b, 0x1a, 0x8b, 0x54, 0xb8, 0x74, 0xb4, 0xb4, - 0xb9, 0x30, 0x10, 0x55, 0x6f, 0xb9, 0x66, 0x06, 0x52, 0x5e, 0xd6, 0xb9, 0xea, 0xb2, 0x5e, 0x84, - 0x16, 0x8a, 0xd7, 0x5b, 0xe4, 0x03, 0x8a, 0x3b, 0x0b, 0x05, 0xa0, 0x6a, 0x77, 0xa8, 0x76, 0xbe, - 0xa8, 0x25, 0xc0, 0xf2, 0xfa, 0x16, 0x4a, 0x5e, 0xdf, 0x3b, 0xd0, 0x91, 0x6c, 0x68, 0xde, 0xc9, - 0x3a, 0x14, 0x02, 0x6e, 0xad, 0x89, 0x67, 0x51, 0xaa, 0x2f, 0x77, 0xd4, 0x97, 0x4b, 0x2f, 0xfa, - 0x52, 0x51, 0xd2, 0xc9, 0x90, 0x98, 0x9b, 0xfb, 0xa9, 0x9f, 0x1c, 0xa9, 0xdd, 0x67, 0xa4, 0x8f, - 0xbb, 0x09, 0x66, 0x5b, 0x30, 0x8f, 0x9f, 0x29, 0x6b, 0x5d, 0xaf, 0x74, 0x82, 0x84, 0x5d, 0x85, - 0x79, 0x3e, 0x1a, 0x73, 0x15, 0x79, 0x30, 0x3b, 0x06, 0xc4, 0x35, 0xf2, 0x04, 0x01, 0x9a, 0x00, - 0x44, 0x4b, 0x26, 0xc0, 0xb6, 0xf4, 0x0b, 0x58, 0x7c, 0x7f, 0xe4, 0x6e, 0x00, 0x7b, 0x28, 0xa4, - 0xd6, 0xcc, 0x9c, 0xfe, 0xc1, 0x1c, 0xb4, 0x0d, 0x18, 0xb5, 0x79, 0x8c, 0x1d, 0x1e, 0x8c, 0x02, - 0x7f, 0xc2, 0x73, 0x9e, 0x4a, 0x49, 0x2d, 0xa1, 0x48, 0xe7, 0x1f, 0x8f, 0x07, 0xf1, 0x34, 0x1f, - 0x8c, 0xf8, 0x38, 0xe5, 0x62, 0x8f, 0xc4, 0xcd, 0xc0, 0x42, 0x91, 0x6e, 0xe2, 0x3f, 0x35, 0xe9, - 0x84, 0x3c, 0x94, 0x50, 0x95, 0x07, 0x15, 0x73, 0xd4, 0x2c, 0xf2, 0xa0, 0x62, 0x46, 0xca, 0x76, - 0x68, 0xbe, 0xc6, 0x0e, 0xbd, 0x0d, 0x9b, 0xc2, 0xe2, 0x48, 0xdd, 0x1c, 0x94, 0xc4, 0xe4, 0x94, - 0x5a, 0xb6, 0x05, 0xab, 0xd8, 0x67, 0x25, 0xe0, 0x59, 0xf0, 0x89, 0xc8, 0x4c, 0x38, 0x5e, 0x05, - 0x47, 0x5a, 0x54, 0x47, 0x8b, 0x56, 0x9c, 0x15, 0x55, 0x70, 0xa2, 0xf5, 0x9f, 0xda, 0xb4, 0x2d, - 0x49, 0x5b, 0xc2, 0xdd, 0x65, 0x68, 0xef, 0xe7, 0x71, 0xa2, 0x16, 0xa5, 0x0b, 0x1d, 0x51, 0x94, - 0x27, 0x83, 0x17, 0xe0, 0x3c, 0x49, 0xd1, 0xa3, 0x38, 0x89, 0xc3, 0x78, 0x3c, 0xdb, 0x9f, 0x1e, - 0x64, 0xc3, 0x34, 0x48, 0x30, 0x22, 0x70, 0xff, 0xde, 0x81, 0x75, 0xab, 0x56, 0xa6, 0x32, 0xbe, - 0x20, 0x44, 0x5a, 0x1f, 0xe9, 0x08, 0xc1, 0x5b, 0x33, 0xcc, 0xa1, 0x20, 0x14, 0x49, 0xa4, 0xc7, - 0xf2, 0x94, 0xe7, 0x26, 0xac, 0xa8, 0x9e, 0xa9, 0x0f, 0x85, 0x14, 0xf6, 0xaa, 0x52, 0x28, 0xbf, - 0xef, 0xca, 0x0f, 0x14, 0x8b, 0x5f, 0x15, 0x7e, 0x35, 0x1f, 0xd1, 0x18, 0x55, 0x4c, 0xdb, 0x57, - 0xdf, 0x9b, 0xce, 0xbc, 0xea, 0xc1, 0x50, 0x83, 0x99, 0xfb, 0x47, 0x0e, 0x40, 0xd1, 0x3b, 0x14, - 0x8c, 0xc2, 0xa4, 0x8b, 0xcb, 0x6a, 0x86, 0xf9, 0x7e, 0x0d, 0x3a, 0x3a, 0x9b, 0x5f, 0xec, 0x12, - 0x6d, 0x85, 0xa1, 0xc3, 0x75, 0x05, 0x56, 0xc6, 0x61, 0x7c, 0x40, 0x5b, 0x2c, 0x1d, 0x35, 0x67, - 0xf2, 0x7c, 0xb4, 0x2b, 0xe0, 0x7b, 0x12, 0x2d, 0xb6, 0x94, 0xa6, 0xb1, 0xa5, 0xb8, 0xdf, 0x69, - 0xe8, 0x1c, 0x70, 0x31, 0xe6, 0x53, 0xb5, 0x8c, 0xed, 0x54, 0x8c, 0xe3, 0x29, 0x29, 0x57, 0xca, - 0xde, 0xec, 0xbd, 0x30, 0x90, 0xbd, 0x01, 0xdd, 0x54, 0x58, 0x1f, 0x65, 0x9a, 0x9a, 0xcf, 0x31, - 0x4d, 0xcb, 0xa9, 0xb5, 0xef, 0xfc, 0x02, 0xac, 0xfa, 0xa3, 0x63, 0x9e, 0xe6, 0x01, 0x45, 0x34, - 0xb4, 0xe9, 0x0b, 0x83, 0xba, 0x62, 0xe0, 0xb4, 0x17, 0x5f, 0x81, 0x15, 0x79, 0x26, 0xad, 0x29, - 0xe5, 0x4d, 0xa4, 0x02, 0x46, 0x42, 0xf7, 0xfb, 0x2a, 0xdd, 0x6c, 0xaf, 0xe1, 0xe9, 0x33, 0x62, - 0x8e, 0xae, 0x51, 0x1a, 0xdd, 0xe7, 0x64, 0xea, 0x77, 0xa4, 0xc2, 0x26, 0x99, 0x84, 0x17, 0xa0, - 0x4c, 0xd5, 0xdb, 0x53, 0xda, 0x7c, 0x99, 0x29, 0x75, 0x3f, 0x73, 0x60, 0x71, 0x37, 0x4e, 0x76, - 0xe5, 0xf1, 0x32, 0x29, 0x82, 0xbe, 0xf1, 0xa1, 0x8a, 0xa6, 0x57, 0xdc, 0xa8, 0x78, 0xc5, 0xd5, - 0xbd, 0x76, 0xb9, 0xbc, 0xd7, 0xfe, 0x1a, 0x5c, 0xa0, 0x68, 0x39, 0x8d, 0x93, 0x38, 0x45, 0x65, - 0xf4, 0x43, 0xb1, 0xb1, 0xc6, 0x51, 0x7e, 0xa4, 0xcc, 0xd8, 0xf3, 0x48, 0x28, 0x3a, 0x0a, 0xf3, - 0xe3, 0x81, 0x70, 0x86, 0xa5, 0x6f, 0x20, 0xac, 0x5b, 0xb5, 0xc2, 0xfd, 0x12, 0xb4, 0xc8, 0xb9, - 0xa5, 0x61, 0xbd, 0x09, 0xad, 0xa3, 0x38, 0x19, 0x1c, 0x05, 0x51, 0xae, 0x94, 0xbb, 0x5b, 0x78, - 0x9d, 0xbb, 0x34, 0x21, 0x9a, 0xc0, 0xfd, 0xf1, 0x1c, 0x2c, 0xbe, 0x1f, 0x1d, 0xc7, 0xc1, 0x90, - 0x32, 0xd3, 0x13, 0x3e, 0x89, 0xd5, 0xfd, 0x17, 0xfc, 0x8d, 0x53, 0x41, 0x67, 0xc1, 0x49, 0x2e, - 0x53, 0xcb, 0xaa, 0x88, 0xdb, 0x7d, 0x5a, 0xdc, 0x09, 0x13, 0xaa, 0x63, 0x20, 0xe8, 0xd8, 0xa7, - 0xe6, 0x85, 0x38, 0x59, 0x2a, 0x2e, 0x10, 0xcd, 0x1b, 0x17, 0x88, 0xe8, 0x1c, 0x43, 0x1c, 0x73, - 0x93, 0x7c, 0x2d, 0x79, 0xaa, 0x48, 0x81, 0x48, 0xca, 0x45, 0x96, 0x83, 0x1c, 0x87, 0x45, 0x19, - 0x88, 0x98, 0x20, 0x3a, 0x17, 0xe2, 0x03, 0x41, 0x23, 0x8c, 0xaf, 0x09, 0xa1, 0xb3, 0x55, 0xbe, - 0x53, 0xd7, 0x12, 0x32, 0x5f, 0x82, 0xd1, 0x42, 0x8f, 0xb8, 0x36, 0xa4, 0x62, 0x0c, 0x20, 0xee, - 0xbc, 0x95, 0x71, 0x23, 0x7c, 0x11, 0xc7, 0xf5, 0x2a, 0x7c, 0x41, 0x41, 0xf1, 0xc3, 0xf0, 0xc0, - 0x1f, 0x3e, 0xa1, 0x9b, 0x8e, 0x74, 0x3a, 0xdf, 0xf2, 0x6c, 0x10, 0x7b, 0x6d, 0xac, 0x26, 0x9d, - 0x84, 0x35, 0x3d, 0x13, 0x62, 0x3b, 0xd0, 0xa6, 0x90, 0x4d, 0xae, 0x67, 0x97, 0xd6, 0x73, 0xd5, - 0x8c, 0xe9, 0x68, 0x45, 0x4d, 0x22, 0x33, 0x5b, 0xbe, 0x62, 0x1f, 0xa0, 0x7f, 0x03, 0xd8, 0xcd, - 0xd1, 0x48, 0xae, 0xb7, 0x0e, 0x19, 0x8b, 0x95, 0x72, 0xac, 0x95, 0xaa, 0x99, 0xb1, 0x46, 0xed, - 0x8c, 0xb9, 0x77, 0xa1, 0xbd, 0x67, 0x5c, 0x77, 0x24, 0xd1, 0x50, 0x17, 0x1d, 0xa5, 0x38, 0x19, - 0x88, 0xd1, 0x60, 0xc3, 0x6c, 0xd0, 0xfd, 0x65, 0x60, 0x0f, 0x82, 0x2c, 0xd7, 0xfd, 0x13, 0xcb, - 0xf1, 0x1a, 0x74, 0x74, 0x80, 0x5d, 0x1c, 0xef, 0xb7, 0x25, 0x46, 0xc7, 0xee, 0x37, 0xc5, 0xbd, - 0x80, 0xf2, 0xc0, 0xb6, 0x60, 0x29, 0x10, 0x50, 0x59, 0x13, 0x14, 0xa5, 0xae, 0x47, 0x7f, 0x4d, - 0x82, 0xd6, 0x2e, 0xfa, 0x03, 0x07, 0x16, 0xe5, 0xd0, 0xd0, 0xdb, 0xb0, 0x2e, 0x7a, 0x8a, 0x81, - 0x59, 0x58, 0xfd, 0xf5, 0xb8, 0xaa, 0x0c, 0xcf, 0xd5, 0xc9, 0x30, 0x83, 0x66, 0xe2, 0xe7, 0x47, - 0x14, 0xa0, 0xb4, 0x3c, 0xfa, 0xcd, 0x56, 0x45, 0xd0, 0x2c, 0x74, 0x85, 0x02, 0xe6, 0xba, 0x1b, - 0x99, 0xc2, 0x24, 0x57, 0x70, 0x1c, 0x14, 0x9d, 0xa4, 0x0b, 0x5c, 0x27, 0xc8, 0xe5, 0x2d, 0x85, - 0x02, 0x2e, 0xe6, 0x4b, 0xb2, 0x28, 0xcf, 0x97, 0x24, 0xf5, 0x74, 0xbd, 0xdb, 0x87, 0xde, 0x1d, - 0x1e, 0xf2, 0x9c, 0xdf, 0x0c, 0xc3, 0x32, 0xff, 0x0b, 0x70, 0xbe, 0xa6, 0x4e, 0x3a, 0x2d, 0xf7, - 0x60, 0xed, 0x0e, 0x3f, 0x98, 0x8e, 0x1f, 0xf0, 0xe3, 0xe2, 0x14, 0x8b, 0x41, 0x33, 0x3b, 0x8a, - 0x4f, 0xe4, 0xda, 0xd2, 0x6f, 0xf6, 0x0a, 0x40, 0x88, 0x34, 0x83, 0x2c, 0xe1, 0x43, 0x75, 0x31, - 0x8c, 0x90, 0xfd, 0x84, 0x0f, 0xdd, 0xb7, 0x81, 0x99, 0x7c, 0xe4, 0x10, 0xd0, 0x0e, 0x4c, 0x0f, - 0x06, 0xd9, 0x2c, 0xcb, 0xf9, 0x44, 0xdd, 0x78, 0x33, 0x21, 0xf7, 0x0a, 0x74, 0xf6, 0xfc, 0x99, - 0xc7, 0x3f, 0x96, 0x77, 0x6d, 0x31, 0x36, 0xf6, 0x67, 0x28, 0xca, 0x3a, 0x36, 0xa6, 0x6a, 0xf7, - 0x3f, 0x1b, 0xb0, 0x20, 0x28, 0x91, 0xeb, 0x88, 0x67, 0x79, 0x10, 0x89, 0x13, 0x1c, 0xc9, 0xd5, - 0x80, 0x2a, 0xb2, 0xd1, 0xa8, 0x91, 0x0d, 0xe9, 0xad, 0xaa, 0x4b, 0x36, 0x52, 0x08, 0x2c, 0x0c, - 0xdd, 0x9a, 0xe2, 0x64, 0x5c, 0x04, 0x67, 0x05, 0x50, 0x4a, 0x96, 0x14, 0xd6, 0x46, 0xf4, 0x4f, - 0x09, 0xad, 0x14, 0x07, 0x13, 0xaa, 0xb5, 0x69, 0x8b, 0x42, 0x6a, 0x2a, 0x36, 0xad, 0x62, 0xbb, - 0x96, 0x5e, 0xc2, 0x76, 0x09, 0x17, 0xf6, 0x79, 0xb6, 0x0b, 0x5e, 0xc2, 0x76, 0xb9, 0x0c, 0x56, - 0xef, 0x71, 0xee, 0x71, 0xdc, 0x15, 0x95, 0x38, 0x7d, 0xd7, 0x81, 0x55, 0xb9, 0xa1, 0xeb, 0x3a, - 0xf6, 0x9a, 0xb5, 0xfb, 0x3b, 0x75, 0x07, 0x01, 0xaf, 0xc3, 0x32, 0xed, 0xc9, 0x3a, 0x2b, 0x24, - 0x53, 0x58, 0x16, 0x88, 0xe3, 0x50, 0xa9, 0xed, 0x49, 0x10, 0xca, 0x45, 0x31, 0x21, 0x95, 0x58, - 0xc2, 0xf8, 0x98, 0x96, 0xc4, 0xf1, 0x74, 0xd9, 0xfd, 0x3b, 0x07, 0xd6, 0x8c, 0x0e, 0x4b, 0x29, - 0xbc, 0x01, 0xea, 0xe4, 0x5c, 0x24, 0x8f, 0x84, 0x32, 0x9d, 0xb3, 0x9d, 0x93, 0xe2, 0x33, 0x8b, - 0x98, 0x16, 0xd3, 0x9f, 0x51, 0x07, 0xb3, 0xe9, 0x44, 0x7a, 0x20, 0x26, 0x84, 0x82, 0x74, 0xc2, - 0xf9, 0x13, 0x4d, 0x32, 0x47, 0x24, 0x16, 0x46, 0x07, 0xa3, 0xe8, 0x4b, 0x68, 0x22, 0x71, 0x17, - 0xc8, 0x06, 0xdd, 0x7f, 0x74, 0x60, 0x5d, 0x38, 0x85, 0xd2, 0xe5, 0xd6, 0xf7, 0x14, 0x17, 0x84, - 0x17, 0x2c, 0x34, 0x72, 0xf7, 0x8c, 0x27, 0xcb, 0xec, 0x8b, 0x2f, 0xe9, 0xc8, 0xea, 0x03, 0xf1, - 0x53, 0xd6, 0x62, 0xae, 0x6e, 0x2d, 0x9e, 0x33, 0xd3, 0x75, 0xc9, 0x92, 0xf9, 0xda, 0x64, 0xc9, - 0xad, 0x45, 0x98, 0xcf, 0x86, 0x71, 0xc2, 0xdd, 0x4d, 0xd8, 0xb0, 0x07, 0x27, 0x4d, 0xd0, 0xf7, - 0x1c, 0xe8, 0xdd, 0x13, 0xa9, 0xc3, 0x20, 0x1a, 0xef, 0x06, 0x59, 0x1e, 0xa7, 0xfa, 0x62, 0xf6, - 0x25, 0x80, 0x2c, 0xf7, 0xd3, 0x5c, 0x5c, 0x58, 0x92, 0x69, 0x8e, 0x02, 0xc1, 0x3e, 0xf2, 0x68, - 0x24, 0x6a, 0xc5, 0xda, 0xe8, 0x32, 0x2e, 0x0c, 0x1d, 0xd6, 0x0f, 0xe2, 0xc3, 0xc3, 0x8c, 0x6b, - 0xb7, 0xd5, 0xc4, 0x30, 0xf2, 0x45, 0x8d, 0xc7, 0x58, 0x8f, 0x1f, 0x93, 0xa9, 0x15, 0xfe, 0x60, - 0x09, 0x75, 0xff, 0xd6, 0x81, 0x95, 0xa2, 0x93, 0x77, 0x11, 0xb4, 0xad, 0x83, 0xe8, 0x9a, 0x61, - 0x1d, 0x54, 0x02, 0x26, 0x18, 0x0d, 0x82, 0x48, 0xf6, 0xcd, 0x40, 0x48, 0x63, 0x65, 0x29, 0x9e, - 0xaa, 0xcb, 0x61, 0x26, 0x24, 0x4e, 0x7e, 0x73, 0xfc, 0x5a, 0xdc, 0x0c, 0x93, 0x25, 0xba, 0x6f, - 0x36, 0xc9, 0xe9, 0xab, 0x05, 0xe1, 0x10, 0xcb, 0xa2, 0xda, 0x9f, 0x16, 0x09, 0xc5, 0x9f, 0xee, - 0x1f, 0x3b, 0x70, 0xbe, 0x66, 0x72, 0xa5, 0x66, 0xdc, 0x81, 0xb5, 0x43, 0x5d, 0xa9, 0x26, 0x40, - 0xa8, 0xc7, 0xa6, 0x94, 0xa2, 0xd2, 0xa0, 0xbd, 0xea, 0x07, 0xe8, 0x1e, 0x53, 0xde, 0x48, 0x4c, - 0xa9, 0x75, 0x69, 0xa2, 0x5a, 0xb1, 0xf3, 0xfd, 0x06, 0x74, 0xc5, 0x51, 0x85, 0x78, 0x9a, 0xc3, - 0x53, 0xf6, 0x01, 0x2c, 0xca, 0x87, 0x50, 0xec, 0xac, 0x6c, 0xd6, 0x7e, 0x7a, 0xd5, 0xdf, 0x2c, - 0xc3, 0x52, 0x76, 0xd6, 0x7f, 0xff, 0xb3, 0x7f, 0xf9, 0x93, 0xc6, 0x32, 0x6b, 0x6f, 0x1f, 0xbf, - 0xb5, 0x3d, 0xe6, 0x51, 0x86, 0x3c, 0x7e, 0x13, 0xa0, 0x78, 0x4b, 0xc4, 0x7a, 0xda, 0xc9, 0x28, - 0xbd, 0x7d, 0xea, 0x9f, 0xaf, 0xa9, 0x91, 0x7c, 0xcf, 0x13, 0xdf, 0x75, 0xb7, 0x8b, 0x7c, 0x83, - 0x28, 0xc8, 0xc5, 0xc3, 0xa2, 0x77, 0x9d, 0x2d, 0x36, 0x82, 0x8e, 0xf9, 0xa6, 0x88, 0xa9, 0x90, - 0xb9, 0xe6, 0xa1, 0x52, 0xff, 0x42, 0x6d, 0x9d, 0xca, 0x17, 0x50, 0x1b, 0x67, 0xdd, 0x55, 0x6c, - 0x63, 0x4a, 0x14, 0xba, 0x95, 0x9d, 0x7f, 0xba, 0x00, 0x2d, 0x9d, 0x76, 0x62, 0xdf, 0x86, 0x65, - 0xeb, 0x74, 0x87, 0x29, 0xc6, 0x75, 0x87, 0x41, 0xfd, 0x8b, 0xf5, 0x95, 0xb2, 0xd9, 0x4b, 0xd4, - 0x6c, 0x8f, 0x6d, 0x62, 0xb3, 0xf2, 0x78, 0x64, 0x9b, 0xce, 0xb4, 0xc4, 0xfd, 0xb2, 0x27, 0xd0, - 0xb5, 0x4f, 0x64, 0xd8, 0x45, 0xdb, 0xa0, 0x94, 0x5a, 0x7b, 0xe5, 0x94, 0x5a, 0xd9, 0xdc, 0x45, - 0x6a, 0x6e, 0x93, 0x6d, 0x98, 0xcd, 0xe9, 0x74, 0x10, 0xa7, 0x1b, 0x81, 0xe6, 0x63, 0x23, 0xf6, - 0x8a, 0x5e, 0xea, 0xba, 0x47, 0x48, 0x7a, 0xd1, 0xaa, 0x2f, 0x91, 0xdc, 0x1e, 0x35, 0xc5, 0x18, - 0x4d, 0xa8, 0xf9, 0xd6, 0x88, 0x7d, 0x04, 0x2d, 0xfd, 0xc0, 0x80, 0x9d, 0x33, 0x5e, 0x75, 0x98, - 0xaf, 0x1e, 0xfa, 0xbd, 0x6a, 0x45, 0xdd, 0x52, 0x99, 0x9c, 0x51, 0x20, 0x1e, 0xc0, 0x59, 0xe9, - 0xa4, 0x1e, 0xf0, 0x9f, 0x64, 0x24, 0x35, 0x4f, 0xa4, 0xae, 0x3b, 0xec, 0x06, 0x2c, 0xa9, 0x77, - 0x1b, 0x6c, 0xb3, 0xfe, 0xfd, 0x49, 0xff, 0x5c, 0x05, 0x97, 0xfa, 0x7c, 0x13, 0xa0, 0x78, 0x73, - 0xa0, 0x25, 0xbf, 0xf2, 0x12, 0x42, 0x4f, 0x62, 0xcd, 0x03, 0x85, 0x31, 0xbd, 0xb0, 0xb0, 0x9f, - 0x34, 0xb0, 0x57, 0x0b, 0xfa, 0xda, 0xc7, 0x0e, 0xcf, 0x61, 0xe8, 0x6e, 0xd2, 0xdc, 0xad, 0x32, - 0x52, 0xa5, 0x88, 0x9f, 0xa8, 0xbb, 0xb1, 0x77, 0xa0, 0x6d, 0xbc, 0x63, 0x60, 0x8a, 0x43, 0xf5, - 0x0d, 0x44, 0xbf, 0x5f, 0x57, 0x25, 0xbb, 0xfb, 0x15, 0x58, 0xb6, 0x1e, 0x24, 0x68, 0xcd, 0xa8, - 0x7b, 0xee, 0xa0, 0x35, 0xa3, 0xfe, 0x0d, 0xc3, 0x37, 0xa1, 0x6d, 0x3c, 0x1f, 0x60, 0xc6, 0x9d, - 0xa0, 0xd2, 0xc3, 0x01, 0xdd, 0xa3, 0xba, 0xd7, 0x06, 0x1b, 0x34, 0xde, 0xae, 0xdb, 0xc2, 0xf1, - 0xd2, 0x05, 0x51, 0x14, 0x92, 0x6f, 0x43, 0xd7, 0x7e, 0x50, 0xa0, 0xb5, 0xaa, 0xf6, 0x69, 0x82, - 0xd6, 0xaa, 0x53, 0x5e, 0x21, 0x48, 0x81, 0xdc, 0x5a, 0xd7, 0x8d, 0x6c, 0x7f, 0x2a, 0x0f, 0x5d, - 0x9e, 0xb1, 0xaf, 0xa3, 0xe9, 0x90, 0x37, 0x76, 0x59, 0xf1, 0x8c, 0xc2, 0xbe, 0xd7, 0xab, 0xa5, - 0xbd, 0x72, 0xb9, 0xd7, 0x5d, 0x23, 0xe6, 0x6d, 0x56, 0x8c, 0x40, 0x58, 0x68, 0xba, 0xb9, 0x6b, - 0x58, 0x68, 0xf3, 0x72, 0xaf, 0x61, 0xa1, 0xad, 0x0b, 0xbe, 0x65, 0x0b, 0x9d, 0x07, 0xc8, 0x23, - 0x82, 0x95, 0xd2, 0x3d, 0x00, 0xad, 0x2c, 0xf5, 0xb7, 0x88, 0xfa, 0x97, 0x9e, 0x7f, 0x7d, 0xc0, - 0x36, 0x33, 0xca, 0xbc, 0x6c, 0xab, 0x4b, 0x5f, 0xbf, 0x05, 0x1d, 0xf3, 0x22, 0xb8, 0xb6, 0xd9, - 0x35, 0xd7, 0xd7, 0xb5, 0xcd, 0xae, 0xbb, 0x39, 0xae, 0x16, 0x97, 0x75, 0xcc, 0x66, 0xd8, 0x37, - 0x61, 0xc5, 0xb8, 0x71, 0xb2, 0x3f, 0x8b, 0x86, 0x5a, 0x78, 0xaa, 0x77, 0x04, 0xfb, 0x75, 0xfe, - 0x99, 0x7b, 0x8e, 0x18, 0xaf, 0xb9, 0x16, 0x63, 0x14, 0x9c, 0xdb, 0xd0, 0x36, 0x6f, 0xb3, 0x3c, - 0x87, 0xef, 0x39, 0xa3, 0xca, 0xbc, 0x2e, 0x77, 0xdd, 0x61, 0x7f, 0xe6, 0x40, 0xc7, 0xbc, 0x7d, - 0xca, 0xac, 0x3c, 0x6f, 0x89, 0x4f, 0xcf, 0xac, 0x33, 0x19, 0xb9, 0x1e, 0x75, 0xf2, 0xc1, 0xd6, - 0x57, 0xac, 0x49, 0xfe, 0xd4, 0xf2, 0xf3, 0xaf, 0x95, 0xdf, 0xf8, 0x3d, 0x2b, 0x13, 0x98, 0xf7, - 0x28, 0x9f, 0x5d, 0x77, 0xd8, 0xbb, 0xe2, 0x1d, 0xa8, 0x8a, 0xeb, 0x99, 0x61, 0xdc, 0xca, 0x53, - 0x66, 0x3e, 0x99, 0xbc, 0xea, 0x5c, 0x77, 0xd8, 0xb7, 0xc4, 0x53, 0x3e, 0xf9, 0x2d, 0xcd, 0xfc, - 0xcb, 0x7e, 0xef, 0xbe, 0x4e, 0xa3, 0xb9, 0xe4, 0x9e, 0xb7, 0x46, 0x53, 0xb6, 0xee, 0x7b, 0x00, - 0x45, 0x92, 0x86, 0x95, 0x32, 0x16, 0xda, 0xee, 0x55, 0xf3, 0x38, 0xf6, 0x8a, 0xaa, 0xc4, 0x06, - 0x72, 0xfc, 0x48, 0x08, 0xa3, 0xa4, 0xcf, 0xf4, 0x92, 0x56, 0x93, 0x2d, 0xfd, 0x7e, 0x5d, 0x55, - 0x9d, 0x28, 0x2a, 0xfe, 0xec, 0x31, 0x2c, 0x3f, 0x88, 0xe3, 0x27, 0xd3, 0x44, 0xa7, 0x11, 0xed, - 0x9c, 0xc1, 0xae, 0x9f, 0x1d, 0xf5, 0x4b, 0xa3, 0x70, 0x2f, 0x13, 0xab, 0x3e, 0xeb, 0x19, 0xac, - 0xb6, 0x3f, 0x2d, 0x52, 0x44, 0xcf, 0x98, 0x0f, 0x6b, 0x7a, 0x8f, 0xd3, 0x1d, 0xef, 0xdb, 0x6c, - 0xcc, 0x4c, 0x4d, 0xa5, 0x09, 0xcb, 0xeb, 0x50, 0xbd, 0xdd, 0xce, 0x14, 0xcf, 0xeb, 0x0e, 0xdb, - 0x83, 0xce, 0x1d, 0x3e, 0x8c, 0x47, 0x5c, 0x46, 0xf9, 0xeb, 0x45, 0xc7, 0x75, 0x7a, 0xa0, 0xbf, - 0x6c, 0x81, 0xb6, 0xd6, 0x27, 0xfe, 0x2c, 0xe5, 0x1f, 0x6f, 0x7f, 0x2a, 0xf3, 0x07, 0xcf, 0x94, - 0xd6, 0xab, 0x9c, 0x87, 0xa5, 0xf5, 0xa5, 0x24, 0x89, 0xa5, 0xf5, 0x95, 0x24, 0x89, 0x35, 0xd5, - 0x2a, 0xe7, 0xc2, 0x42, 0x58, 0xab, 0xe4, 0x55, 0xf4, 0x4e, 0x79, 0x5a, 0x36, 0xa6, 0x7f, 0xf9, - 0x74, 0x02, 0xbb, 0xb5, 0x2d, 0xbb, 0xb5, 0x7d, 0x58, 0xbe, 0xc3, 0xc5, 0x64, 0x89, 0xb3, 0xca, - 0xbe, 0x6d, 0x46, 0xcc, 0x73, 0xcd, 0xb2, 0x89, 0xa1, 0x3a, 0xdb, 0xac, 0xd3, 0x41, 0x21, 0xfb, - 0x08, 0xda, 0xf7, 0x79, 0xae, 0x0e, 0x27, 0xb5, 0xbf, 0x51, 0x3a, 0xad, 0xec, 0xd7, 0x9c, 0x6d, - 0xda, 0x32, 0x43, 0xdc, 0xb6, 0xf9, 0x68, 0xcc, 0x85, 0xb2, 0x0f, 0x82, 0xd1, 0x33, 0xf6, 0xeb, - 0xc4, 0x5c, 0xdf, 0x67, 0xd8, 0x34, 0xce, 0xb4, 0x4c, 0xe6, 0x2b, 0x25, 0xbc, 0x8e, 0x73, 0x14, - 0x8f, 0xb8, 0xb1, 0xc1, 0x45, 0xd0, 0x36, 0x2e, 0xdb, 0x68, 0x05, 0xaa, 0x5e, 0xf0, 0xd1, 0x0a, - 0x54, 0x73, 0x37, 0xc7, 0xbd, 0x4a, 0xed, 0xb8, 0xec, 0x72, 0xd1, 0x8e, 0xb8, 0x8f, 0x53, 0xb4, - 0xb4, 0xfd, 0xa9, 0x3f, 0xc9, 0x9f, 0xb1, 0x0f, 0xe9, 0x29, 0x8b, 0x79, 0x00, 0x5b, 0xf8, 0x3b, - 0xe5, 0xb3, 0x5a, 0x3d, 0x59, 0x46, 0x95, 0xed, 0x03, 0x89, 0xa6, 0x68, 0x1f, 0xfc, 0x22, 0xc0, - 0x7e, 0x1e, 0x27, 0x77, 0x7c, 0x3e, 0x89, 0xa3, 0xc2, 0x72, 0x15, 0x87, 0x8c, 0x85, 0xe5, 0x32, - 0x4e, 0x1a, 0xd9, 0x87, 0x86, 0xc7, 0x69, 0x9d, 0x5f, 0x2b, 0xe1, 0x3a, 0xf5, 0x1c, 0x52, 0x4f, - 0x48, 0xcd, 0x59, 0xe4, 0x75, 0x07, 0xfd, 0xc7, 0x22, 0x8b, 0xa7, 0xfd, 0xc7, 0x4a, 0x82, 0x50, - 0x9b, 0xbd, 0x9a, 0x94, 0xdf, 0x1e, 0xb4, 0x8a, 0xb4, 0x90, 0xda, 0x92, 0xca, 0x49, 0x24, 0xbd, - 0xc7, 0x54, 0x92, 0x35, 0xee, 0x2a, 0x4d, 0x15, 0xb0, 0x25, 0x9c, 0x2a, 0xca, 0xc0, 0x04, 0xb0, - 0x2e, 0x3a, 0xa8, 0x37, 0x4c, 0x3a, 0x36, 0x53, 0x23, 0xa9, 0x49, 0x98, 0x68, 0x6d, 0xae, 0xcd, - 0x37, 0x58, 0xb1, 0x1d, 0x4a, 0xab, 0x38, 0xb2, 0x43, 0xd3, 0x3c, 0x81, 0xb5, 0x4a, 0xb0, 0xac, - 0x55, 0xfa, 0xb4, 0x1c, 0x85, 0x56, 0xe9, 0x53, 0xe3, 0x6c, 0xf7, 0x2c, 0x35, 0xb9, 0xe2, 0x02, - 0x36, 0x99, 0x9d, 0x04, 0xf9, 0xf0, 0xe8, 0x5d, 0x67, 0xeb, 0x60, 0x81, 0xfe, 0x69, 0xe4, 0xf3, - 0xff, 0x1b, 0x00, 0x00, 0xff, 0xff, 0x0b, 0xc7, 0xfe, 0x7f, 0x9b, 0x44, 0x00, 0x00, + // 5611 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x7c, 0xcb, 0x73, 0x1c, 0xc9, + 0x71, 0x37, 0x7b, 0x30, 0x78, 0x4c, 0xce, 0x60, 0x00, 0x14, 0x40, 0x70, 0x38, 0xe4, 0x72, 0xb9, + 0xad, 0x0d, 0x91, 0x1f, 0xbe, 0x35, 0xc1, 0x85, 0xa4, 0xf5, 0x6a, 0x69, 0x4b, 0xe6, 0x1b, 0x2b, + 0x71, 0x29, 0xa8, 0x41, 0x8a, 0xb6, 0x64, 0x7b, 0xd4, 0x98, 0x29, 0x0c, 0x7a, 0xd9, 0xd3, 0xdd, + 0xdb, 0xdd, 0x03, 0x70, 0x76, 0xcd, 0x08, 0xbf, 0xc2, 0x27, 0x2b, 0x7c, 0xb0, 0x2f, 0xb2, 0xc3, + 0xe1, 0x08, 0xe9, 0x62, 0x1f, 0x7c, 0xf4, 0x49, 0xf6, 0x3f, 0xe0, 0x08, 0x87, 0x0f, 0x7b, 0x52, + 0xf8, 0xe6, 0xc7, 0xc1, 0x56, 0xf8, 0xe2, 0x08, 0x5f, 0x7c, 0x70, 0x38, 0x32, 0xeb, 0xd1, 0x55, + 0xdd, 0x0d, 0x92, 0x7a, 0xd8, 0xb7, 0xa9, 0x5f, 0x65, 0x67, 0xbd, 0x32, 0xb3, 0x32, 0xb3, 0xaa, + 0x06, 0x5a, 0x69, 0x32, 0xbc, 0x96, 0xa4, 0x71, 0x1e, 0xb3, 0xf9, 0x30, 0x4a, 0x93, 0x61, 0xff, + 0xe2, 0x38, 0x8e, 0xc7, 0x21, 0xdf, 0xf6, 0x93, 0x60, 0xdb, 0x8f, 0xa2, 0x38, 0xf7, 0xf3, 0x20, + 0x8e, 0x32, 0x41, 0xe4, 0x7e, 0x1b, 0xba, 0xf7, 0x79, 0xb4, 0xcf, 0xf9, 0xc8, 0xe3, 0x1f, 0x4d, + 0x79, 0x96, 0xb3, 0xff, 0x0f, 0x6b, 0x3e, 0xff, 0x98, 0xf3, 0xd1, 0x20, 0xf1, 0xb3, 0x2c, 0x39, + 0x4a, 0xfd, 0x8c, 0xf7, 0x9c, 0xcb, 0xce, 0xd5, 0x8e, 0xb7, 0x2a, 0x2a, 0xf6, 0x34, 0xce, 0xde, + 0x80, 0x4e, 0x86, 0xa4, 0x3c, 0xca, 0xd3, 0x38, 0x99, 0xf5, 0x1a, 0x44, 0xd7, 0x46, 0xec, 0xae, + 0x80, 0xdc, 0x10, 0x56, 0x74, 0x0b, 0x59, 0x12, 0x47, 0x19, 0x67, 0xd7, 0x61, 0x63, 0x18, 0x24, + 0x47, 0x3c, 0x1d, 0xd0, 0xc7, 0x93, 0x88, 0x4f, 0xe2, 0x28, 0x18, 0xf6, 0x9c, 0xcb, 0x73, 0x57, + 0x5b, 0x1e, 0x13, 0x75, 0xf8, 0xc5, 0x07, 0xb2, 0x86, 0x5d, 0x81, 0x15, 0x1e, 0x09, 0x9c, 0x8f, + 0xe8, 0x2b, 0xd9, 0x54, 0xb7, 0x80, 0xf1, 0x03, 0xf7, 0x4f, 0x1c, 0x58, 0x7b, 0x3f, 0x0a, 0xf2, + 0x27, 0x7e, 0x18, 0xf2, 0x5c, 0x8d, 0xe9, 0x0a, 0xac, 0x9c, 0x10, 0x40, 0x63, 0x3a, 0x89, 0xd3, + 0x91, 0x1c, 0x51, 0x57, 0xc0, 0x7b, 0x12, 0x3d, 0xb5, 0x67, 0x8d, 0x53, 0x7b, 0x56, 0x3b, 0x5d, + 0x73, 0xf5, 0xd3, 0xe5, 0x6e, 0x00, 0x33, 0x3b, 0x27, 0xa6, 0xc3, 0xfd, 0x12, 0xac, 0x3f, 0x8e, + 0xc2, 0x78, 0xf8, 0xf4, 0x27, 0xeb, 0xb4, 0xbb, 0x09, 0x1b, 0xf6, 0xf7, 0x92, 0xef, 0x77, 0x1b, + 0xd0, 0x7e, 0x94, 0xfa, 0x51, 0xe6, 0x0f, 0x71, 0xc9, 0x59, 0x0f, 0x16, 0xf3, 0x67, 0x83, 0x23, + 0x3f, 0x3b, 0x22, 0x46, 0x2d, 0x4f, 0x15, 0xd9, 0x26, 0x2c, 0xf8, 0x93, 0x78, 0x1a, 0xe5, 0x34, + 0xab, 0x73, 0x9e, 0x2c, 0xb1, 0xb7, 0x60, 0x2d, 0x9a, 0x4e, 0x06, 0xc3, 0x38, 0x3a, 0x0c, 0xd2, + 0x89, 0x10, 0x1c, 0x1a, 0xdc, 0xbc, 0x57, 0xad, 0x60, 0x97, 0x00, 0x0e, 0xb0, 0x1b, 0xa2, 0x89, + 0x26, 0x35, 0x61, 0x20, 0xcc, 0x85, 0x8e, 0x2c, 0xf1, 0x60, 0x7c, 0x94, 0xf7, 0xe6, 0x89, 0x91, + 0x85, 0x21, 0x8f, 0x3c, 0x98, 0xf0, 0x41, 0x96, 0xfb, 0x93, 0xa4, 0xb7, 0x40, 0xbd, 0x31, 0x10, + 0xaa, 0x8f, 0x73, 0x3f, 0x1c, 0x1c, 0x72, 0x9e, 0xf5, 0x16, 0x65, 0xbd, 0x46, 0xd8, 0x67, 0xa1, + 0x3b, 0xe2, 0x59, 0x3e, 0xf0, 0x47, 0xa3, 0x94, 0x67, 0x19, 0xcf, 0x7a, 0x4b, 0xb4, 0x74, 0x25, + 0xd4, 0xed, 0xc1, 0xe6, 0x7d, 0x9e, 0x1b, 0xb3, 0x93, 0xc9, 0x69, 0x77, 0x1f, 0x00, 0x33, 0xe0, + 0x3b, 0x3c, 0xf7, 0x83, 0x30, 0x63, 0xef, 0x40, 0x27, 0x37, 0x88, 0x49, 0x54, 0xdb, 0x3b, 0xec, + 0x1a, 0xe9, 0xd8, 0x35, 0xe3, 0x03, 0xcf, 0xa2, 0x73, 0xff, 0xcb, 0x81, 0xf6, 0x3e, 0x8f, 0xb4, + 0x76, 0x31, 0x68, 0x62, 0x4f, 0xe4, 0x4a, 0xd2, 0x6f, 0xf6, 0x3a, 0xb4, 0xa9, 0x77, 0x59, 0x9e, + 0x06, 0xd1, 0x98, 0x96, 0xa0, 0xe5, 0x01, 0x42, 0xfb, 0x84, 0xb0, 0x55, 0x98, 0xf3, 0x27, 0x39, + 0x4d, 0xfc, 0x9c, 0x87, 0x3f, 0x51, 0xef, 0x12, 0x7f, 0x36, 0xe1, 0x51, 0x5e, 0x4c, 0x76, 0xc7, + 0x6b, 0x4b, 0x6c, 0x17, 0x67, 0xfb, 0x1a, 0xac, 0x9b, 0x24, 0x8a, 0xfb, 0x3c, 0x71, 0x5f, 0x33, + 0x28, 0x65, 0x23, 0x57, 0x60, 0x45, 0xd1, 0xa7, 0xa2, 0xb3, 0x34, 0xfd, 0x2d, 0xaf, 0x2b, 0x61, + 0x35, 0x84, 0xab, 0xb0, 0x7a, 0x18, 0x44, 0x7e, 0x38, 0x18, 0x86, 0xf9, 0xf1, 0x60, 0xc4, 0xc3, + 0xdc, 0xa7, 0x85, 0x98, 0xf7, 0xba, 0x84, 0xdf, 0x0e, 0xf3, 0xe3, 0x3b, 0x88, 0xba, 0x7f, 0xe4, + 0x40, 0x47, 0x0c, 0x5e, 0x2a, 0xfe, 0x9b, 0xb0, 0xac, 0xda, 0xe0, 0x69, 0x1a, 0xa7, 0x52, 0x0e, + 0x6d, 0x90, 0x6d, 0xc1, 0xaa, 0x02, 0x92, 0x94, 0x07, 0x13, 0x7f, 0xcc, 0xa5, 0xb6, 0x57, 0x70, + 0xb6, 0x53, 0x70, 0x4c, 0xe3, 0x69, 0x2e, 0x54, 0xaf, 0xbd, 0xd3, 0x91, 0x0b, 0xe3, 0x21, 0xe6, + 0xd9, 0x24, 0xee, 0xf7, 0x1c, 0xe8, 0xdc, 0x3e, 0xf2, 0xa3, 0x88, 0x87, 0x7b, 0x71, 0x10, 0xe5, + 0xec, 0x3a, 0xb0, 0xc3, 0x69, 0x34, 0x0a, 0xa2, 0xf1, 0x20, 0x7f, 0x16, 0x8c, 0x06, 0x07, 0xb3, + 0x9c, 0x67, 0x62, 0x89, 0x76, 0xcf, 0x78, 0x35, 0x75, 0xec, 0x2d, 0x58, 0xb5, 0xd0, 0x2c, 0x4f, + 0xc5, 0xba, 0xed, 0x9e, 0xf1, 0x2a, 0x35, 0x28, 0xf8, 0xf1, 0x34, 0x4f, 0xa6, 0xf9, 0x20, 0x88, + 0x46, 0xfc, 0x19, 0xf5, 0x71, 0xd9, 0xb3, 0xb0, 0x5b, 0x5d, 0xe8, 0x98, 0xdf, 0xb9, 0x5f, 0x82, + 0xd5, 0x07, 0xa8, 0x11, 0x51, 0x10, 0x8d, 0x6f, 0x0a, 0xb1, 0x45, 0x35, 0x4d, 0xa6, 0x07, 0x4f, + 0xf9, 0x4c, 0xce, 0x9b, 0x2c, 0xa1, 0x50, 0x1d, 0xc5, 0x59, 0x2e, 0x25, 0x87, 0x7e, 0xbb, 0xff, + 0xe4, 0xc0, 0x0a, 0xce, 0xfd, 0x07, 0x7e, 0x34, 0x53, 0x2b, 0xf7, 0x00, 0x3a, 0xc8, 0xea, 0x51, + 0x7c, 0x53, 0x28, 0xbb, 0x10, 0xe2, 0xab, 0x72, 0xae, 0x4a, 0xd4, 0xd7, 0x4c, 0x52, 0x34, 0xe6, + 0x33, 0xcf, 0xfa, 0x1a, 0xc5, 0x36, 0xf7, 0xd3, 0x31, 0xcf, 0xc9, 0x0c, 0x48, 0xb3, 0x00, 0x02, + 0xba, 0x1d, 0x47, 0x87, 0xec, 0x32, 0x74, 0x32, 0x3f, 0x1f, 0x24, 0x3c, 0xa5, 0x59, 0x23, 0xd1, + 0x9b, 0xf3, 0x20, 0xf3, 0xf3, 0x3d, 0x9e, 0xde, 0x9a, 0xe5, 0xbc, 0xff, 0x65, 0x58, 0xab, 0xb4, + 0x82, 0xd2, 0x5e, 0x0c, 0x11, 0x7f, 0xb2, 0x0d, 0x98, 0x3f, 0xf6, 0xc3, 0x29, 0x97, 0xd6, 0x49, + 0x14, 0xde, 0x6b, 0xbc, 0xeb, 0xb8, 0x9f, 0x85, 0xd5, 0xa2, 0xdb, 0x52, 0xc8, 0x18, 0x34, 0x71, + 0x06, 0x25, 0x03, 0xfa, 0xed, 0xfe, 0x96, 0x23, 0x08, 0x6f, 0xc7, 0x81, 0xd6, 0x74, 0x24, 0x44, + 0x83, 0xa0, 0x08, 0xf1, 0xf7, 0xa9, 0x96, 0xf0, 0xa7, 0x1f, 0xac, 0x7b, 0x05, 0xd6, 0x8c, 0x2e, + 0xbc, 0xa0, 0xb3, 0xdf, 0x71, 0x60, 0xed, 0x21, 0x3f, 0x91, 0xab, 0xae, 0x7a, 0xfb, 0x2e, 0x34, + 0xf3, 0x59, 0x22, 0xb6, 0xe2, 0xee, 0xce, 0x9b, 0x72, 0xd1, 0x2a, 0x74, 0xd7, 0x64, 0xf1, 0xd1, + 0x2c, 0xe1, 0x1e, 0x7d, 0xe1, 0x7e, 0x09, 0xda, 0x06, 0xc8, 0xce, 0xc1, 0xfa, 0x93, 0xf7, 0x1f, + 0x3d, 0xbc, 0xbb, 0xbf, 0x3f, 0xd8, 0x7b, 0x7c, 0xeb, 0xab, 0x77, 0x7f, 0x65, 0xb0, 0x7b, 0x73, + 0x7f, 0x77, 0xf5, 0x0c, 0xdb, 0x04, 0xf6, 0xf0, 0xee, 0xfe, 0xa3, 0xbb, 0x77, 0x2c, 0xdc, 0x71, + 0xfb, 0xd0, 0x7b, 0xc8, 0x4f, 0x9e, 0x04, 0x79, 0xc4, 0xb3, 0xcc, 0x6e, 0xcd, 0xbd, 0x06, 0xcc, + 0xec, 0x82, 0x1c, 0x55, 0x0f, 0x16, 0xa5, 0xa9, 0x55, 0x3b, 0x8d, 0x2c, 0xba, 0x9f, 0x05, 0xb6, + 0x1f, 0x8c, 0xa3, 0x0f, 0x78, 0x96, 0xf9, 0x63, 0xae, 0xc6, 0xb6, 0x0a, 0x73, 0x93, 0x6c, 0x2c, + 0x8d, 0x22, 0xfe, 0x74, 0x3f, 0x07, 0xeb, 0x16, 0x9d, 0x64, 0x7c, 0x11, 0x5a, 0x59, 0x30, 0x8e, + 0xfc, 0x7c, 0x9a, 0x72, 0xc9, 0xba, 0x00, 0xdc, 0x7b, 0xb0, 0xf1, 0x0d, 0x9e, 0x06, 0x87, 0xb3, + 0x97, 0xb1, 0xb7, 0xf9, 0x34, 0xca, 0x7c, 0xee, 0xc2, 0xd9, 0x12, 0x1f, 0xd9, 0xbc, 0x10, 0x44, + 0xb9, 0x5c, 0x4b, 0x9e, 0x28, 0x18, 0x6a, 0xd9, 0x30, 0xd5, 0xd2, 0x7d, 0x0c, 0xec, 0x76, 0x1c, + 0x45, 0x7c, 0x98, 0xef, 0x71, 0x9e, 0x16, 0xfe, 0x55, 0x21, 0x75, 0xed, 0x9d, 0x73, 0x72, 0x1d, + 0xcb, 0xba, 0x2e, 0xc5, 0x91, 0x41, 0x33, 0xe1, 0xe9, 0x84, 0x18, 0x2f, 0x79, 0xf4, 0xdb, 0x3d, + 0x0b, 0xeb, 0x16, 0x5b, 0xb9, 0xdb, 0xbf, 0x0d, 0x67, 0xef, 0x04, 0xd9, 0xb0, 0xda, 0x60, 0x0f, + 0x16, 0x93, 0xe9, 0xc1, 0xa0, 0xd0, 0x29, 0x55, 0xc4, 0x4d, 0xb0, 0xfc, 0x89, 0x64, 0xf6, 0x7b, + 0x0e, 0x34, 0x77, 0x1f, 0x3d, 0xb8, 0xcd, 0xfa, 0xb0, 0x14, 0x44, 0xc3, 0x78, 0x82, 0x5b, 0x87, + 0x18, 0xb4, 0x2e, 0x9f, 0xaa, 0x2b, 0x17, 0xa1, 0x45, 0x3b, 0x0e, 0xee, 0xeb, 0xd2, 0x15, 0x2a, + 0x00, 0xf4, 0x29, 0xf8, 0xb3, 0x24, 0x48, 0xc9, 0x69, 0x50, 0xae, 0x40, 0x93, 0x2c, 0x62, 0xb5, + 0xc2, 0xfd, 0xef, 0x26, 0x2c, 0x4a, 0x5b, 0x4d, 0xed, 0x0d, 0xf3, 0xe0, 0x98, 0xcb, 0x9e, 0xc8, + 0x12, 0xee, 0x2a, 0x29, 0x9f, 0xc4, 0x39, 0x1f, 0x58, 0xcb, 0x60, 0x83, 0x48, 0x35, 0x14, 0x8c, + 0x06, 0x09, 0x5a, 0x7d, 0xea, 0x59, 0xcb, 0xb3, 0x41, 0x9c, 0x2c, 0x04, 0x06, 0xc1, 0x88, 0xfa, + 0xd4, 0xf4, 0x54, 0x11, 0x67, 0x62, 0xe8, 0x27, 0xfe, 0x30, 0xc8, 0x67, 0x52, 0xb9, 0x75, 0x19, + 0x79, 0x87, 0xf1, 0xd0, 0x0f, 0x07, 0x07, 0x7e, 0xe8, 0x47, 0x43, 0x2e, 0x1d, 0x17, 0x1b, 0x44, + 0xdf, 0x44, 0x76, 0x49, 0x91, 0x09, 0xff, 0xa5, 0x84, 0xa2, 0x8f, 0x33, 0x8c, 0x27, 0x93, 0x20, + 0x47, 0x97, 0xa6, 0xb7, 0x24, 0x0c, 0x49, 0x81, 0xd0, 0x48, 0x44, 0xe9, 0x44, 0xcc, 0x5e, 0x4b, + 0xb4, 0x66, 0x81, 0xc8, 0xe5, 0x90, 0x73, 0x32, 0x48, 0x4f, 0x4f, 0x7a, 0x20, 0xb8, 0x14, 0x08, + 0xae, 0xc3, 0x34, 0xca, 0x78, 0x9e, 0x87, 0x7c, 0xa4, 0x3b, 0xd4, 0x26, 0xb2, 0x6a, 0x05, 0xbb, + 0x0e, 0xeb, 0xc2, 0xcb, 0xca, 0xfc, 0x3c, 0xce, 0x8e, 0x82, 0x6c, 0x90, 0xf1, 0x28, 0xef, 0x75, + 0x88, 0xbe, 0xae, 0x8a, 0xbd, 0x0b, 0xe7, 0x4a, 0x70, 0xca, 0x87, 0x3c, 0x38, 0xe6, 0xa3, 0xde, + 0x32, 0x7d, 0x75, 0x5a, 0x35, 0xbb, 0x0c, 0x6d, 0x74, 0x2e, 0xa7, 0xc9, 0xc8, 0xc7, 0x7d, 0xb8, + 0x4b, 0xeb, 0x60, 0x42, 0xec, 0x6d, 0x58, 0x4e, 0xb8, 0xd8, 0x2c, 0x8f, 0xf2, 0x70, 0x98, 0xf5, + 0x56, 0x68, 0x27, 0x6b, 0x4b, 0x65, 0x42, 0xc9, 0xf5, 0x6c, 0x0a, 0x14, 0xca, 0x61, 0x46, 0xee, + 0x8a, 0x3f, 0xeb, 0xad, 0x92, 0xb8, 0x15, 0x00, 0xe9, 0x48, 0x1a, 0x1c, 0xfb, 0x39, 0xef, 0xad, + 0x91, 0x6c, 0xa9, 0xa2, 0xfb, 0x67, 0x0e, 0xac, 0x3f, 0x08, 0xb2, 0x5c, 0x0a, 0xa1, 0x36, 0xc7, + 0xaf, 0x43, 0x5b, 0x88, 0xdf, 0x20, 0x8e, 0xc2, 0x99, 0x94, 0x48, 0x10, 0xd0, 0xd7, 0xa2, 0x70, + 0xc6, 0x3e, 0x03, 0xcb, 0x41, 0x64, 0x92, 0x08, 0x1d, 0xee, 0x28, 0x90, 0x88, 0x5e, 0x87, 0x76, + 0x32, 0x3d, 0x08, 0x83, 0xa1, 0x20, 0x99, 0x13, 0x5c, 0x04, 0x44, 0x04, 0xe8, 0xe8, 0x89, 0x9e, + 0x08, 0x8a, 0x26, 0x51, 0xb4, 0x25, 0x86, 0x24, 0xee, 0x2d, 0xd8, 0xb0, 0x3b, 0x28, 0x8d, 0xd5, + 0x16, 0x2c, 0x49, 0xd9, 0xce, 0x7a, 0x6d, 0x9a, 0x9f, 0xae, 0x9c, 0x1f, 0x49, 0xea, 0xe9, 0x7a, + 0xf7, 0xdf, 0x1c, 0x68, 0xa2, 0x01, 0x38, 0xdd, 0x58, 0x98, 0x36, 0x7d, 0xce, 0xb2, 0xe9, 0xe4, + 0xf7, 0xa3, 0x57, 0x24, 0x44, 0x42, 0xa8, 0x8d, 0x81, 0x14, 0xf5, 0x29, 0x1f, 0x1e, 0x93, 0xee, + 0xe8, 0x7a, 0x44, 0x50, 0xb3, 0x70, 0xeb, 0xa4, 0xaf, 0x85, 0xe2, 0xe8, 0xb2, 0xaa, 0xa3, 0x2f, + 0x17, 0x8b, 0x3a, 0xfa, 0xae, 0x07, 0x8b, 0x41, 0x74, 0x10, 0x4f, 0xa3, 0x11, 0x29, 0xc9, 0x92, + 0xa7, 0x8a, 0xb8, 0xd8, 0x09, 0x79, 0x52, 0xc1, 0x84, 0x4b, 0xed, 0x28, 0x00, 0x97, 0xa1, 0x6b, + 0x95, 0x91, 0xc1, 0xd3, 0xfb, 0xd8, 0x3b, 0xb0, 0x66, 0x60, 0x72, 0x06, 0xdf, 0x80, 0xf9, 0x04, + 0x01, 0xe9, 0x28, 0x29, 0xf1, 0x22, 0x4b, 0x29, 0x6a, 0xdc, 0x55, 0x8c, 0x9f, 0xf3, 0xf7, 0xa3, + 0xc3, 0x58, 0x71, 0xfa, 0xe1, 0x1c, 0x06, 0xbc, 0x12, 0x92, 0x8c, 0xae, 0xc2, 0x4a, 0x30, 0xe2, + 0x51, 0x1e, 0xe4, 0xb3, 0x81, 0xe5, 0xc1, 0x95, 0x61, 0xdc, 0x61, 0xfc, 0x30, 0xf0, 0x33, 0x69, + 0xc3, 0x44, 0x81, 0xed, 0xc0, 0x06, 0x8a, 0xbf, 0x92, 0x68, 0xbd, 0xac, 0xc2, 0x91, 0xac, 0xad, + 0x43, 0x8d, 0x45, 0x5c, 0x4a, 0xa0, 0xfe, 0x44, 0x58, 0xda, 0xba, 0x2a, 0x9c, 0x35, 0xc1, 0x09, + 0x87, 0x3c, 0x2f, 0x54, 0x44, 0x03, 0x95, 0xe8, 0x6d, 0x41, 0x38, 0xb1, 0xe5, 0xe8, 0xcd, 0x88, + 0x00, 0x97, 0x2a, 0x11, 0xe0, 0x55, 0x58, 0xc9, 0x66, 0xd1, 0x90, 0x8f, 0x06, 0x79, 0x8c, 0xed, + 0x06, 0x11, 0xad, 0xce, 0x92, 0x57, 0x86, 0x29, 0x56, 0xe5, 0x59, 0x1e, 0xf1, 0x9c, 0x4c, 0xd7, + 0x92, 0xa7, 0x8a, 0xb8, 0x0b, 0x10, 0x89, 0x10, 0xea, 0x96, 0x27, 0x4b, 0xb8, 0x55, 0x4e, 0xd3, + 0x20, 0xeb, 0x75, 0x08, 0xa5, 0xdf, 0xec, 0xf3, 0x70, 0xf6, 0x00, 0x23, 0xab, 0x23, 0xee, 0x8f, + 0x78, 0x4a, 0xab, 0x2f, 0x02, 0x4b, 0x61, 0x81, 0xea, 0x2b, 0xb1, 0xed, 0x63, 0x9e, 0x66, 0x41, + 0x1c, 0x91, 0xed, 0x69, 0x79, 0xaa, 0xe8, 0x7e, 0x4c, 0x3b, 0xba, 0x0e, 0x79, 0x1f, 0x93, 0x39, + 0x62, 0x17, 0xa0, 0x25, 0xc6, 0x98, 0x1d, 0xf9, 0xd2, 0xc9, 0x58, 0x22, 0x60, 0xff, 0xc8, 0x47, + 0x05, 0xb6, 0xa6, 0xad, 0x41, 0x9e, 0x63, 0x9b, 0xb0, 0x5d, 0x31, 0x6b, 0x6f, 0x42, 0x57, 0x05, + 0xd3, 0xd9, 0x20, 0xe4, 0x87, 0xb9, 0x0a, 0x10, 0xa2, 0xe9, 0x04, 0x9b, 0xcb, 0x1e, 0xf0, 0xc3, + 0xdc, 0x7d, 0x08, 0x6b, 0x52, 0x6f, 0xbf, 0x96, 0x70, 0xd5, 0xf4, 0x17, 0xcb, 0x9b, 0x9a, 0xf0, + 0x2a, 0xd6, 0x6d, 0x45, 0xa7, 0x28, 0xa7, 0xb4, 0xd3, 0xb9, 0x1e, 0x30, 0x59, 0x7d, 0x3b, 0x8c, + 0x33, 0x2e, 0x19, 0xba, 0xd0, 0x19, 0x86, 0x71, 0xa6, 0xc2, 0x10, 0x39, 0x1c, 0x0b, 0xc3, 0xf9, + 0xc9, 0xa6, 0xc3, 0x21, 0x5a, 0x02, 0x61, 0xd3, 0x54, 0xd1, 0xfd, 0x73, 0x07, 0xd6, 0x89, 0x9b, + 0xb2, 0x30, 0xda, 0x77, 0x7d, 0xf5, 0x6e, 0x76, 0x86, 0x66, 0x68, 0xb6, 0x01, 0xf3, 0x87, 0x71, + 0x3a, 0xe4, 0xb2, 0x25, 0x51, 0xf8, 0xf1, 0xbd, 0xf1, 0x66, 0xc5, 0x1b, 0xff, 0xa1, 0x03, 0x6b, + 0xd4, 0xd5, 0xfd, 0xdc, 0xcf, 0xa7, 0x99, 0x1c, 0xfe, 0x2f, 0xc0, 0x32, 0x0e, 0x95, 0x2b, 0x75, + 0x92, 0x1d, 0xdd, 0xd0, 0x9a, 0x4f, 0xa8, 0x20, 0xde, 0x3d, 0xe3, 0xd9, 0xc4, 0xec, 0xcb, 0xd0, + 0x31, 0x33, 0x22, 0xd4, 0xe7, 0xf6, 0xce, 0x79, 0x35, 0xca, 0x8a, 0xe4, 0xec, 0x9e, 0xf1, 0xac, + 0x0f, 0xd8, 0x0d, 0x00, 0x72, 0x37, 0x88, 0xad, 0x0c, 0x65, 0xcf, 0xdb, 0x93, 0x64, 0x2c, 0xd6, + 0xee, 0x19, 0xcf, 0x20, 0xbf, 0xb5, 0x04, 0x0b, 0x62, 0x7f, 0x74, 0xef, 0xc3, 0xb2, 0xd5, 0x53, + 0x2b, 0xca, 0xe8, 0x88, 0x28, 0xa3, 0x12, 0x94, 0x36, 0xaa, 0x41, 0xa9, 0xfb, 0x2f, 0x0d, 0x60, + 0x28, 0x6d, 0xa5, 0xe5, 0xc4, 0x0d, 0x3a, 0x1e, 0x59, 0xee, 0x56, 0xc7, 0x33, 0x21, 0x76, 0x0d, + 0x98, 0x51, 0x54, 0xb9, 0x07, 0xb1, 0x6f, 0xd4, 0xd4, 0xa0, 0x81, 0x13, 0xbe, 0x92, 0x8a, 0x81, + 0xa5, 0x63, 0x29, 0xd6, 0xad, 0xb6, 0x0e, 0xb7, 0x86, 0x64, 0x9a, 0x1d, 0xa1, 0x03, 0xa1, 0x1c, + 0x32, 0x55, 0x2e, 0x0b, 0xc8, 0xc2, 0x4b, 0x05, 0x64, 0xb1, 0x2c, 0x20, 0xa6, 0x4b, 0xb0, 0x64, + 0xb9, 0x04, 0xe8, 0x7f, 0x4d, 0x82, 0x88, 0xfc, 0x8a, 0xc1, 0x04, 0x5b, 0x97, 0xfe, 0x97, 0x05, + 0xb2, 0x2d, 0x58, 0x95, 0x7e, 0x5d, 0xe1, 0x77, 0x00, 0xcd, 0x71, 0x05, 0x77, 0x3f, 0x75, 0x60, + 0x15, 0xe7, 0xd9, 0x92, 0xc5, 0xf7, 0x80, 0x54, 0xe1, 0x15, 0x45, 0xd1, 0xa2, 0xfd, 0xe9, 0x25, + 0xf1, 0x5d, 0x68, 0x11, 0xc3, 0x38, 0xe1, 0x91, 0x14, 0xc4, 0x9e, 0x2d, 0x88, 0x85, 0x15, 0xda, + 0x3d, 0xe3, 0x15, 0xc4, 0x86, 0x18, 0xfe, 0xbd, 0x03, 0x6d, 0xd9, 0xcd, 0x9f, 0x38, 0x96, 0xe8, + 0xc3, 0x12, 0x4a, 0xa4, 0xe1, 0xb0, 0xeb, 0x32, 0xee, 0x26, 0x13, 0x0c, 0xd8, 0x70, 0xfb, 0xb4, + 0xe2, 0x88, 0x32, 0x8c, 0x7b, 0x21, 0x19, 0xdc, 0x6c, 0x90, 0x07, 0xe1, 0x40, 0xd5, 0xca, 0x04, + 0x64, 0x5d, 0x15, 0xda, 0x9d, 0x2c, 0xf7, 0xc7, 0x5c, 0x6e, 0x73, 0xa2, 0x80, 0x01, 0x93, 0x1c, + 0x50, 0xc9, 0x1d, 0x74, 0xff, 0xa6, 0x03, 0xe7, 0x2a, 0x55, 0x3a, 0xdd, 0x2d, 0x1d, 0xe4, 0x30, + 0x98, 0x1c, 0xc4, 0xda, 0xd7, 0x76, 0x4c, 0xdf, 0xd9, 0xaa, 0x62, 0x63, 0x38, 0xab, 0xf6, 0x73, + 0x9c, 0xd3, 0x62, 0xf7, 0x6e, 0x90, 0x23, 0xf2, 0xb6, 0x2d, 0x03, 0xe5, 0x06, 0x15, 0x6e, 0x6a, + 0x6e, 0x3d, 0x3f, 0x76, 0x04, 0x3d, 0xed, 0x38, 0x48, 0x13, 0x6f, 0x38, 0x17, 0xd8, 0xd6, 0x5b, + 0x2f, 0x69, 0x8b, 0xec, 0xd1, 0x48, 0x35, 0x73, 0x2a, 0x37, 0x36, 0x83, 0x4b, 0xaa, 0x8e, 0x6c, + 0x78, 0xb5, 0xbd, 0xe6, 0x2b, 0x8d, 0xed, 0x1e, 0x7e, 0x6c, 0x37, 0xfa, 0x12, 0xc6, 0xec, 0x43, + 0xd8, 0x3c, 0xf1, 0x83, 0x5c, 0x75, 0xcb, 0x70, 0x86, 0xe6, 0xa9, 0xc9, 0x9d, 0x97, 0x34, 0xf9, + 0x44, 0x7c, 0x6c, 0x6d, 0x6c, 0xa7, 0x70, 0xec, 0xff, 0xad, 0x03, 0x5d, 0x9b, 0x0f, 0x8a, 0xa9, + 0x54, 0x78, 0x65, 0xf8, 0x94, 0xf3, 0x57, 0x82, 0xab, 0x21, 0x6a, 0xa3, 0x2e, 0x44, 0x35, 0x03, + 0xd1, 0xb9, 0x97, 0x05, 0xa2, 0xcd, 0x57, 0x0b, 0x44, 0xe7, 0xeb, 0x02, 0xd1, 0xfe, 0x7f, 0x3a, + 0xc0, 0xaa, 0xb2, 0xc4, 0xee, 0x8b, 0x18, 0x39, 0xe2, 0xa1, 0xb4, 0x49, 0x3f, 0xf7, 0x6a, 0xf2, + 0xa8, 0xe6, 0x4e, 0x7d, 0x8d, 0x8a, 0x61, 0x1a, 0x1d, 0xd3, 0x45, 0x5a, 0xf6, 0xea, 0xaa, 0x4a, + 0xa1, 0x71, 0xf3, 0xe5, 0xa1, 0xf1, 0xfc, 0xcb, 0x43, 0xe3, 0x85, 0x72, 0x68, 0xdc, 0xff, 0x5d, + 0x07, 0xd6, 0x6b, 0x16, 0xfd, 0x67, 0x37, 0x70, 0x5c, 0x26, 0xcb, 0x16, 0x34, 0xe4, 0x32, 0x99, + 0x60, 0xff, 0x37, 0x60, 0xd9, 0x12, 0xf4, 0x9f, 0x5d, 0xfb, 0x65, 0x2f, 0x4f, 0xc8, 0x99, 0x85, + 0xf5, 0x7f, 0xd4, 0x00, 0x56, 0x55, 0xb6, 0xff, 0xd3, 0x3e, 0x54, 0xe7, 0x69, 0xae, 0x66, 0x9e, + 0xfe, 0x57, 0xf7, 0x81, 0xb7, 0x60, 0x2d, 0xe5, 0xc3, 0xf8, 0x98, 0xce, 0x1d, 0xed, 0xec, 0x4e, + 0xb5, 0x02, 0xfd, 0x5c, 0x3b, 0x2f, 0xb1, 0x64, 0x1d, 0x13, 0x19, 0x9b, 0x61, 0x29, 0x3d, 0xe1, + 0x6e, 0xc2, 0x86, 0x38, 0xbd, 0xbb, 0x25, 0x58, 0xa9, 0x7d, 0xe5, 0x4f, 0x1d, 0x38, 0x5b, 0xaa, + 0x28, 0xce, 0x52, 0xc4, 0xd6, 0x61, 0xef, 0x27, 0x36, 0x88, 0xfd, 0x97, 0x7a, 0x64, 0xf4, 0x5f, + 0x48, 0x5b, 0xb5, 0x02, 0xe7, 0x67, 0x1a, 0x55, 0xe9, 0xc5, 0xac, 0xd7, 0x55, 0xb9, 0xe7, 0xe0, + 0xac, 0x5c, 0xd9, 0x52, 0xc7, 0x0f, 0x61, 0xb3, 0x5c, 0x51, 0x24, 0x87, 0xed, 0x2e, 0xab, 0x22, + 0x7a, 0x81, 0xd6, 0x36, 0x65, 0xf7, 0xb7, 0xb6, 0xce, 0xfd, 0x75, 0x60, 0x5f, 0x9f, 0xf2, 0x74, + 0x46, 0x27, 0x3d, 0x3a, 0x3b, 0x73, 0xae, 0x9c, 0xc6, 0x58, 0x48, 0xa6, 0x07, 0x5f, 0xe5, 0x33, + 0x75, 0x94, 0xd6, 0x28, 0x8e, 0xd2, 0x5e, 0x03, 0xc0, 0xe8, 0x8b, 0x8e, 0x86, 0xd4, 0xe1, 0x26, + 0x86, 0xbd, 0x82, 0xa1, 0x7b, 0x03, 0xd6, 0x2d, 0xfe, 0x7a, 0xf6, 0x17, 0xe4, 0x17, 0x22, 0x37, + 0x60, 0x1f, 0x38, 0xc9, 0x3a, 0xf7, 0xdf, 0x1d, 0x98, 0xdb, 0x8d, 0x13, 0x33, 0xab, 0xe8, 0xd8, + 0x59, 0x45, 0x69, 0xf2, 0x07, 0xda, 0xa2, 0x4b, 0x4b, 0x60, 0x81, 0x6c, 0x0b, 0xba, 0xfe, 0x24, + 0xc7, 0xe8, 0xf8, 0x30, 0x4e, 0x4f, 0xfc, 0x74, 0x24, 0x96, 0xe4, 0x56, 0xa3, 0xe7, 0x78, 0xa5, + 0x1a, 0xb6, 0x01, 0x73, 0xda, 0x36, 0x12, 0x01, 0x16, 0xd1, 0xbf, 0xa2, 0xe4, 0xea, 0x4c, 0x06, + 0xf6, 0xb2, 0x84, 0x2b, 0x6e, 0x7f, 0x2f, 0x3c, 0x5a, 0x21, 0xe1, 0x75, 0x55, 0xb8, 0xfd, 0xa0, + 0xa9, 0x24, 0x32, 0x99, 0x91, 0x51, 0x65, 0xf7, 0x5f, 0x1d, 0x98, 0xa7, 0x19, 0x40, 0x9d, 0x14, + 0x82, 0x48, 0x67, 0xb7, 0x94, 0x09, 0x76, 0x84, 0x4e, 0x96, 0x60, 0xe6, 0x5a, 0x27, 0xba, 0x0d, + 0xdd, 0x6d, 0xf3, 0x54, 0xf7, 0x32, 0xb4, 0x44, 0x49, 0x1f, 0x83, 0x12, 0x49, 0x01, 0xb2, 0x4b, + 0xd0, 0x3c, 0x8a, 0x13, 0xe5, 0x44, 0x80, 0x4a, 0x04, 0xc6, 0x89, 0x47, 0x78, 0xd1, 0x1f, 0xe4, + 0x27, 0x3a, 0x2f, 0xb6, 0x86, 0x32, 0x8c, 0x9b, 0xa3, 0x66, 0x6b, 0x4e, 0x46, 0x09, 0x75, 0xb7, + 0x60, 0xe5, 0x61, 0x3c, 0xe2, 0x46, 0xea, 0xe7, 0x54, 0xa9, 0x73, 0x7f, 0xd3, 0x81, 0x25, 0x45, + 0xcc, 0xae, 0x42, 0x13, 0x77, 0xfc, 0x92, 0x3f, 0xaf, 0x0f, 0x00, 0x90, 0xce, 0x23, 0x0a, 0x34, + 0x91, 0x94, 0x18, 0x28, 0xbc, 0x3f, 0x95, 0x16, 0x28, 0x9c, 0x1b, 0xdd, 0xdd, 0x92, 0x4f, 0x50, + 0x42, 0xdd, 0xbf, 0x70, 0x60, 0xd9, 0x6a, 0x03, 0xa3, 0xb8, 0xd0, 0xcf, 0x72, 0x99, 0x54, 0x95, + 0xcb, 0x63, 0x42, 0x66, 0x32, 0xb0, 0x61, 0x27, 0x03, 0x75, 0x9a, 0x6a, 0xce, 0x4c, 0x53, 0x5d, + 0x87, 0x56, 0x71, 0xee, 0xde, 0xb4, 0x4c, 0x1f, 0xb6, 0xa8, 0x8e, 0x36, 0x0a, 0x22, 0xe4, 0x33, + 0x8c, 0xc3, 0x38, 0x95, 0xc7, 0xd2, 0xa2, 0xe0, 0xde, 0x80, 0xb6, 0x41, 0x8f, 0xdd, 0x88, 0x78, + 0x7e, 0x12, 0xa7, 0x4f, 0x55, 0x4e, 0x52, 0x16, 0xf5, 0x09, 0x5e, 0xa3, 0x38, 0xc1, 0x73, 0xff, + 0xd2, 0x81, 0x65, 0x94, 0xc1, 0x20, 0x1a, 0xef, 0xc5, 0x61, 0x30, 0x9c, 0xd1, 0xda, 0x2b, 0x71, + 0x93, 0xe7, 0xd5, 0x4a, 0x16, 0x6d, 0x18, 0x65, 0x5b, 0x05, 0x71, 0x52, 0x11, 0x75, 0x19, 0x35, + 0x15, 0xe5, 0xfc, 0xc0, 0xcf, 0xa4, 0xf0, 0xcb, 0xbd, 0xc8, 0x02, 0x51, 0x9f, 0x10, 0x48, 0xfd, + 0x9c, 0x0f, 0x26, 0x41, 0x18, 0x06, 0x82, 0x56, 0x78, 0x2a, 0x75, 0x55, 0xee, 0x0f, 0x1a, 0xd0, + 0x96, 0x96, 0xf2, 0xee, 0x68, 0x2c, 0xb2, 0xff, 0xd2, 0xdf, 0xd3, 0xe6, 0xc2, 0x40, 0x54, 0xbd, + 0xe5, 0x21, 0x1a, 0x48, 0x79, 0x59, 0xe7, 0xaa, 0xcb, 0x7a, 0x11, 0x5a, 0x28, 0x5e, 0x6f, 0x93, + 0x2b, 0x2a, 0xae, 0x69, 0x14, 0x80, 0xaa, 0xdd, 0xa1, 0xda, 0xf9, 0xa2, 0x96, 0x00, 0xcb, 0xf9, + 0x5c, 0x28, 0x39, 0x9f, 0xef, 0x42, 0x47, 0xb2, 0xa1, 0x79, 0x27, 0xeb, 0x50, 0x08, 0xb8, 0xb5, + 0x26, 0x9e, 0x45, 0xa9, 0xbe, 0xdc, 0x51, 0x5f, 0x2e, 0xbd, 0xec, 0x4b, 0x45, 0x49, 0x87, 0x61, + 0x62, 0x6e, 0xee, 0xa7, 0x7e, 0x72, 0xa4, 0x76, 0x9f, 0x91, 0x3e, 0xe1, 0x27, 0x98, 0x6d, 0xc1, + 0x3c, 0x7e, 0xa6, 0xac, 0x75, 0xbd, 0xd2, 0x09, 0x12, 0x76, 0x15, 0xe6, 0xf9, 0x68, 0xcc, 0x55, + 0xb0, 0xc5, 0xec, 0xb0, 0x17, 0xd7, 0xc8, 0x13, 0x04, 0x68, 0x02, 0x10, 0x2d, 0x99, 0x00, 0xdb, + 0xd2, 0x2f, 0x60, 0xf1, 0xfd, 0x91, 0xbb, 0x01, 0xec, 0xa1, 0x90, 0x5a, 0x33, 0x59, 0xfc, 0x3b, + 0x73, 0xd0, 0x36, 0x60, 0xd4, 0xe6, 0x31, 0x76, 0x78, 0x30, 0x0a, 0xfc, 0x09, 0xcf, 0x79, 0x2a, + 0x25, 0xb5, 0x84, 0x22, 0x9d, 0x7f, 0x3c, 0x1e, 0xc4, 0xd3, 0x7c, 0x30, 0xe2, 0xe3, 0x94, 0x8b, + 0x3d, 0x12, 0x37, 0x03, 0x0b, 0x45, 0xba, 0x89, 0xff, 0xcc, 0xa4, 0x13, 0xf2, 0x50, 0x42, 0x55, + 0xea, 0x57, 0xcc, 0x51, 0xb3, 0x48, 0xfd, 0x8a, 0x19, 0x29, 0xdb, 0xa1, 0xf9, 0x1a, 0x3b, 0xf4, + 0x0e, 0x6c, 0x0a, 0x8b, 0x23, 0x75, 0x73, 0x50, 0x12, 0x93, 0x53, 0x6a, 0xd9, 0x16, 0xac, 0x62, + 0x9f, 0x95, 0x80, 0x67, 0xc1, 0xc7, 0x22, 0x19, 0xe3, 0x78, 0x15, 0x1c, 0x69, 0x51, 0x1d, 0x2d, + 0x5a, 0x71, 0x3c, 0x56, 0xc1, 0x89, 0xd6, 0x7f, 0x66, 0xd3, 0xb6, 0x24, 0x6d, 0x09, 0x77, 0x97, + 0xa1, 0xbd, 0x9f, 0xc7, 0x89, 0x5a, 0x94, 0x2e, 0x74, 0x44, 0x51, 0x1e, 0x86, 0x5e, 0x80, 0xf3, + 0x24, 0x45, 0x8f, 0xe2, 0x24, 0x0e, 0xe3, 0xf1, 0x6c, 0x7f, 0x7a, 0x90, 0x0d, 0xd3, 0x20, 0xc1, + 0xc0, 0xc4, 0xfd, 0x3b, 0x07, 0xd6, 0xad, 0x5a, 0x99, 0xbd, 0xf9, 0xbc, 0x10, 0x69, 0x7d, 0x8a, + 0x25, 0x04, 0x6f, 0xcd, 0x30, 0x87, 0x82, 0x50, 0xe4, 0xcd, 0x1e, 0xcb, 0x83, 0xad, 0x9b, 0xb0, + 0xa2, 0x7a, 0xa6, 0x3e, 0x14, 0x52, 0xd8, 0xab, 0x4a, 0xa1, 0xfc, 0xbe, 0x2b, 0x3f, 0x50, 0x2c, + 0x7e, 0x51, 0xf8, 0xd5, 0x7c, 0x44, 0x63, 0x54, 0x61, 0x7c, 0x5f, 0x7d, 0x6f, 0x3a, 0xf3, 0xaa, + 0x07, 0x43, 0x0d, 0x66, 0xee, 0xef, 0x3b, 0x00, 0x45, 0xef, 0x50, 0x30, 0x0a, 0x93, 0x2e, 0xee, + 0xe7, 0x19, 0xe6, 0xfb, 0x0d, 0xe8, 0xe8, 0x03, 0x8c, 0x62, 0x97, 0x68, 0x2b, 0x0c, 0x1d, 0xae, + 0x2b, 0xb0, 0x32, 0x0e, 0xe3, 0x03, 0xda, 0x62, 0xe9, 0x74, 0x3d, 0x93, 0x47, 0xc2, 0x5d, 0x01, + 0xdf, 0x93, 0x68, 0xb1, 0xa5, 0x34, 0x8d, 0x2d, 0xc5, 0xfd, 0x4e, 0x43, 0xa7, 0xbd, 0x8b, 0x31, + 0x9f, 0xaa, 0x65, 0x6c, 0xa7, 0x62, 0x1c, 0x4f, 0xc9, 0x32, 0x53, 0xc2, 0x6a, 0xef, 0xa5, 0xf1, + 0xf4, 0x0d, 0xe8, 0xa6, 0xc2, 0xfa, 0x28, 0xd3, 0xd4, 0x7c, 0x81, 0x69, 0x5a, 0x4e, 0xad, 0x7d, + 0xe7, 0xff, 0xc1, 0xaa, 0x3f, 0x3a, 0xe6, 0x69, 0x1e, 0x50, 0x44, 0x43, 0x9b, 0xbe, 0x30, 0xa8, + 0x2b, 0x06, 0x4e, 0x7b, 0xf1, 0x15, 0x58, 0x91, 0xc7, 0xf0, 0x9a, 0x52, 0x5e, 0xbe, 0x2a, 0x60, + 0x24, 0x74, 0xbf, 0xaf, 0x32, 0xec, 0xf6, 0x1a, 0x9e, 0x3e, 0x23, 0xe6, 0xe8, 0x1a, 0xa5, 0xd1, + 0x7d, 0x46, 0x66, 0xbb, 0x47, 0x2a, 0x6c, 0x92, 0xe7, 0x0e, 0x02, 0x94, 0xa7, 0x13, 0xf6, 0x94, + 0x36, 0x5f, 0x65, 0x4a, 0xdd, 0x4f, 0x1d, 0x58, 0xdc, 0x8d, 0x93, 0x5d, 0x79, 0xa2, 0x4e, 0x8a, + 0xa0, 0x2f, 0xb9, 0xa8, 0xa2, 0xe9, 0x15, 0x37, 0x2a, 0x5e, 0x71, 0x75, 0xaf, 0x5d, 0x2e, 0xef, + 0xb5, 0xbf, 0x04, 0x17, 0x28, 0x68, 0x4f, 0xe3, 0x24, 0x4e, 0x51, 0x19, 0xfd, 0x50, 0x6c, 0xac, + 0x71, 0x94, 0x1f, 0x29, 0x33, 0xf6, 0x22, 0x12, 0x8a, 0x8e, 0xc2, 0xfc, 0x78, 0x20, 0x9c, 0x61, + 0xe9, 0x1b, 0x08, 0xeb, 0x56, 0xad, 0x70, 0xbf, 0x08, 0x2d, 0x72, 0x6e, 0x69, 0x58, 0x6f, 0x41, + 0xeb, 0x28, 0x4e, 0x06, 0x47, 0x41, 0x94, 0x2b, 0xe5, 0xee, 0x16, 0x5e, 0xe7, 0x2e, 0x4d, 0x88, + 0x26, 0x70, 0x7f, 0x34, 0x07, 0x8b, 0xef, 0x47, 0xc7, 0x71, 0x30, 0xa4, 0x64, 0xfc, 0x84, 0x4f, + 0x62, 0x75, 0xe5, 0x07, 0x7f, 0xe3, 0x54, 0xd0, 0xf1, 0x77, 0x92, 0xcb, 0x6c, 0xba, 0x2a, 0xe2, + 0x76, 0x9f, 0x16, 0xd7, 0xe0, 0x84, 0xea, 0x18, 0x08, 0x3a, 0xf6, 0xa9, 0x79, 0x07, 0x50, 0x96, + 0x8a, 0x3b, 0x53, 0xf3, 0xc6, 0x9d, 0x29, 0x3a, 0xba, 0x11, 0x27, 0xfb, 0x24, 0x5f, 0x4b, 0x9e, + 0x2a, 0x52, 0x20, 0x92, 0x72, 0x91, 0x6c, 0x21, 0xc7, 0x61, 0x51, 0x06, 0x22, 0x26, 0x88, 0xce, + 0x85, 0xf8, 0x40, 0xd0, 0x08, 0xe3, 0x6b, 0x42, 0xe8, 0x6c, 0x95, 0xaf, 0x11, 0xb6, 0x84, 0xcc, + 0x97, 0x60, 0xb4, 0xd0, 0x23, 0xae, 0x0d, 0xa9, 0x18, 0x03, 0x88, 0x6b, 0x7e, 0x65, 0xdc, 0x08, + 0x5f, 0xc4, 0x0d, 0x05, 0x15, 0xbe, 0xa0, 0xa0, 0xf8, 0x61, 0x78, 0xe0, 0x0f, 0x9f, 0xd2, 0xe5, + 0x4e, 0xba, 0x90, 0xd0, 0xf2, 0x6c, 0x10, 0x7b, 0x6d, 0xac, 0x26, 0x1d, 0xfe, 0x35, 0x3d, 0x13, + 0x62, 0x3b, 0xd0, 0xa6, 0x90, 0x4d, 0xae, 0x67, 0x97, 0xd6, 0x73, 0xd5, 0x8c, 0xe9, 0x68, 0x45, + 0x4d, 0x22, 0xf3, 0x80, 0x60, 0xc5, 0xbe, 0x33, 0xf0, 0x0d, 0x60, 0x37, 0x47, 0x23, 0xb9, 0xde, + 0x3a, 0x64, 0x2c, 0x56, 0xca, 0xb1, 0x56, 0xaa, 0x66, 0xc6, 0x1a, 0xb5, 0x33, 0xe6, 0xde, 0x85, + 0xf6, 0x9e, 0x71, 0xc3, 0x93, 0x44, 0x43, 0xdd, 0xed, 0x94, 0xe2, 0x64, 0x20, 0x46, 0x83, 0x0d, + 0xb3, 0x41, 0xf7, 0xe7, 0x81, 0x3d, 0x08, 0xb2, 0x5c, 0xf7, 0x4f, 0x2c, 0xc7, 0x1b, 0xd0, 0xd1, + 0x01, 0x76, 0x71, 0xa3, 0xa1, 0x2d, 0x31, 0xba, 0x69, 0x70, 0x53, 0x5c, 0x85, 0x28, 0x0f, 0x6c, + 0x0b, 0x96, 0x02, 0x01, 0x95, 0x35, 0x41, 0x51, 0xea, 0x7a, 0xf4, 0xd7, 0x24, 0x68, 0xed, 0xa2, + 0x3f, 0x70, 0x60, 0x51, 0x0e, 0x0d, 0xbd, 0x0d, 0xeb, 0x6e, 0xab, 0x18, 0x98, 0x85, 0xd5, 0xdf, + 0x08, 0xac, 0xca, 0xf0, 0x5c, 0x9d, 0x0c, 0x33, 0x68, 0x26, 0x7e, 0x7e, 0x44, 0x01, 0x4a, 0xcb, + 0xa3, 0xdf, 0x6c, 0x55, 0x04, 0xcd, 0x42, 0x57, 0x28, 0x60, 0xae, 0xbb, 0x84, 0x2a, 0x4c, 0x72, + 0x05, 0xc7, 0x41, 0xd1, 0xe5, 0x01, 0x81, 0xeb, 0x33, 0x01, 0x79, 0x31, 0xa3, 0x80, 0x8b, 0xf9, + 0x92, 0x2c, 0xca, 0xf3, 0x25, 0x49, 0x3d, 0x5d, 0xef, 0xf6, 0xa1, 0x77, 0x87, 0x87, 0x3c, 0xe7, + 0x37, 0xc3, 0xb0, 0xcc, 0xff, 0x02, 0x9c, 0xaf, 0xa9, 0x93, 0x4e, 0xcb, 0x3d, 0x58, 0xbb, 0xc3, + 0x0f, 0xa6, 0xe3, 0x07, 0xfc, 0xb8, 0x38, 0xb8, 0x63, 0xd0, 0xcc, 0x8e, 0xe2, 0x13, 0xb9, 0xb6, + 0xf4, 0x9b, 0xbd, 0x06, 0x10, 0x22, 0xcd, 0x20, 0x4b, 0xf8, 0x50, 0xdd, 0x85, 0x23, 0x64, 0x3f, + 0xe1, 0x43, 0xf7, 0x1d, 0x60, 0x26, 0x1f, 0x39, 0x04, 0xb4, 0x03, 0xd3, 0x83, 0x41, 0x36, 0xcb, + 0x72, 0x3e, 0x51, 0x97, 0xfc, 0x4c, 0xc8, 0xbd, 0x02, 0x9d, 0x3d, 0x7f, 0xe6, 0xf1, 0x8f, 0xe4, + 0xf5, 0x62, 0x8c, 0x8d, 0xfd, 0x19, 0x8a, 0xb2, 0x8e, 0x8d, 0xa9, 0xda, 0xfd, 0x8f, 0x06, 0x2c, + 0x08, 0x4a, 0xe4, 0x3a, 0xe2, 0x59, 0x1e, 0x44, 0xe2, 0xd0, 0x4a, 0x72, 0x35, 0xa0, 0x8a, 0x6c, + 0x34, 0x6a, 0x64, 0x43, 0x7a, 0xab, 0xea, 0x5e, 0x91, 0x14, 0x02, 0x0b, 0x43, 0xb7, 0xa6, 0xb8, + 0x0c, 0x20, 0x82, 0xb3, 0x02, 0x28, 0x25, 0x4b, 0x0a, 0x6b, 0x23, 0xfa, 0xa7, 0x84, 0x56, 0x8a, + 0x83, 0x09, 0xd5, 0xda, 0xb4, 0x45, 0x21, 0x35, 0x15, 0x9b, 0x56, 0xb1, 0x5d, 0x4b, 0xaf, 0x60, + 0xbb, 0x84, 0x0b, 0xfb, 0x22, 0xdb, 0x05, 0xaf, 0x60, 0xbb, 0x5c, 0x06, 0xab, 0xf7, 0x38, 0xf7, + 0x38, 0xee, 0x8a, 0x4a, 0x9c, 0xbe, 0xeb, 0xc0, 0xaa, 0xdc, 0xd0, 0x75, 0x1d, 0x7b, 0xc3, 0xda, + 0xfd, 0x9d, 0xba, 0xf3, 0x88, 0x37, 0x61, 0x99, 0xf6, 0x64, 0x9d, 0x15, 0x92, 0x29, 0x2c, 0x0b, + 0xc4, 0x71, 0xa8, 0x0c, 0xfb, 0x24, 0x08, 0xe5, 0xa2, 0x98, 0x90, 0x4a, 0x2c, 0x61, 0x7c, 0x4c, + 0x4b, 0xe2, 0x78, 0xba, 0xec, 0xfe, 0xb5, 0x03, 0x6b, 0x46, 0x87, 0xa5, 0x14, 0xde, 0x00, 0x75, + 0x59, 0x40, 0x24, 0x8f, 0x84, 0x32, 0x9d, 0xb3, 0x9d, 0x93, 0xe2, 0x33, 0x8b, 0x98, 0x16, 0xd3, + 0x9f, 0x51, 0x07, 0xb3, 0xe9, 0x44, 0x7a, 0x20, 0x26, 0x84, 0x82, 0x74, 0xc2, 0xf9, 0x53, 0x4d, + 0x32, 0x47, 0x24, 0x16, 0x46, 0x67, 0xc1, 0xe8, 0x4b, 0x68, 0x22, 0x71, 0xfd, 0xc9, 0x06, 0xdd, + 0x7f, 0x70, 0x60, 0x5d, 0x38, 0x85, 0xd2, 0xe5, 0xd6, 0x57, 0x33, 0x17, 0x84, 0x17, 0x2c, 0x34, + 0x72, 0xf7, 0x8c, 0x27, 0xcb, 0xec, 0x0b, 0xaf, 0xe8, 0xc8, 0xea, 0x3b, 0x00, 0xa7, 0xac, 0xc5, + 0x5c, 0xdd, 0x5a, 0xbc, 0x60, 0xa6, 0xeb, 0x92, 0x25, 0xf3, 0xb5, 0xc9, 0x92, 0x5b, 0x8b, 0x30, + 0x9f, 0x0d, 0xe3, 0x84, 0xbb, 0x9b, 0xb0, 0x61, 0x0f, 0x4e, 0x9a, 0xa0, 0xef, 0x39, 0xd0, 0xbb, + 0x27, 0x52, 0x87, 0x41, 0x34, 0xde, 0x0d, 0xb2, 0x3c, 0x4e, 0xf5, 0x5d, 0xf4, 0x4b, 0x00, 0x59, + 0xee, 0xa7, 0xb9, 0xb8, 0xa3, 0x25, 0xd3, 0x1c, 0x05, 0x82, 0x7d, 0xe4, 0xd1, 0x48, 0xd4, 0x8a, + 0xb5, 0xd1, 0x65, 0x5c, 0x18, 0xba, 0x9f, 0x30, 0x88, 0x0f, 0x0f, 0x33, 0xae, 0xdd, 0x56, 0x13, + 0xc3, 0xc8, 0x17, 0x35, 0x1e, 0x63, 0x3d, 0x7e, 0x4c, 0xa6, 0x56, 0xf8, 0x83, 0x25, 0xd4, 0xfd, + 0x2b, 0x07, 0x56, 0x8a, 0x4e, 0xde, 0x45, 0xd0, 0xb6, 0x0e, 0xa2, 0x6b, 0x86, 0x75, 0x50, 0x09, + 0x98, 0x60, 0x34, 0x08, 0x22, 0xd9, 0x37, 0x03, 0x21, 0x8d, 0x95, 0xa5, 0x78, 0xaa, 0xee, 0xc3, + 0x99, 0x90, 0x38, 0xec, 0xce, 0xf1, 0x6b, 0x71, 0x19, 0x4e, 0x96, 0xe8, 0x8a, 0xdd, 0x24, 0xa7, + 0xaf, 0x16, 0x84, 0x43, 0x2c, 0x8b, 0x6a, 0x7f, 0x5a, 0x24, 0x14, 0x7f, 0xba, 0x7f, 0xe0, 0xc0, + 0xf9, 0x9a, 0xc9, 0x95, 0x9a, 0x71, 0x07, 0xd6, 0x0e, 0x75, 0xa5, 0x9a, 0x00, 0xa1, 0x1e, 0x9b, + 0x52, 0x8a, 0x4a, 0x83, 0xf6, 0xaa, 0x1f, 0xa0, 0x7b, 0x4c, 0x79, 0x23, 0x31, 0xa5, 0xd6, 0x3d, + 0x91, 0x6a, 0xc5, 0xce, 0xf7, 0x1b, 0xd0, 0x15, 0x47, 0x15, 0xe2, 0x35, 0x12, 0x4f, 0xd9, 0x07, + 0xb0, 0x28, 0xdf, 0x7e, 0xb1, 0xb3, 0xb2, 0x59, 0xfb, 0xb5, 0x59, 0x7f, 0xb3, 0x0c, 0x4b, 0xd9, + 0x59, 0xff, 0xed, 0x4f, 0xff, 0xf9, 0x0f, 0x1b, 0xcb, 0xac, 0xbd, 0x7d, 0xfc, 0xf6, 0xf6, 0x98, + 0x47, 0x19, 0xf2, 0xf8, 0x55, 0x80, 0xe2, 0xf9, 0x14, 0xeb, 0x69, 0x27, 0xa3, 0xf4, 0xdc, 0xab, + 0x7f, 0xbe, 0xa6, 0x46, 0xf2, 0x3d, 0x4f, 0x7c, 0xd7, 0xdd, 0x2e, 0xf2, 0x0d, 0xa2, 0x20, 0x17, + 0x6f, 0xa9, 0xde, 0x73, 0xb6, 0xd8, 0x08, 0x3a, 0xe6, 0x33, 0x2a, 0xa6, 0x42, 0xe6, 0x9a, 0xb7, + 0x59, 0xfd, 0x0b, 0xb5, 0x75, 0x2a, 0x5f, 0x40, 0x6d, 0x9c, 0x75, 0x57, 0xb1, 0x8d, 0x29, 0x51, + 0xe8, 0x56, 0x76, 0xfe, 0xf1, 0x02, 0xb4, 0x74, 0xda, 0x89, 0x7d, 0x08, 0xcb, 0xd6, 0xe9, 0x0e, + 0x53, 0x8c, 0xeb, 0x0e, 0x83, 0xfa, 0x17, 0xeb, 0x2b, 0x65, 0xb3, 0x97, 0xa8, 0xd9, 0x1e, 0xdb, + 0xc4, 0x66, 0xe5, 0xf1, 0xc8, 0x36, 0x9d, 0x69, 0x89, 0x2b, 0x75, 0x4f, 0xa1, 0x6b, 0x9f, 0xc8, + 0xb0, 0x8b, 0xb6, 0x41, 0x29, 0xb5, 0xf6, 0xda, 0x29, 0xb5, 0xb2, 0xb9, 0x8b, 0xd4, 0xdc, 0x26, + 0xdb, 0x30, 0x9b, 0xd3, 0xe9, 0x20, 0x4e, 0x97, 0x20, 0xcd, 0xf7, 0x55, 0xec, 0x35, 0xbd, 0xd4, + 0x75, 0xef, 0xae, 0xf4, 0xa2, 0x55, 0x1f, 0x5f, 0xb9, 0x3d, 0x6a, 0x8a, 0x31, 0x9a, 0x50, 0xf3, + 0x79, 0x15, 0xfb, 0x16, 0xb4, 0xf4, 0x9b, 0x0a, 0x76, 0xce, 0x78, 0xc8, 0x62, 0x3e, 0xf4, 0xe8, + 0xf7, 0xaa, 0x15, 0x75, 0x4b, 0x65, 0x72, 0x46, 0x81, 0x78, 0x00, 0x67, 0xa5, 0x93, 0x7a, 0xc0, + 0x7f, 0x9c, 0x91, 0xd4, 0xbc, 0x0a, 0xbb, 0xee, 0xb0, 0x1b, 0xb0, 0xa4, 0x9e, 0xaa, 0xb0, 0xcd, + 0xfa, 0x27, 0x37, 0xfd, 0x73, 0x15, 0x5c, 0xea, 0xf3, 0x4d, 0x80, 0xe2, 0x99, 0x85, 0x96, 0xfc, + 0xca, 0xe3, 0x0f, 0x3d, 0x89, 0x35, 0x6f, 0x32, 0xc6, 0xf4, 0xa8, 0xc4, 0x7e, 0xc5, 0xc1, 0x5e, + 0x2f, 0xe8, 0x6b, 0xdf, 0x77, 0xbc, 0x80, 0xa1, 0xbb, 0x49, 0x73, 0xb7, 0xca, 0x48, 0x95, 0x22, + 0x7e, 0xa2, 0xae, 0x03, 0xdf, 0x81, 0xb6, 0xf1, 0x74, 0x83, 0x29, 0x0e, 0xd5, 0x67, 0x1f, 0xfd, + 0x7e, 0x5d, 0x95, 0xec, 0xee, 0x57, 0x60, 0xd9, 0x7a, 0x83, 0xa1, 0x35, 0xa3, 0xee, 0x85, 0x87, + 0xd6, 0x8c, 0xfa, 0x67, 0x1b, 0xdf, 0x84, 0xb6, 0xf1, 0x62, 0x82, 0x19, 0xd7, 0xa0, 0x4a, 0x6f, + 0x25, 0x74, 0x8f, 0xea, 0x1e, 0x58, 0x6c, 0xd0, 0x78, 0xbb, 0x6e, 0x0b, 0xc7, 0x4b, 0x77, 0x62, + 0x51, 0x48, 0x3e, 0x84, 0xae, 0xfd, 0x86, 0x42, 0x6b, 0x55, 0xed, 0x6b, 0x0c, 0xad, 0x55, 0xa7, + 0x3c, 0xbc, 0x90, 0x02, 0xb9, 0xb5, 0xae, 0x1b, 0xd9, 0xfe, 0x44, 0x1e, 0xba, 0x3c, 0x67, 0x5f, + 0x47, 0xd3, 0x21, 0x2f, 0x29, 0xb3, 0xe2, 0xe5, 0x88, 0x7d, 0x95, 0x59, 0x4b, 0x7b, 0xe5, 0x3e, + 0xb3, 0xbb, 0x46, 0xcc, 0xdb, 0xac, 0x18, 0x81, 0xb0, 0xd0, 0x74, 0x59, 0xd9, 0xb0, 0xd0, 0xe6, + 0x7d, 0x66, 0xc3, 0x42, 0x5b, 0x77, 0x9a, 0xcb, 0x16, 0x3a, 0x0f, 0x90, 0x47, 0x04, 0x2b, 0xa5, + 0x7b, 0x00, 0x5a, 0x59, 0xea, 0x2f, 0x4e, 0xf5, 0x2f, 0xbd, 0xf8, 0xfa, 0x80, 0x6d, 0x66, 0x94, + 0x79, 0xd9, 0x56, 0xf7, 0xdc, 0x7e, 0x0d, 0x3a, 0xe6, 0xdd, 0x77, 0x6d, 0xb3, 0x6b, 0x6e, 0xec, + 0x6b, 0x9b, 0x5d, 0x77, 0x59, 0x5e, 0x2d, 0x2e, 0xeb, 0x98, 0xcd, 0xb0, 0x6f, 0xc2, 0x8a, 0x71, + 0xf1, 0x65, 0x7f, 0x16, 0x0d, 0xb5, 0xf0, 0x54, 0xaf, 0x45, 0xf6, 0xeb, 0xfc, 0x33, 0xf7, 0x1c, + 0x31, 0x5e, 0x73, 0x2d, 0xc6, 0x28, 0x38, 0xb7, 0xa1, 0x6d, 0x5e, 0xaa, 0x79, 0x01, 0xdf, 0x73, + 0x46, 0x95, 0x79, 0x43, 0xf0, 0xba, 0xc3, 0xfe, 0xd8, 0x81, 0x8e, 0x75, 0x45, 0xc5, 0xca, 0xf3, + 0x96, 0xf8, 0xf4, 0xcc, 0x3a, 0x93, 0x91, 0xeb, 0x51, 0x27, 0x1f, 0x6c, 0x7d, 0xc5, 0x9a, 0xe4, + 0x4f, 0x2c, 0x3f, 0xff, 0x5a, 0xf9, 0x59, 0xe3, 0xf3, 0x32, 0x81, 0x79, 0x75, 0xf4, 0xf9, 0x75, + 0x87, 0xbd, 0x27, 0x9e, 0xbe, 0xaa, 0xb8, 0x9e, 0x19, 0xc6, 0xad, 0x3c, 0x65, 0xe6, 0x2b, 0xd1, + 0xab, 0xce, 0x75, 0x87, 0x7d, 0x5b, 0xbc, 0x5e, 0x94, 0xdf, 0xd2, 0xcc, 0xbf, 0xea, 0xf7, 0xee, + 0x9b, 0x34, 0x9a, 0x4b, 0xee, 0x79, 0x6b, 0x34, 0x65, 0xeb, 0xbe, 0x07, 0x50, 0x24, 0x69, 0x58, + 0x29, 0x63, 0xa1, 0xed, 0x5e, 0x35, 0x8f, 0x63, 0xaf, 0xa8, 0x4a, 0x6c, 0x20, 0xc7, 0x6f, 0x09, + 0x61, 0x94, 0xf4, 0x99, 0x5e, 0xd2, 0x6a, 0xb2, 0xa5, 0xdf, 0xaf, 0xab, 0xaa, 0x13, 0x45, 0xc5, + 0x9f, 0x3d, 0x86, 0xe5, 0x07, 0x71, 0xfc, 0x74, 0x9a, 0xe8, 0x34, 0xa2, 0x9d, 0x33, 0xd8, 0xf5, + 0xb3, 0xa3, 0x7e, 0x69, 0x14, 0xee, 0x65, 0x62, 0xd5, 0x67, 0x3d, 0x83, 0xd5, 0xf6, 0x27, 0x45, + 0x8a, 0xe8, 0x39, 0xf3, 0x61, 0x4d, 0xef, 0x71, 0xba, 0xe3, 0x7d, 0x9b, 0x8d, 0x99, 0xa9, 0xa9, + 0x34, 0x61, 0x79, 0x1d, 0xaa, 0xb7, 0xdb, 0x99, 0xe2, 0x79, 0xdd, 0x61, 0x7b, 0xd0, 0xb9, 0xc3, + 0x87, 0xf1, 0x88, 0xcb, 0x28, 0x7f, 0xbd, 0xe8, 0xb8, 0x4e, 0x0f, 0xf4, 0x97, 0x2d, 0xd0, 0xd6, + 0xfa, 0xc4, 0x9f, 0xa5, 0xfc, 0xa3, 0xed, 0x4f, 0x64, 0xfe, 0xe0, 0xb9, 0xd2, 0x7a, 0x95, 0xf3, + 0xb0, 0xb4, 0xbe, 0x94, 0x24, 0xb1, 0xb4, 0xbe, 0x92, 0x24, 0xb1, 0xa6, 0x5a, 0xe5, 0x5c, 0x58, + 0x08, 0x6b, 0x95, 0xbc, 0x8a, 0xde, 0x29, 0x4f, 0xcb, 0xc6, 0xf4, 0x2f, 0x9f, 0x4e, 0x60, 0xb7, + 0xb6, 0x65, 0xb7, 0xb6, 0x0f, 0xcb, 0x77, 0xb8, 0x98, 0x2c, 0x71, 0x56, 0xd9, 0xb7, 0xcd, 0x88, + 0x79, 0xae, 0x59, 0x36, 0x31, 0x54, 0x67, 0x9b, 0x75, 0x3a, 0x28, 0x64, 0xdf, 0x82, 0xf6, 0x7d, + 0x9e, 0xab, 0xc3, 0x49, 0xed, 0x6f, 0x94, 0x4e, 0x2b, 0xfb, 0x35, 0x67, 0x9b, 0xb6, 0xcc, 0x10, + 0xb7, 0x6d, 0x3e, 0x1a, 0x73, 0xa1, 0xec, 0x83, 0x60, 0xf4, 0x9c, 0xfd, 0x32, 0x31, 0xd7, 0xf7, + 0x19, 0x36, 0x8d, 0x33, 0x2d, 0x93, 0xf9, 0x4a, 0x09, 0xaf, 0xe3, 0x1c, 0xc5, 0x23, 0x6e, 0x6c, + 0x70, 0x11, 0xb4, 0x8d, 0xcb, 0x36, 0x5a, 0x81, 0xaa, 0x17, 0x7c, 0xb4, 0x02, 0xd5, 0xdc, 0xcd, + 0x71, 0xaf, 0x52, 0x3b, 0x2e, 0xbb, 0x5c, 0xb4, 0x23, 0xee, 0xe3, 0x14, 0x2d, 0x6d, 0x7f, 0xe2, + 0x4f, 0xf2, 0xe7, 0xec, 0x09, 0xbd, 0xde, 0x31, 0x0f, 0x60, 0x0b, 0x7f, 0xa7, 0x7c, 0x56, 0xab, + 0x27, 0xcb, 0xa8, 0xb2, 0x7d, 0x20, 0xd1, 0x14, 0xed, 0x83, 0x5f, 0x00, 0xd8, 0xcf, 0xe3, 0xe4, + 0x8e, 0xcf, 0x27, 0x71, 0x54, 0x58, 0xae, 0xe2, 0x90, 0xb1, 0xb0, 0x5c, 0xc6, 0x49, 0x23, 0x7b, + 0x62, 0x78, 0x9c, 0xd6, 0xf9, 0xb5, 0x12, 0xae, 0x53, 0xcf, 0x21, 0xf5, 0x84, 0xd4, 0x9c, 0x45, + 0x5e, 0x77, 0xd0, 0x7f, 0x2c, 0xb2, 0x78, 0xda, 0x7f, 0xac, 0x24, 0x08, 0xb5, 0xd9, 0xab, 0x49, + 0xf9, 0xed, 0x41, 0xab, 0x48, 0x0b, 0xa9, 0x2d, 0xa9, 0x9c, 0x44, 0xd2, 0x7b, 0x4c, 0x25, 0x59, + 0xe3, 0xae, 0xd2, 0x54, 0x01, 0x5b, 0xc2, 0xa9, 0xa2, 0x0c, 0x4c, 0x00, 0xeb, 0xa2, 0x83, 0x7a, + 0xc3, 0xa4, 0x63, 0x33, 0x35, 0x92, 0x9a, 0x84, 0x89, 0xd6, 0xe6, 0xda, 0x7c, 0x83, 0x15, 0xdb, + 0xa1, 0xb4, 0x8a, 0x23, 0x3b, 0x34, 0xcd, 0x13, 0x58, 0xab, 0x04, 0xcb, 0x5a, 0xa5, 0x4f, 0xcb, + 0x51, 0x68, 0x95, 0x3e, 0x35, 0xce, 0x76, 0xcf, 0x52, 0x93, 0x2b, 0x2e, 0x60, 0x93, 0xd9, 0x49, + 0x90, 0x0f, 0x8f, 0xde, 0x73, 0xb6, 0x0e, 0x16, 0xe8, 0xcf, 0x55, 0x3e, 0xf7, 0x3f, 0x01, 0x00, + 0x00, 0xff, 0xff, 0x8f, 0xec, 0x5b, 0xef, 0x8e, 0x45, 0x00, 0x00, } diff --git a/lnrpc/rpc.proto b/lnrpc/rpc.proto index 8680b326..4f6760f1 100644 --- a/lnrpc/rpc.proto +++ b/lnrpc/rpc.proto @@ -1063,6 +1063,14 @@ message PendingChannelsResponse { int64 fee_per_kw = 6 [ json_name = "fee_per_kw" ]; } + message WaitingCloseChannel { + /// The pending channel waiting for closing tx to confirm + PendingChannel channel = 1; + + /// The balance in satoshis encumbered in this channel + int64 limbo_balance = 2 [ json_name = "limbo_balance" ]; + } + message ClosedChannel { /// The pending channel to be closed PendingChannel channel = 1; @@ -1108,6 +1116,9 @@ message PendingChannelsResponse { /// Channels pending force closing repeated ForceClosedChannel pending_force_closing_channels = 4 [ json_name = "pending_force_closing_channels" ]; + + /// Channels waiting for closing tx to confirm + repeated WaitingCloseChannel waiting_close_channels = 5 [ json_name = "waiting_close_channels" ]; } message WalletBalanceRequest { diff --git a/lnrpc/rpc.swagger.json b/lnrpc/rpc.swagger.json index 70bf8ab8..417a78ec 100644 --- a/lnrpc/rpc.swagger.json +++ b/lnrpc/rpc.swagger.json @@ -893,6 +893,20 @@ } } }, + "PendingChannelsResponseWaitingCloseChannel": { + "type": "object", + "properties": { + "channel": { + "$ref": "#/definitions/PendingChannelsResponsePendingChannel", + "title": "/ The pending channel waiting for closing tx to confirm" + }, + "limbo_balance": { + "type": "string", + "format": "int64", + "title": "/ The balance in satoshis encumbered in this channel" + } + } + }, "lnrpcAddInvoiceResponse": { "type": "object", "properties": { @@ -2035,6 +2049,13 @@ "$ref": "#/definitions/PendingChannelsResponseForceClosedChannel" }, "title": "/ Channels pending force closing" + }, + "waiting_close_channels": { + "type": "array", + "items": { + "$ref": "#/definitions/PendingChannelsResponseWaitingCloseChannel" + }, + "title": "/ Channels waiting for closing tx to confirm" } } }, From 97977c8a0617eb5bc1cd04eca403ee22f03e94b8 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Tue, 27 Mar 2018 14:25:46 +0200 Subject: [PATCH 27/31] rpcserver: add channels waiting for commitment confirmation to pending channels response --- rpcserver.go | 64 ++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/rpcserver.go b/rpcserver.go index b70f960d..e6e694d0 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -1343,18 +1343,24 @@ func (r *rpcServer) WalletBalance(ctx context.Context, func (r *rpcServer) ChannelBalance(ctx context.Context, in *lnrpc.ChannelBalanceRequest) (*lnrpc.ChannelBalanceResponse, error) { - channels, err := r.server.chanDB.FetchAllChannels() + openChannels, err := r.server.chanDB.FetchAllOpenChannels() if err != nil { return nil, err } - var pendingOpenBalance, balance btcutil.Amount - for _, channel := range channels { - if channel.IsPending { - pendingOpenBalance += channel.LocalCommitment.LocalBalance.ToSatoshis() - } else { - balance += channel.LocalCommitment.LocalBalance.ToSatoshis() - } + var balance btcutil.Amount + for _, channel := range openChannels { + balance += channel.LocalCommitment.LocalBalance.ToSatoshis() + } + + pendingChannels, err := r.server.chanDB.FetchPendingChannels() + if err != nil { + return nil, err + } + + var pendingOpenBalance btcutil.Amount + for _, channel := range pendingChannels { + pendingOpenBalance += channel.LocalCommitment.LocalBalance.ToSatoshis() } return &lnrpc.ChannelBalanceResponse{ @@ -1457,7 +1463,8 @@ func (r *rpcServer) PendingChannels(ctx context.Context, // If the channel was force closed, then we'll need to query // the utxoNursery for additional information. - case channeldb.ForceClose: + // TODO(halseth): distinguish remote and local case? + case channeldb.LocalForceClose, channeldb.RemoteForceClose: forceClose := &lnrpc.PendingChannelsResponse_ForceClosedChannel{ Channel: channel, ClosingTxid: closeTXID, @@ -1522,6 +1529,39 @@ func (r *rpcServer) PendingChannels(ctx context.Context, } } + // We'll also fetch all channels that are open, but have had their + // commitment broadcasted, meaning they are waiting for the closing + // transaction to confirm. + waitingCloseChans, err := r.server.chanDB.FetchWaitingCloseChannels() + if err != nil { + rpcsLog.Errorf("unable to fetch channels waiting close: %v", + err) + return nil, err + } + + for _, waitingClose := range waitingCloseChans { + pub := waitingClose.IdentityPub.SerializeCompressed() + chanPoint := waitingClose.FundingOutpoint + channel := &lnrpc.PendingChannelsResponse_PendingChannel{ + RemoteNodePub: hex.EncodeToString(pub), + ChannelPoint: chanPoint.String(), + Capacity: int64(waitingClose.Capacity), + LocalBalance: int64(waitingClose.LocalCommitment.LocalBalance.ToSatoshis()), + } + + // A close tx has been broadcasted, all our balance will be in + // limbo until it confirms. + resp.WaitingCloseChannels = append( + resp.WaitingCloseChannels, + &lnrpc.PendingChannelsResponse_WaitingCloseChannel{ + Channel: channel, + LimboBalance: channel.LocalBalance, + }, + ) + + resp.TotalLimboBalance += channel.LocalBalance + } + return resp, nil } @@ -1544,7 +1584,7 @@ func (r *rpcServer) ListChannels(ctx context.Context, graph := r.server.chanDB.ChannelGraph() - dbChannels, err := r.server.chanDB.FetchAllChannels() + dbChannels, err := r.server.chanDB.FetchAllOpenChannels() if err != nil { return nil, err } @@ -1553,10 +1593,6 @@ func (r *rpcServer) ListChannels(ctx context.Context, len(dbChannels)) for _, dbChannel := range dbChannels { - if dbChannel.IsPending { - continue - } - nodePub := dbChannel.IdentityPub nodeID := hex.EncodeToString(nodePub.SerializeCompressed()) chanPoint := dbChannel.FundingOutpoint From ddf62bbeea1459e22c3891f4e22fc2a66d945a18 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Thu, 12 Apr 2018 15:14:04 +0200 Subject: [PATCH 28/31] contractcourt/channel_arbitrator: remove unused transationTrigger --- contractcourt/channel_arbitrator.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/contractcourt/channel_arbitrator.go b/contractcourt/channel_arbitrator.go index 47448e0f..cd6fc2a9 100644 --- a/contractcourt/channel_arbitrator.go +++ b/contractcourt/channel_arbitrator.go @@ -551,7 +551,6 @@ func (c *ChannelArbitrator) stateStep(triggerHeight uint32, // outstanding contracts. htlcResolvers, pktsToSend, err := c.prepContractResolutions( chainActions, contractResolutions, triggerHeight, - trigger, ) if err != nil { log.Errorf("ChannelArbitrator(%v): unable to "+ @@ -933,7 +932,6 @@ func (c *ChannelArbitrator) checkChainActions(height uint32, // are properly resolved. func (c *ChannelArbitrator) prepContractResolutions(htlcActions ChainActionMap, contractResolutions *ContractResolutions, height uint32, - trigger transitionTrigger, ) ([]ContractResolver, []ResolutionMsg, error) { // There may be a class of HTLC's which we can fail back immediately, From 028d1b7e26116e532366db623f0554c620081770 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 13 Apr 2018 13:18:35 +0200 Subject: [PATCH 29/31] lnd_test: add more relevant info to error messages --- lnd_test.go | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/lnd_test.go b/lnd_test.go index 8ca60dd4..ef7506fc 100644 --- a/lnd_test.go +++ b/lnd_test.go @@ -112,7 +112,7 @@ func assertTxInBlock(t *harnessTest, block *wire.MsgBlock, txid *chainhash.Hash) } } - t.Fatalf("funding tx was not included in block") + t.Fatalf("tx was not included in block") } // mineBlocks mine 'num' of blocks and check that blocks are present in @@ -3828,7 +3828,7 @@ func waitForNTxsInMempool(miner *rpcclient.Client, n int, for { select { case <-breakTimeout: - return nil, fmt.Errorf("wanted %v, only found %v txs "+ + return nil, fmt.Errorf("wanted %v, found %v txs "+ "in mempool", n, len(mempool)) case <-ticker.C: mempool, err = miner.GetRawMempool() @@ -4649,7 +4649,13 @@ func assertNodeNumChannels(t *harnessTest, ctxb context.Context, // Return true if the query returned the expected number of // channels. - return len(chanInfo.Channels) == numChannels + num := len(chanInfo.Channels) + if num != numChannels { + predErr = fmt.Errorf("expected %v channels, got %v", + numChannels, num) + return false + } + return true } if err := lntest.WaitPredicate(pred, time.Second*15); err != nil { @@ -6033,7 +6039,7 @@ func testMultiHopHtlcLocalTimeout(net *lntest.NetworkHarness, t *harnessTest) { return true }, time.Second*15) if err != nil { - t.Fatalf("htlc mismatch: %v", err) + t.Fatalf("htlc mismatch: %v", predErr) } // TODO(roasbeef): need to fix utxn so it can accept incubation for @@ -6226,7 +6232,12 @@ func testMultiHopReceiverChainClaim(net *lntest.NetworkHarness, t *harnessTest) if err != nil { t.Fatalf("unable to get txid: %v", err) } + bobFundingTxid, err := chainhash.NewHash(txidHash) + if err != nil { + t.Fatalf("unable to create sha hash: %v", err) + } + carolFundingPoint := wire.OutPoint{ Hash: *bobFundingTxid, Index: bobChanPoint.OutputIndex, @@ -6642,7 +6653,7 @@ func testMultHopRemoteForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness, return true }, time.Second*15) if err != nil { - t.Fatalf("htlc mismatch: %v", err) + t.Fatalf("htlc mismatch: %v", predErr) } // At this point, we'll now instruct Carol to force close the @@ -6929,9 +6940,11 @@ func testMultiHopHtlcLocalChainClaim(net *lntest.NetworkHarness, t *harnessTest) "but doesn't") return false } - if forceCloseChan.PendingHtlcs[0].Stage != 1 { + stage := forceCloseChan.PendingHtlcs[0].Stage + if stage != 1 { predErr = fmt.Errorf("bob's htlc should have "+ - "advanced to the first stage: %v", err) + "advanced to the first stage but was "+ + "stage: %v", stage) return false } } From 8d5a33e3498f2380abcb7bbc85c6a31571d064ac Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 13 Apr 2018 13:20:12 +0200 Subject: [PATCH 30/31] lnd_test: modify tests to work with on-chain spend registrations This commit modifies the integration tests to work with the recent changes to the ChannelArbitrator, where it will only act on commitments that has been confirmed. Main changes involving when to look for transactions in the mempool and in blocks, and using the new RPC for getting channels in the "waiting close" phase when they are waiting for the commitment to confirm. --- lnd_test.go | 565 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 377 insertions(+), 188 deletions(-) diff --git a/lnd_test.go b/lnd_test.go index ef7506fc..af09964b 100644 --- a/lnd_test.go +++ b/lnd_test.go @@ -1109,8 +1109,17 @@ func testDisconnectingTargetPeer(net *lntest.NetworkHarness, t *harnessTest) { // Disconnect Alice-peer from Bob-peer without getting error // about existing channels. - if err := net.DisconnectNodes(ctxt, net.Alice, net.Bob); err != nil { - t.Fatalf("unable to disconnect Bob's peer from Alice's: err %v", err) + var predErr error + err = lntest.WaitPredicate(func() bool { + if err := net.DisconnectNodes(ctxt, net.Alice, net.Bob); err != nil { + predErr = err + return false + } + return true + }, time.Second*15) + if err != nil { + t.Fatalf("unable to disconnect Bob's peer from Alice's: err %v", + predErr) } // Check zero peer connections. @@ -1353,6 +1362,27 @@ func findForceClosedChannel(t *harnessTest, return forceClose } +// findWaitingCloseChannel searches a pending channel response for a particular +// channel, returning the waiting close channel upon success. +func findWaitingCloseChannel(t *harnessTest, + pendingChanResp *lnrpc.PendingChannelsResponse, + op *wire.OutPoint) *lnrpc.PendingChannelsResponse_WaitingCloseChannel { + + var found bool + var waitingClose *lnrpc.PendingChannelsResponse_WaitingCloseChannel + for _, waitingClose = range pendingChanResp.WaitingCloseChannels { + if waitingClose.Channel.ChannelPoint == op.String() { + found = true + break + } + } + if !found { + t.Fatalf("channel not marked as waiting close") + } + + return waitingClose +} + func assertCommitmentMaturity(t *harnessTest, forceClose *lnrpc.PendingChannelsResponse_ForceClosedChannel, maturityHeight uint32, blocksTilMaturity int32) { @@ -1394,6 +1424,18 @@ func assertNumForceClosedChannels(t *harnessTest, } } +// assertNumWaitingCloseChannels checks that a pending channel response has the +// expected number of channels waiting for closing tx to confirm. +func assertNumWaitingCloseChannels(t *harnessTest, + pendingChanResp *lnrpc.PendingChannelsResponse, expectedNumChans int) { + + if len(pendingChanResp.WaitingCloseChannels) != expectedNumChans { + t.Fatalf("expected to find %d channels waiting closure, got %d", + expectedNumChans, + len(pendingChanResp.WaitingCloseChannels)) + } +} + // assertPendingHtlcStageAndMaturity uniformly tests all pending htlc's // belonging to a force closed channel, testing for the expected stage number, // blocks till maturity, and the maturity height. @@ -1574,13 +1616,13 @@ func testChannelForceClosure(net *lntest.NetworkHarness, t *harnessTest) { } // Now that the channel has been force closed, it should show up in the - // PendingChannels RPC under the force close section. + // PendingChannels RPC under the waiting close section. pendingChansRequest := &lnrpc.PendingChannelsRequest{} pendingChanResp, err := net.Alice.PendingChannels(ctxb, pendingChansRequest) if err != nil { t.Fatalf("unable to query for pending channels: %v", err) } - assertNumForceClosedChannels(t, pendingChanResp, 1) + assertNumWaitingCloseChannels(t, pendingChanResp, 1) // Compute the outpoint of the channel, which we will use repeatedly to // locate the pending channel information in the rpc responses. @@ -1597,21 +1639,12 @@ func testChannelForceClosure(net *lntest.NetworkHarness, t *harnessTest) { Index: chanPoint.OutputIndex, } - forceClose := findForceClosedChannel(t, pendingChanResp, &op) + waitingClose := findWaitingCloseChannel(t, pendingChanResp, &op) - // Immediately after force closing, all of the funds should be in limbo, - // and the pending channels response should not indicate that any funds - // have been recovered. - if forceClose.LimboBalance == 0 { + // Immediately after force closing, all of the funds should be in limbo. + if waitingClose.LimboBalance == 0 { t.Fatalf("all funds should still be in limbo") } - if forceClose.RecoveredBalance != 0 { - t.Fatalf("no funds should yet be shown as recovered") - } - - // The commitment transaction has not been confirmed, so we expect to - // see a maturity height and blocks til maturity of 0. - assertCommitmentMaturity(t, forceClose, 0, 0) // The several restarts in this test are intended to ensure that when a // channel is force-closed, the UTXO nursery has persisted the state of @@ -1639,13 +1672,15 @@ func testChannelForceClosure(net *lntest.NetworkHarness, t *harnessTest) { duration := time.Millisecond * 300 time.Sleep(duration) + // Now that the commitment has been confirmed, the channel should be + // marked as force closed. pendingChanResp, err = net.Alice.PendingChannels(ctxb, pendingChansRequest) if err != nil { t.Fatalf("unable to query for pending channels: %v", err) } assertNumForceClosedChannels(t, pendingChanResp, 1) - forceClose = findForceClosedChannel(t, pendingChanResp, &op) + forceClose := findForceClosedChannel(t, pendingChanResp, &op) // Now that the channel has been force closed, it should now have the // height and number of blocks to confirm populated. @@ -4224,11 +4259,24 @@ func testRevokedCloseRetributionZeroValueRemoteOutput(net *lntest.NetworkHarness // commitment transaction of a prior *revoked* state, so he'll soon // feel the wrath of Alice's retribution. force := true - closeUpdates, _, err := net.CloseChannel(ctxb, carol, chanPoint, force) + closeUpdates, closeTxId, err := net.CloseChannel(ctxb, carol, + chanPoint, force) if err != nil { t.Fatalf("unable to close channel: %v", err) } + // Query the mempool for the breaching closing transaction, this should + // be broadcast by Carol when she force closes the channel above. + txid, err := waitForTxInMempool(net.Miner.Node, 20*time.Second) + if err != nil { + t.Fatalf("unable to find Carol's force close tx in mempool: %v", + err) + } + if *txid != *closeTxId { + t.Fatalf("expected closeTx(%v) in mempool, instead found %v", + closeTxId, txid) + } + // Finally, generate a single block, wait for the final close status // update, then ensure that the closing transaction was included in the // block. @@ -4545,17 +4593,22 @@ func testRevokedCloseRetributionRemoteHodl(net *lntest.NetworkHarness, // commitment transaction of a prior *revoked* state, so she'll soon // feel the wrath of Dave's retribution. force := true - closeUpdates, _, err := net.CloseChannel(ctxb, carol, chanPoint, force) + closeUpdates, closeTxId, err := net.CloseChannel(ctxb, carol, + chanPoint, force) if err != nil { t.Fatalf("unable to close channel: %v", err) } - // Query the mempool for Dave's justice transaction, this should be - // broadcast as Carol's contract breaching transaction gets confirmed - // above. - _, err = waitForTxInMempool(net.Miner.Node, 5*time.Second) + // Query the mempool for the breaching closing transaction, this should + // be broadcast by Carol when she force closes the channel above. + txid, err := waitForTxInMempool(net.Miner.Node, 20*time.Second) if err != nil { - t.Fatalf("unable to find Dave's justice tx in mempool: %v", err) + t.Fatalf("unable to find Carol's force close tx in mempool: %v", + err) + } + if *txid != *closeTxId { + t.Fatalf("expected closeTx(%v) in mempool, instead found %v", + closeTxId, txid) } time.Sleep(200 * time.Millisecond) @@ -4577,14 +4630,101 @@ func testRevokedCloseRetributionRemoteHodl(net *lntest.NetworkHarness, if err != nil { t.Fatalf("error while waiting for channel close: %v", err) } + if *breachTXID != *closeTxId { + t.Fatalf("expected breach ID(%v) to be equal to close ID (%v)", + breachTXID, closeTxId) + } assertTxInBlock(t, block, breachTXID) // Query the mempool for Dave's justice transaction, this should be // broadcast as Carol's contract breaching transaction gets confirmed - // above. - justiceTXID, err := waitForTxInMempool(net.Miner.Node, 5*time.Second) + // above. Since Carol might have had the time to take some of the HTLC + // outputs to the second level before Alice broadcasts her justice tx, + // we'll search through the mempool for a tx that matches the number of + // expected inputs in the justice tx. + // TODO(halseth): change to deterministic check if/when only acting on + // confirmed second level spends? + var predErr error + var justiceTxid *chainhash.Hash + err = lntest.WaitPredicate(func() bool { + mempool, err := net.Miner.Node.GetRawMempool() + if err != nil { + t.Fatalf("unable to get mempool from miner: %v", err) + } + + for _, txid := range mempool { + // Check that the justice tx has the appropriate number + // of inputs. + tx, err := net.Miner.Node.GetRawTransaction(txid) + if err != nil { + predErr = fmt.Errorf("unable to query for "+ + "txs: %v", err) + return false + } + + exNumInputs := 2 + numInvoices + if len(tx.MsgTx().TxIn) == exNumInputs { + justiceTxid = txid + return true + } + + } + + predErr = fmt.Errorf("justice tx not found") + return false + }, time.Second*15) if err != nil { - t.Fatalf("unable to find Dave's justice tx in mempool: %v", err) + t.Fatalf(predErr.Error()) + } + + justiceTx, err := net.Miner.Node.GetRawTransaction(justiceTxid) + if err != nil { + t.Fatalf("unable to query for justice tx: %v", err) + } + + // isSecondLevelSpend checks that the passed secondLevelTxid is a + // potentitial second level spend spending from the commit tx. + isSecondLevelSpend := func(commitTxid, secondLevelTxid *chainhash.Hash) bool { + secondLevel, err := net.Miner.Node.GetRawTransaction( + secondLevelTxid) + if err != nil { + t.Fatalf("unable to query for tx: %v", err) + } + + // A second level spend should have only one input, and one + // output. + if len(secondLevel.MsgTx().TxIn) != 1 { + return false + } + if len(secondLevel.MsgTx().TxOut) != 1 { + return false + } + + // The sole input should be spending from the commit tx. + txIn := secondLevel.MsgTx().TxIn[0] + if !bytes.Equal(txIn.PreviousOutPoint.Hash[:], commitTxid[:]) { + return false + } + + return true + } + + // Check that all the inputs of this transaction are spending outputs + // generated by Carol's breach transaction above. + for _, txIn := range justiceTx.MsgTx().TxIn { + if bytes.Equal(txIn.PreviousOutPoint.Hash[:], breachTXID[:]) { + continue + } + + // If the justice tx is spending from an output that was not on + // the breach tx, Carol might have had the time to take an + // output to the second level. In that case, check that the + // justice tx is spending this second level output. + if isSecondLevelSpend(breachTXID, &txIn.PreviousOutPoint.Hash) { + continue + } + t.Fatalf("justice tx not spending commitment utxo "+ + "instead is: %v", txIn.PreviousOutPoint) } time.Sleep(100 * time.Millisecond) @@ -4597,36 +4737,12 @@ func testRevokedCloseRetributionRemoteHodl(net *lntest.NetworkHarness, t.Fatalf("unable to restart Dave's node: %v", err) } - // Query for the mempool transaction found above. Then assert that (1) - // the justice tx has the appropriate number of inputs, and (2) all the - // inputs of this transaction are spending outputs generated by Carol's - // breach transaction above, and also the HTLCs from Carol to Dave. - justiceTx, err := net.Miner.Node.GetRawTransaction(justiceTXID) - if err != nil { - t.Fatalf("unable to query for justice tx: %v", err) - } - exNumInputs := 2 + numInvoices - if len(justiceTx.MsgTx().TxIn) != exNumInputs { - t.Fatalf("justice tx should have exactly 2 commitment inputs"+ - "and %v htlc inputs, expected %v in total, got %v", - numInvoices/2, exNumInputs, - len(justiceTx.MsgTx().TxIn)) - } - // Now mine a block, this transaction should include Dave's justice // transaction which was just accepted into the mempool. block = mineBlocks(t, net, 1)[0] + assertTxInBlock(t, block, justiceTxid) - // The block should have exactly *two* transactions, one of which is - // the justice transaction. - if len(block.Transactions) != 2 { - t.Fatalf("transaction wasn't mined") - } - justiceSha := block.Transactions[1].TxHash() - if !bytes.Equal(justiceTx.Hash()[:], justiceSha[:]) { - t.Fatalf("justice tx wasn't mined") - } - + // Dave should have no open channels. assertNodeNumChannels(t, ctxb, dave, 0) } @@ -6026,6 +6142,9 @@ func testMultiHopHtlcLocalTimeout(net *lntest.NetworkHarness, t *harnessTest) { }, ) + // Mine a block to confirm the closing transaction. + mineBlocks(t, net, 1) + // At this point, Bob should have cancelled backwards the dust HTLC // that we sent earlier. This means Alice should now only have a single // HTLC on her channel. @@ -6056,7 +6175,7 @@ func testMultiHopHtlcLocalTimeout(net *lntest.NetworkHarness, t *harnessTest) { // The second layer HTLC timeout transaction should now have been // broadcast on-chain. - _, err = waitForTxInMempool(net.Miner.Node, time.Second*10) + secondLayerHash, err := waitForTxInMempool(net.Miner.Node, time.Second*10) if err != nil { t.Fatalf("unable to find bob's second layer transaction") } @@ -6083,13 +6202,12 @@ func testMultiHopHtlcLocalTimeout(net *lntest.NetworkHarness, t *harnessTest) { } // Now we'll mine an additional block. - if _, err := net.Miner.Node.Generate(1); err != nil { - t.Fatalf("unable to generate blocks: %v", err) - } + block := mineBlocks(t, net, 1)[0] // The block should have confirmed Bob's second layer sweeping // transaction. Therefore, at this point, there should be no active // HTLC's on the commitment transaction from Alice -> Bob. + assertTxInBlock(t, block, secondLayerHash) nodes = []*lntest.HarnessNode{net.Alice} err = lntest.WaitPredicate(func() bool { return assertNumActiveHtlcs(nodes, 0) @@ -6222,12 +6340,11 @@ func testMultiHopReceiverChainClaim(net *lntest.NetworkHarness, t *harnessTest) // At this point, Carol should broadcast her active commitment // transaction in order to go to the chain and sweep her HTLC. - // Additionally, Carol's should have broadcast her second layer sweep - // transaction for the HTLC as well. - txids, err := waitForNTxsInMempool(net.Miner.Node, 2, time.Second*15) + txids, err := waitForNTxsInMempool(net.Miner.Node, 1, time.Second*20) if err != nil { - t.Fatalf("transactions not found in mempool: %v", err) + t.Fatalf("expected transaction not found in mempool: %v", err) } + txidHash, err := getChanPointFundingTxid(bobChanPoint) if err != nil { t.Fatalf("unable to get txid: %v", err) @@ -6243,42 +6360,53 @@ func testMultiHopReceiverChainClaim(net *lntest.NetworkHarness, t *harnessTest) Index: bobChanPoint.OutputIndex, } - tx1, err := net.Miner.Node.GetRawTransaction(txids[0]) + // The commitment transaction should be spending from the funding + // transaction. + commitHash := txids[0] + tx, err := net.Miner.Node.GetRawTransaction(commitHash) if err != nil { t.Fatalf("unable to get txn: %v", err) } - tx1Hash := tx1.MsgTx().TxHash() - tx2, err := net.Miner.Node.GetRawTransaction(txids[1]) - if err != nil { - t.Fatalf("unable to get txn: %v", err) - } - tx2Hash := tx2.MsgTx().TxHash() + commitTx := tx.MsgTx() - // Of the two transactions, one should be spending from the funding - // transaction, and the second transaction should then be spending from + if commitTx.TxIn[0].PreviousOutPoint != carolFundingPoint { + t.Fatalf("commit transaction not spending from expected "+ + "outpoint: %v", spew.Sdump(commitTx)) + } + + // Confirm the commitment. + mineBlocks(t, net, 1) + + // After the force close transaction is mined, Carol should broadcast + // her second level HTLC transaction. Bob will broadcast a sweep tx to + // sweep his output in the channel with Carol. When Bob notices Carol's + // second level transaction in the mempool, he will extract the + // preimage and settle the HTLC back off-chain. + secondLevelHashes, err := waitForNTxsInMempool(net.Miner.Node, 2, + time.Second*15) + if err != nil { + t.Fatalf("transactions not found in mempool: %v", err) + } + + // Carol's second level transaction should be spending from // the commitment transaction. - var commitHash *chainhash.Hash - if tx1.MsgTx().TxIn[0].PreviousOutPoint == carolFundingPoint { - commitHash = &tx1Hash - if tx2.MsgTx().TxIn[0].PreviousOutPoint.Hash != *commitHash { - t.Fatalf("second transaction not spending commit tx: %v", - spew.Sdump(tx2)) + var secondLevelHash *chainhash.Hash + for _, txid := range secondLevelHashes { + tx, err := net.Miner.Node.GetRawTransaction(txid) + if err != nil { + t.Fatalf("unable to get txn: %v", err) + } + + if tx.MsgTx().TxIn[0].PreviousOutPoint.Hash == *commitHash { + secondLevelHash = txid } } - if tx2.MsgTx().TxIn[0].PreviousOutPoint == carolFundingPoint { - commitHash = &tx2Hash - if tx1.MsgTx().TxIn[0].PreviousOutPoint.Hash != *commitHash { - t.Fatalf("second transaction not spending commit tx: %v", - spew.Sdump(tx1)) - } - } - if commitHash == nil { - t.Fatalf("commit tx not found in mempool") + if secondLevelHash == nil { + t.Fatalf("Carol's second level tx not found") } // We'll now mine an additional block which should confirm both the - // second layer transaction as well as the commitment transaction - // itself. + // second layer transactions. if _, err := net.Miner.Node.Generate(1); err != nil { t.Fatalf("unable to generate block: %v", err) } @@ -6438,17 +6566,32 @@ func testMultiHopLocalForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness, // At this point, Bob should have a pending force close channel as he // just went to chain. pendingChansRequest := &lnrpc.PendingChannelsRequest{} - pendingChanResp, err := net.Bob.PendingChannels(ctxb, pendingChansRequest) + err = lntest.WaitPredicate(func() bool { + pendingChanResp, err := net.Bob.PendingChannels(ctxb, + pendingChansRequest) + if err != nil { + predErr = fmt.Errorf("unable to query for pending "+ + "channels: %v", err) + return false + } + if len(pendingChanResp.PendingForceClosingChannels) == 0 { + predErr = fmt.Errorf("bob should have pending for " + + "close chan but doesn't") + return false + } + + forceCloseChan := pendingChanResp.PendingForceClosingChannels[0] + if forceCloseChan.LimboBalance == 0 { + predErr = fmt.Errorf("bob should have nonzero limbo "+ + "balance instead has: %v", + forceCloseChan.LimboBalance) + return false + } + + return true + }, time.Second*15) if err != nil { - t.Fatalf("unable to query for pending channels: %v", err) - } - if len(pendingChanResp.PendingForceClosingChannels) == 0 { - t.Fatalf("bob should have pending for close chan but doesn't") - } - forceCloseChan := pendingChanResp.PendingForceClosingChannels[0] - if forceCloseChan.LimboBalance == 0 { - t.Fatalf("bob should have nonzero limbo balance instead "+ - "has: %v", forceCloseChan.LimboBalance) + t.Fatalf(predErr.Error()) } // We'll now mine enough blocks for the HTLC to expire. After this, Bob @@ -6470,12 +6613,12 @@ func testMultiHopLocalForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness, } if len(pendingChanResp.PendingForceClosingChannels) == 0 { - predErr = fmt.Errorf("bob should have pending for " + + predErr = fmt.Errorf("bob should have pending force " + "close chan but doesn't") return false } - forceCloseChan = pendingChanResp.PendingForceClosingChannels[0] + forceCloseChan := pendingChanResp.PendingForceClosingChannels[0] if len(forceCloseChan.PendingHtlcs) != 1 { predErr = fmt.Errorf("bob should have pending htlc " + "but doesn't") @@ -6534,7 +6677,7 @@ func testMultiHopLocalForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness, return false } - forceCloseChan = pendingChanResp.PendingForceClosingChannels[0] + forceCloseChan := pendingChanResp.PendingForceClosingChannels[0] if len(forceCloseChan.PendingHtlcs) != 1 { predErr = fmt.Errorf("bob should have pending htlc " + "but doesn't") @@ -6570,7 +6713,7 @@ func testMultiHopLocalForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness, // At this point, Bob should no longer show any channels as pending // close. err = lntest.WaitPredicate(func() bool { - pendingChanResp, err = net.Bob.PendingChannels( + pendingChanResp, err := net.Bob.PendingChannels( ctxb, pendingChansRequest, ) if err != nil { @@ -6665,12 +6808,25 @@ func testMultHopRemoteForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness, // At this point, Bob should have a pending force close channel as // Carol has gone directly to chain. pendingChansRequest := &lnrpc.PendingChannelsRequest{} - pendingChanResp, err := net.Bob.PendingChannels(ctxb, pendingChansRequest) + err = lntest.WaitPredicate(func() bool { + pendingChanResp, err := net.Bob.PendingChannels( + ctxb, pendingChansRequest, + ) + if err != nil { + predErr = fmt.Errorf("unable to query for "+ + "pending channels: %v", err) + return false + } + if len(pendingChanResp.PendingForceClosingChannels) == 0 { + predErr = fmt.Errorf("bob should have pending " + + "force close channels but doesn't") + return false + } + + return true + }, time.Second*15) if err != nil { - t.Fatalf("unable to query for pending channels: %v", err) - } - if len(pendingChanResp.PendingForceClosingChannels) == 0 { - t.Fatalf("bob should have pending for close chan but doesn't") + t.Fatalf(predErr.Error()) } // Next, we'll mine enough blocks for the HTLC to expire. At this @@ -6750,7 +6906,7 @@ func testMultHopRemoteForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness, // commitment, he doesn't have to wait for any CSV delays. As a result, // he should show no additional pending transactions. err = lntest.WaitPredicate(func() bool { - pendingChanResp, err = net.Bob.PendingChannels( + pendingChanResp, err := net.Bob.PendingChannels( ctxb, pendingChansRequest, ) if err != nil { @@ -6846,9 +7002,8 @@ func testMultiHopHtlcLocalChainClaim(net *lntest.NetworkHarness, t *harnessTest) t.Fatalf("unable to generate blocks") } - // Carol's commitment transaction should now be in the mempool. She - // should also have broadcast her second level HTLC transaction. - txids, err := waitForNTxsInMempool(net.Miner.Node, 2, time.Second*15) + // Carol's commitment transaction should now be in the mempool. + txids, err := waitForNTxsInMempool(net.Miner.Node, 1, time.Second*15) if err != nil { t.Fatalf("transactions not found in mempool: %v", err) } @@ -6865,50 +7020,52 @@ func testMultiHopHtlcLocalChainClaim(net *lntest.NetworkHarness, t *harnessTest) Index: bobChanPoint.OutputIndex, } - // Of the two transactions, one should be spending from the funding - // transaction, and the second transaction should then be spending from + // The tx should be spending from the funding transaction, + commitHash := txids[0] + tx1, err := net.Miner.Node.GetRawTransaction(commitHash) + if err != nil { + t.Fatalf("unable to get txn: %v", err) + } + if tx1.MsgTx().TxIn[0].PreviousOutPoint != carolFundingPoint { + t.Fatalf("commit transaction not spending fundingtx: %v", + spew.Sdump(tx1)) + } + + // Mine a block that should confirm the commit tx. + block := mineBlocks(t, net, 1)[0] + if len(block.Transactions) != 2 { + t.Fatalf("expected 2 transactions in block, got %v", + len(block.Transactions)) + } + assertTxInBlock(t, block, commitHash) + + // After the force close transacion is mined, Carol should broadcast + // her second level HTLC transacion. Bob will braodcast a sweep tx to + // sweep his output in the channel with Carol. When Bob notices Carol's + // second level transaction in the mempool, he will extract the + // preimage and broadcast a second level tx to claim the HTLC in his + // (already closed) channel with Alice. + secondLevelHashes, err := waitForNTxsInMempool(net.Miner.Node, 3, + time.Second*20) + if err != nil { + t.Fatalf("transactions not found in mempool: %v", err) + } + + // Carol's second level transaction should be spending from // the commitment transaction. - var commitHash *chainhash.Hash - tx1, err := net.Miner.Node.GetRawTransaction(txids[0]) - if err != nil { - t.Fatalf("unable to get txn: %v", err) - } - tx1Hash := tx1.MsgTx().TxHash() - tx2, err := net.Miner.Node.GetRawTransaction(txids[1]) - if err != nil { - t.Fatalf("unable to get txn: %v", err) - } - tx2Hash := tx2.MsgTx().TxHash() - if tx1.MsgTx().TxIn[0].PreviousOutPoint == carolFundingPoint { - commitHash = &tx1Hash - if tx2.MsgTx().TxIn[0].PreviousOutPoint.Hash != *commitHash { - t.Fatalf("second transaction not spending commit tx: %v", - spew.Sdump(tx2)) + var secondLevelHash *chainhash.Hash + for _, txid := range secondLevelHashes { + tx, err := net.Miner.Node.GetRawTransaction(txid) + if err != nil { + t.Fatalf("unable to get txn: %v", err) + } + + if tx.MsgTx().TxIn[0].PreviousOutPoint.Hash == *commitHash { + secondLevelHash = txid } } - if tx2.MsgTx().TxIn[0].PreviousOutPoint == carolFundingPoint { - commitHash = &tx2Hash - if tx1.MsgTx().TxIn[0].PreviousOutPoint.Hash != *commitHash { - t.Fatalf("second transaction not spending commit tx: %v", - spew.Sdump(tx1)) - } - } - if commitHash == nil { - t.Fatalf("commit tx not found in mempool") - } - - // We'll now mine a block which should confirm both the second layer - // transaction as well as the commitment transaction. - if _, err := net.Miner.Node.Generate(1); err != nil { - t.Fatalf("unable to generate block: %v", err) - } - - // At this point, Bob should detect that Carol has revealed the - // preimage on-chain. As a result, he should now attempt to broadcast - // his second-layer claim transaction to claim the output. - _, err = waitForTxInMempool(net.Miner.Node, time.Second*10) - if err != nil { - t.Fatalf("unable to find bob's sweeping transaction") + if secondLevelHash == nil { + t.Fatalf("Carol's second level tx not found") } // At this point, Bob should have broadcast his second layer success @@ -6955,6 +7112,15 @@ func testMultiHopHtlcLocalChainClaim(net *lntest.NetworkHarness, t *harnessTest) t.Fatalf("bob didn't hand off time-locked HTLC: %v", predErr) } + // We'll now mine a block which should confirm the two second layer + // transactions and the commit sweep. + block = mineBlocks(t, net, 1)[0] + if len(block.Transactions) != 4 { + t.Fatalf("expected 4 transactions in block, got %v", + len(block.Transactions)) + } + assertTxInBlock(t, block, secondLevelHash) + // If we then mine 4 additional blocks, Bob should pull the output // destined for him. if _, err := net.Miner.Node.Generate(defaultCSV); err != nil { @@ -7007,6 +7173,8 @@ func testMultiHopHtlcRemoteChainClaim(net *lntest.NetworkHarness, t *harnessTest timeout := time.Duration(time.Second * 15) ctxb := context.Background() + defaultCSV := uint32(4) + // First, we'll create a three hop network: Alice -> Bob -> Carol, with // Carol refusing to actually settle or directly cancel any HTLC's // self. @@ -7065,9 +7233,8 @@ func testMultiHopHtlcRemoteChainClaim(net *lntest.NetworkHarness, t *harnessTest t.Fatalf("unable to generate blocks") } - // Carol's commitment transaction should now be in the mempool. She - // should also have broadcast her second level HTLC transaction. - txids, err := waitForNTxsInMempool(net.Miner.Node, 2, time.Second*15) + // Carol's commitment transaction should now be in the mempool. + txids, err := waitForNTxsInMempool(net.Miner.Node, 1, time.Second*15) if err != nil { t.Fatalf("transactions not found in mempool: %v", err) } @@ -7084,48 +7251,70 @@ func testMultiHopHtlcRemoteChainClaim(net *lntest.NetworkHarness, t *harnessTest Index: bobChanPoint.OutputIndex, } - // Of the two transactions, one should be spending from the funding - // transaction, and the second transaction should then be spending from - // the commitment transaction. - var commitHash *chainhash.Hash - tx1, err := net.Miner.Node.GetRawTransaction(txids[0]) + // The transaction should be spending from the funding transaction + commitHash := txids[0] + tx1, err := net.Miner.Node.GetRawTransaction(commitHash) if err != nil { t.Fatalf("unable to get txn: %v", err) } - tx1Hash := tx1.MsgTx().TxHash() - tx2, err := net.Miner.Node.GetRawTransaction(txids[1]) - if err != nil { - t.Fatalf("unable to get txn: %v", err) - } - tx2Hash := tx2.MsgTx().TxHash() - if tx1.MsgTx().TxIn[0].PreviousOutPoint == carolFundingPoint { - commitHash = &tx1Hash - if tx2.MsgTx().TxIn[0].PreviousOutPoint.Hash != *commitHash { - t.Fatalf("second transaction not spending commit tx: %v", - spew.Sdump(tx2)) - } - } - if tx2.MsgTx().TxIn[0].PreviousOutPoint == carolFundingPoint { - commitHash = &tx2Hash - if tx1.MsgTx().TxIn[0].PreviousOutPoint.Hash != *commitHash { - t.Fatalf("second transaction not spending commit tx: %v", - spew.Sdump(tx1)) - } - } - if commitHash == nil { - t.Fatalf("commit tx not found in mempool") + if tx1.MsgTx().TxIn[0].PreviousOutPoint != carolFundingPoint { + t.Fatalf("commit transaction not spending fundingtx: %v", + spew.Sdump(tx1)) } - // We'll now mine a block which should confirm both the second layer - // transaction as well as the commitment transaction. - if _, err := net.Miner.Node.Generate(1); err != nil { + // Mine a block, which should contain the commitment. + block := mineBlocks(t, net, 1)[0] + if len(block.Transactions) != 2 { + t.Fatalf("expected 2 transactions in block, got %v", + len(block.Transactions)) + } + assertTxInBlock(t, block, commitHash) + + // After the force close transacion is mined, Carol should broadcast + // her second level HTLC transacion. Bob will braodcast a sweep tx to + // sweep his output in the channel with Carol. When Bob notices Carol's + // second level transaction in the mempool, he will extract the + // preimage and broadcast a second level tx to claim the HTLC in his + // (already closed) channel with Alice. + secondLevelHashes, err := waitForNTxsInMempool(net.Miner.Node, 3, + time.Second*20) + if err != nil { + t.Fatalf("transactions not found in mempool: %v", err) + } + + // Carol's second level transaction should be spending from + // the commitment transaction. + var secondLevelHash *chainhash.Hash + for _, txid := range secondLevelHashes { + tx, err := net.Miner.Node.GetRawTransaction(txid) + if err != nil { + t.Fatalf("unable to get txn: %v", err) + } + + if tx.MsgTx().TxIn[0].PreviousOutPoint.Hash == *commitHash { + secondLevelHash = txid + } + } + if secondLevelHash == nil { + t.Fatalf("Carol's second level tx not found") + } + + // We'll now mine a block which should confirm the two second layer + // transactions and the commit sweep. + block = mineBlocks(t, net, 1)[0] + if len(block.Transactions) != 4 { + t.Fatalf("expected 4 transactions in block, got %v", + len(block.Transactions)) + } + assertTxInBlock(t, block, secondLevelHash) + + // If we then mine 4 additional blocks, Bob should pull the output + // destined for him. + if _, err := net.Miner.Node.Generate(defaultCSV); err != nil { t.Fatalf("unable to generate block: %v", err) } - // With the block mined above, Bob should detect that Carol is - // attempting to sweep the HTLC on-chain, and should obtain the - // preimage. - _, err = waitForNTxsInMempool(net.Miner.Node, 2, time.Second*15) + _, err = waitForNTxsInMempool(net.Miner.Node, 1, time.Second*15) if err != nil { t.Fatalf("unable to find bob's sweeping transaction") } From 432042111036acf252e30e26cb6fe46938401667 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Mon, 16 Apr 2018 15:21:21 +0200 Subject: [PATCH 31/31] contractcourt/chain_watcher: don't delete syncDispatch clients on Cancel() This commit makes clients subscribing to channel events that are marked "sync dispatch" _not_ being deleted from the list of clients when they call Cancel(). Instead a go routine will be launched that will send an error on every read of the ProcessACK channel. This fixes a race in handing off the breach info while lnd was shutting down. The breach arbiter could end up being shut down (and calling Cancel()) before while the ChainWatcher was in the process of dispatching a breach. Since the breach arbiter no longer was among the registered clients at this point, the ChainWatcher would assume the breach was handed off successfully, and mark the channel as pending closed. When lnd now was restarted, the breach arbiter would not know about the breach, and the ChainWatcher wouldn't attempt to re-dispatch, as it was already marked as pending closed. --- contractcourt/chain_watcher.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/contractcourt/chain_watcher.go b/contractcourt/chain_watcher.go index e4d1ac40..ffd632b6 100644 --- a/contractcourt/chain_watcher.go +++ b/contractcourt/chain_watcher.go @@ -251,6 +251,30 @@ func (c *chainWatcher) SubscribeChannelEvents(syncDispatch bool) *ChainEventSubs if syncDispatch { sub.ProcessACK = make(chan error, 1) + + // If this client is syncDispatch, we cannot safely delete it + // from our list of clients. This is because of a potential + // race at shutdown, where the client shuts down and calls + // Cancel(). In this case we must make sure the ChainWatcher + // doesn't think it has successfully handed off a contract + // breach to the client. We start a goroutine that will send an + // error on the ProcessACK channel until the ChainWatcher is + // shutdown. + sub.Cancel = func() { + c.wg.Add(1) + go func() { + defer c.wg.Done() + + err := fmt.Errorf("cancelled") + for { + select { + case sub.ProcessACK <- err: + case <-c.quit: + return + } + } + }() + } } c.Lock()