Merge pull request #384 from cfromknecht/utxn-incubate-2nd-layer-htlcs

Utxo Nursery, adds outgoing 2nd-layer HTLC persistence
This commit is contained in:
Olaoluwa Osuntokun 2017-11-16 19:00:40 -08:00 committed by GitHub
commit caec23a236
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 3446 additions and 1367 deletions

@ -753,6 +753,13 @@ func testDisconnectingTargetPeer(net *networkHarness, t *harnessTest) {
// Check existing connection.
assertNumConnections(ctxb, t, net.Alice, net.Bob, 1)
// Mine enough blocks to clear the force closed outputs from the UTXO
// nursery.
if _, err := net.Miner.Node.Generate(4); err != nil {
t.Fatalf("unable to mine blocks: %v", err)
}
time.Sleep(300 * time.Millisecond)
}
// testFundingPersistence is intended to ensure that the Funding Manager
@ -962,57 +969,248 @@ func testChannelBalance(net *networkHarness, t *harnessTest) {
closeChannelAndAssert(ctx, t, net, net.Alice, chanPoint, false)
}
// findForceClosedChannel searches a pending channel response for a particular
// channel, returning the force closed channel upon success.
func findForceClosedChannel(t *harnessTest,
pendingChanResp *lnrpc.PendingChannelResponse,
op *wire.OutPoint) *lnrpc.PendingChannelResponse_ForceClosedChannel {
var found bool
var forceClose *lnrpc.PendingChannelResponse_ForceClosedChannel
for _, forceClose = range pendingChanResp.PendingForceClosingChannels {
if forceClose.Channel.ChannelPoint == op.String() {
found = true
break
}
}
if !found {
t.Fatalf("channel not marked as force closed")
}
return forceClose
}
func assertCommitmentMaturity(t *harnessTest,
forceClose *lnrpc.PendingChannelResponse_ForceClosedChannel,
maturityHeight uint32, blocksTilMaturity int32) {
if forceClose.MaturityHeight != maturityHeight {
t.Fatalf("expected commitment maturity height to be %d, "+
"found %d instead", maturityHeight,
forceClose.MaturityHeight)
}
if forceClose.BlocksTilMaturity != blocksTilMaturity {
t.Fatalf("expected commitment blocks til maturity to be %d, "+
"found %d instead", blocksTilMaturity,
forceClose.BlocksTilMaturity)
}
}
// assertForceClosedChannelNumHtlcs verifies that a force closed channel has the
// proper number of htlcs.
func assertPendingChannelNumHtlcs(t *harnessTest,
forceClose *lnrpc.PendingChannelResponse_ForceClosedChannel,
expectedNumHtlcs int) {
if len(forceClose.PendingHtlcs) != expectedNumHtlcs {
t.Fatalf("expected force closed channel to have %d pending "+
"htlcs, found %d instead", expectedNumHtlcs,
len(forceClose.PendingHtlcs))
}
}
// assertNumForceClosedChannels checks that a pending channel response has the
// expected number of force closed channels.
func assertNumForceClosedChannels(t *harnessTest,
pendingChanResp *lnrpc.PendingChannelResponse, expectedNumChans int) {
if len(pendingChanResp.PendingForceClosingChannels) != expectedNumChans {
t.Fatalf("expected to find %d force closed channels, got %d",
expectedNumChans,
len(pendingChanResp.PendingForceClosingChannels))
}
}
// assertPendingHtlcStageAndMaturity uniformly tests all pending htlc's
// belonging to a force closed channel, testing for the expeced stage number,
// blocks till maturity, and the maturity height.
func assertPendingHtlcStageAndMaturity(t *harnessTest,
forceClose *lnrpc.PendingChannelResponse_ForceClosedChannel,
stage, maturityHeight uint32, blocksTillMaturity int32) {
for _, pendingHtlc := range forceClose.PendingHtlcs {
if pendingHtlc.Stage != stage {
t.Fatalf("expected pending htlc to be stage %d, "+
"found %d", stage, pendingHtlc.Stage)
}
if pendingHtlc.MaturityHeight != maturityHeight {
t.Fatalf("expected pending htlc maturity height to be "+
"%d, instead has %d", maturityHeight,
pendingHtlc.MaturityHeight)
}
if pendingHtlc.BlocksTilMaturity != blocksTillMaturity {
t.Fatalf("expected pending htlc blocks til maturity "+
"to be %d, instead has %d", blocksTillMaturity,
pendingHtlc.BlocksTilMaturity)
}
}
}
// testChannelForceClosure performs a test to exercise the behavior of "force"
// closing a channel or unilaterally broadcasting the latest local commitment
// state on-chain. The test creates a new channel between Alice and Bob, then
// force closes the channel after some cursory assertions. Within the test, two
// transactions should be broadcast on-chain, the commitment transaction itself
// (which closes the channel), and the sweep transaction a few blocks later
// once the output(s) become mature. This test also includes several restarts
// to ensure that the transaction output states are persisted throughout
// the forced closure process.
// state on-chain. The test creates a new channel between Alice and Carol, then
// force closes the channel after some cursory assertions. Within the test, a
// total of 3 + n transactions will be broadcast, representing the commitment
// transaction, a transaction sweeping the local CSV delayed output, a
// transaction sweeping the CSV delayed 2nd-layer htlcs outputs, and n
// htlc success transactions, where n is the number of payments Alice attempted
// to send to Carol. This test includes several restarts to ensure that the
// transaction output states are persisted throughout the forced closure
// process.
//
// TODO(roasbeef): also add an unsettled HTLC before force closing.
func testChannelForceClosure(net *networkHarness, t *harnessTest) {
timeout := time.Duration(time.Second * 10)
ctxb := context.Background()
const (
timeout = time.Duration(time.Second * 10)
chanAmt = btcutil.Amount(10e6)
pushAmt = btcutil.Amount(5e6)
paymentAmt = 100000
numInvoices = 6
)
// Before we start, obtain Bob's current wallet balance, we'll check to
// ensure that at the end of the force closure by Alice, Bob recognizes
// his new on-chain output.
bobBalReq := &lnrpc.WalletBalanceRequest{}
bobBalResp, err := net.Bob.WalletBalance(ctxb, bobBalReq)
// TODO(roasbeef): should check default value in config here
// instead, or make delay a param
defaultCSV := uint32(4)
defaultCLTV := defaultBitcoinForwardingPolicy.TimeLockDelta
// Since we'd like to test failure scenarios with outstanding htlcs,
// we'll introduce another node into our test network: Carol.
carol, err := net.NewNode([]string{"--debughtlc", "--hodlhtlc"})
if err != nil {
t.Fatalf("unable to get bob's balance: %v", err)
t.Fatalf("unable to create new nodes: %v", err)
}
bobStartingBalance := btcutil.Amount(bobBalResp.Balance * 1e8)
// First establish a channel with a capacity of 100k satoshis between
// Alice and Bob. We also push 50k satoshis of the initial amount
// towards Bob.
numFundingConfs := uint32(1)
chanAmt := btcutil.Amount(10e4)
pushAmt := btcutil.Amount(5e4)
chanOpenUpdate, err := net.OpenChannel(ctxb, net.Alice, net.Bob,
chanAmt, pushAmt)
// We must let Alice have an open channel before she can send a node
// announcement, so we open a channel with Carol,
if err := net.ConnectNodes(ctxb, net.Alice, carol); err != nil {
t.Fatalf("unable to connect alice to carol: %v", err)
}
// Before we start, obtain Carol's current wallet balance, we'll check
// to ensure that at the end of the force closure by Alice, Carol
// recognizes his new on-chain output.
carolBalReq := &lnrpc.WalletBalanceRequest{}
carolBalResp, err := carol.WalletBalance(ctxb, carolBalReq)
if err != nil {
t.Fatalf("unable to open channel: %v", err)
t.Fatalf("unable to get carol's balance: %v", err)
}
if _, err := net.Miner.Node.Generate(numFundingConfs); err != nil {
t.Fatalf("unable to mine block: %v", err)
}
carolStartingBalance := btcutil.Amount(carolBalResp.Balance * 1e8)
ctxt, _ := context.WithTimeout(ctxb, timeout)
chanPoint, err := net.WaitForChannelOpen(ctxt, chanOpenUpdate)
chanPoint := openChannelAndAssert(ctxt, t, net, net.Alice, carol,
chanAmt, pushAmt)
// Wait for Alice to receive the channel edge from the funding manager.
ctxt, _ = context.WithTimeout(ctxb, timeout)
err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint)
if err != nil {
t.Fatalf("error while waiting for channel to open: %v", err)
t.Fatalf("alice didn't see the alice->carol channel before "+
"timeout: %v", err)
}
// Now that the channel is open, immediately execute a force closure of
// the channel. This will also assert that the commitment transaction
// was immediately broadcast in order to fulfill the force closure
// request.
// With the channel open, we'll create a few invoices for Carol that
// Alice will pay to in order to advance the state of the channel.
carolPaymentReqs := make([]string, numInvoices)
for i := 0; i < numInvoices; i++ {
preimage := bytes.Repeat([]byte{byte(128 - i)}, 32)
invoice := &lnrpc.Invoice{
Memo: "testing",
RPreimage: preimage,
Value: paymentAmt,
}
resp, err := carol.AddInvoice(ctxb, invoice)
if err != nil {
t.Fatalf("unable to add invoice: %v", err)
}
carolPaymentReqs[i] = resp.PaymentRequest
}
// As we'll be querying the state of Carols's channels frequently we'll
// create a closure helper function for the purpose.
getAliceChanInfo := func() (*lnrpc.ActiveChannel, error) {
req := &lnrpc.ListChannelsRequest{}
aliceChannelInfo, err := net.Alice.ListChannels(ctxb, req)
if err != nil {
return nil, err
}
if len(aliceChannelInfo.Channels) != 1 {
t.Fatalf("alice should only have a single channel, "+
"instead he has %v",
len(aliceChannelInfo.Channels))
}
return aliceChannelInfo.Channels[0], nil
}
// Open up a payment stream to Alice that we'll use to send payment to
// Carol. We also create a small helper function to send payments to
// Carol, consuming the payment hashes we generated above.
alicePayStream, err := net.Alice.SendPayment(ctxb)
if err != nil {
t.Fatalf("unable to create payment stream for alice: %v", err)
}
sendPayments := func(start, stop int) error {
for i := start; i < stop; i++ {
sendReq := &lnrpc.SendRequest{
PaymentRequest: carolPaymentReqs[i],
}
if err := alicePayStream.Send(sendReq); err != nil {
return err
}
}
return nil
}
// Fetch starting height of this test so we can compute the block
// heights we expect certain events to take place.
_, curHeight, err := net.Miner.Node.GetBestBlock()
if err != nil {
t.Fatalf("unable to get best block height")
}
// Using the current height of the chain, derive the relevant heights
// for incubating two-stage htlcs.
var (
startHeight = uint32(curHeight)
commCsvMaturityHeight = startHeight + 1 + defaultCSV
htlcExpiryHeight = startHeight + defaultCLTV
htlcCsvMaturityHeight = startHeight + defaultCLTV + 1 + defaultCSV
)
// Send payments from Alice to Carol, since Carol is htlchodl mode,
// the htlc outputs should be left unsettled, and should be swept by the
// utxo nursery.
if err := sendPayments(0, numInvoices); err != nil {
t.Fatalf("unable to send payment: %v", err)
}
time.Sleep(200 * time.Millisecond)
aliceChan, err := getAliceChanInfo()
if err != nil {
t.Fatalf("unable to get alice's channel info: %v", err)
}
if aliceChan.NumUpdates == 0 {
t.Fatalf("alice should see at least one update to her channel")
}
// Now that the channel is open and we have unsettled htlcs, immediately
// execute a force closure of the channel. This will also assert that
// the commitment transaction was immediately broadcast in order to
// fulfill the force closure request.
_, closingTxID, err := net.CloseChannel(ctxb, net.Alice, chanPoint, true)
if err != nil {
t.Fatalf("unable to execute force channel closure: %v", err)
@ -1025,25 +1223,41 @@ func testChannelForceClosure(net *networkHarness, t *harnessTest) {
if err != nil {
t.Fatalf("unable to query for pending channels: %v", err)
}
var found bool
assertNumForceClosedChannels(t, pendingChanResp, 1)
// Compute the outpoint of the channel, which we will use repeatedly to
// locate the pending channel information in the rpc responses.
txid, _ := chainhash.NewHash(chanPoint.FundingTxid[:])
op := wire.OutPoint{
Hash: *txid,
Index: chanPoint.OutputIndex,
}
for _, forceClose := range pendingChanResp.PendingForceClosingChannels {
if forceClose.Channel.ChannelPoint == op.String() {
found = true
break
}
forceClose := findForceClosedChannel(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 {
t.Fatalf("all funds should still be in limbo")
}
if !found {
t.Fatalf("channel not marked as force close for alice")
if forceClose.RecoveredBalance != 0 {
t.Fatalf("no funds should yet be shown as recovered")
}
// TODO(roasbeef): should check default value in config here instead,
// or make delay a param
const defaultCSV = 4
// 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)
// Since all of our payments were sent with Carol in hodl mode, all of
// them should be unsettled and attached to the commitment transaction.
// They also should have been configured such that they are not filtered
// as dust. At this point, all pending htlcs should be in stage 1, with
// a timeout set to the default CLTV expiry (144) blocks above the
// starting height.
assertPendingChannelNumHtlcs(t, forceClose, numInvoices)
assertPendingHtlcStageAndMaturity(t, forceClose, 1, htlcExpiryHeight,
int32(defaultCLTV))
// 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
@ -1071,23 +1285,31 @@ func testChannelForceClosure(net *networkHarness, t *harnessTest) {
duration := time.Millisecond * 300
time.Sleep(duration)
// Now that the channel has been force closed, it should now have the
// height and number of blocks to confirm populated.
pendingChan, err := net.Alice.PendingChannels(ctxb, pendingChansRequest)
pendingChanResp, err = net.Alice.PendingChannels(ctxb, pendingChansRequest)
if err != nil {
t.Fatalf("unable to query for pending channels: %v", err)
}
if len(pendingChan.PendingForceClosingChannels) == 0 {
t.Fatalf("channel not marked as force close for alice")
assertNumForceClosedChannels(t, pendingChanResp, 1)
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.
assertCommitmentMaturity(t, forceClose, commCsvMaturityHeight,
int32(defaultCSV))
// Check that our pending htlcs have deducted the block confirming the
// commitment transactionfrom their blocks til maturity value.
assertPendingChannelNumHtlcs(t, forceClose, numInvoices)
assertPendingHtlcStageAndMaturity(t, forceClose, 1, htlcExpiryHeight,
int32(defaultCLTV)-1)
// None of our outputs have been swept, so they should all be limbo.
if forceClose.LimboBalance == 0 {
t.Fatalf("all funds should still be in limbo")
}
forceClosedChan := pendingChan.PendingForceClosingChannels[0]
if forceClosedChan.MaturityHeight == 0 {
t.Fatalf("force close channel marked as not confirmed")
}
if forceClosedChan.BlocksTilMaturity != defaultCSV {
t.Fatalf("force closed channel has incorrect maturity time: "+
"expected %v, got %v", forceClosedChan.BlocksTilMaturity,
defaultCSV)
if forceClose.RecoveredBalance != 0 {
t.Fatalf("no funds should yet be shown as recovered")
}
// The following restart is intended to ensure that outputs from the
@ -1106,13 +1328,40 @@ func testChannelForceClosure(net *networkHarness, t *harnessTest) {
t.Fatalf("unable to mine blocks: %v", err)
}
// The following restart checks to ensure that outputs in the kindergarten
// bucket are persisted while waiting for the required number of
// confirmations to be reported.
// The following restart checks to ensure that outputs in the
// kindergarten bucket are persisted while waiting for the required
// number of confirmations to be reported.
if err := net.RestartNode(net.Alice, nil); err != nil {
t.Fatalf("Node restart failed: %v", err)
}
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)
// At this point, the nursery should show that the commitment output has
// 1 block left before its CSV delay expires. In total, we have mined
// exactly defaultCSV blocks, so the htlc outputs should also reflect
// that this many blocks have passed.
assertCommitmentMaturity(t, forceClose, commCsvMaturityHeight, 1)
assertPendingChannelNumHtlcs(t, forceClose, numInvoices)
assertPendingHtlcStageAndMaturity(t, forceClose, 1, htlcExpiryHeight,
int32(defaultCLTV)-int32(defaultCSV))
// All funds should still be shown in limbo.
if forceClose.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")
}
// Generate an additional block, which should cause the CSV delayed
// output from the commitment txn to expire.
if _, err := net.Miner.Node.Generate(1); err != nil {
t.Fatalf("unable to mine blocks: %v", err)
}
@ -1120,37 +1369,11 @@ func testChannelForceClosure(net *networkHarness, t *harnessTest) {
// At this point, the sweeping transaction should now be broadcast. So
// we fetch the node's mempool to ensure it has been properly
// broadcast.
var sweepingTXID *chainhash.Hash
var mempool []*chainhash.Hash
mempoolTimeout := time.After(3 * time.Second)
checkMempoolTick := time.NewTicker(100 * time.Millisecond)
defer checkMempoolTick.Stop()
mempoolPoll:
for {
select {
case <-mempoolTimeout:
t.Fatalf("sweep tx not found in mempool")
case <-checkMempoolTick.C:
mempool, err = net.Miner.Node.GetRawMempool()
if err != nil {
t.Fatalf("unable to fetch node's mempool: %v", err)
}
if len(mempool) != 0 {
break mempoolPoll
}
}
sweepingTXID, err := waitForTxInMempool(net.Miner.Node, 3*time.Second)
if err != nil {
t.Fatalf("failed to get sweep tx from mempool: %v", err)
}
// There should be exactly one transaction within the mempool at this
// point.
// TODO(roasbeef): assertion may not necessarily hold with concurrent
// test executions
if len(mempool) != 1 {
t.Fatalf("node's mempool is wrong size, expected 1 got %v",
len(mempool))
}
sweepingTXID = mempool[0]
// Fetch the sweep transaction, all input it's spending should be from
// the commitment transaction which was broadcast on-chain.
sweepTx, err := net.Miner.Node.GetRawTransaction(sweepingTXID)
@ -1165,7 +1388,13 @@ mempoolPoll:
}
}
// Finally, we mine an additional block which should include the sweep
// Restart Alice to ensure that she resumes watching the finalized
// commitment sweep txid.
if err := net.RestartNode(net.Alice, nil); err != nil {
t.Fatalf("Node restart failed: %v", err)
}
// Next, we mine an additional block which should include the sweep
// transaction as the input scripts and the sequence locks on the
// inputs should be properly met.
blockHash, err := net.Miner.Node.Generate(1)
@ -1179,28 +1408,309 @@ mempoolPoll:
assertTxInBlock(t, block, sweepTx.Hash())
// Now that the channel has been fully swept, it should no longer show
// up within the pending channels RPC.
time.Sleep(time.Millisecond * 300)
pendingChans, err := net.Alice.PendingChannels(ctxb, pendingChansRequest)
// We sleep here to ensure that Alice has enough time to receive a
// confirmation for the commitment transaction, which we already
// asserted was in the last block.
time.Sleep(300 * time.Millisecond)
// Now that the commit output has been fully swept, check to see that
// the channel remains open for the pending htlc outputs.
pendingChanResp, err = net.Alice.PendingChannels(ctxb, pendingChansRequest)
if err != nil {
t.Fatalf("unable to query for pending channels: %v", err)
}
if len(pendingChans.PendingForceClosingChannels) != 0 {
t.Fatalf("no channels should be shown as force closed")
assertNumForceClosedChannels(t, pendingChanResp, 1)
// Check that the commitment transactions shows that we are still past
// the maturity of the commitment output.
forceClose = findForceClosedChannel(t, pendingChanResp, &op)
assertCommitmentMaturity(t, forceClose, commCsvMaturityHeight, -1)
// Our pending htlcs should still be shown in the first stage, having
// deducted an additional two blocks from the relative maturity time..
assertPendingChannelNumHtlcs(t, forceClose, numInvoices)
assertPendingHtlcStageAndMaturity(t, forceClose, 1, htlcExpiryHeight,
int32(defaultCLTV)-int32(defaultCSV)-2)
// The htlc funds will still be shown as limbo, since they are still in
// their first stage. The commitment funds will have been recovered
// after the commit txn was included in the last block.
if forceClose.LimboBalance == 0 {
t.Fatalf("htlc funds should still be in limbo")
}
if forceClose.RecoveredBalance == 0 {
t.Fatalf("commitment funds should be shown as recovered")
}
// At this point, Bob should now be aware of his new immediately
// Compute the height preceding that which will cause the htlc CLTV
// timeouts will expire. The outputs entered at the same height as the
// output spending from the commitment txn, so we must deduct the number
// of blocks we have generated since adding it to the nursery, and take
// an additional block off so that we end up one block shy of the expiry
// height.
cltvHeightDelta := defaultCLTV - defaultCSV - 2 - 1
// Check that our htlcs are still expected to expire the computed expiry
// height, and that the remaining number of blocks is equal to the delta
// we just computed, including an additional block to actually trigger
// the broadcast.
assertPendingChannelNumHtlcs(t, forceClose, numInvoices)
assertPendingHtlcStageAndMaturity(t, forceClose, 1, htlcExpiryHeight,
int32(cltvHeightDelta+1))
// Advance the blockchain until just before the CLTV expires, nothing
// exciting should have happened during this time.
blockHash, err = net.Miner.Node.Generate(cltvHeightDelta)
if err != nil {
t.Fatalf("unable to generate block: %v", err)
}
time.Sleep(duration)
// We now restart Alice, to ensure that she will broadcast the presigned
// htlc timeout txns after the delay expires after experiencing an while
// waiting for the htlc outputs to incubate.
if err := net.RestartNode(net.Alice, nil); err != nil {
t.Fatalf("Node restart failed: %v", err)
}
time.Sleep(duration)
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)
// Verify that commitment output was confirmed many moons ago.
assertCommitmentMaturity(t, forceClose, commCsvMaturityHeight,
-int32(cltvHeightDelta)-1)
// We should now be at the block just before the utxo nursery will
// attempt to broadcast the htlc timeout transactions.
assertPendingChannelNumHtlcs(t, forceClose, numInvoices)
assertPendingHtlcStageAndMaturity(t, forceClose, 1, htlcExpiryHeight, 1)
// Now that our commitment confirmation depth has been surpassed, we
// should now see a non-zero recovered balance. All htlc outputs are
// still left in limbo, so it should be non-zero as well.
if forceClose.LimboBalance == 0 {
t.Fatalf("htlc funds should still be in limbo")
}
if forceClose.RecoveredBalance == 0 {
t.Fatalf("commitment funds should not be in limbo")
}
// Now, generate the block which will cause Alice to broadcast the
// presigned htlc timeout txns.
blockHash, err = net.Miner.Node.Generate(1)
if err != nil {
t.Fatalf("unable to generate block: %v", err)
}
// Since Alice had numInvoices (6) htlcs extended to Carol before force
// closing, we expect Alice to broadcast an htlc timeout txn for each
// one. Wait for them all to show up in the mempool.
htlcTxIDs, err := waitForNTxsInMempool(net.Miner.Node, numInvoices,
3*time.Second)
if err != nil {
t.Fatalf("unable to find htlc timeout txns in mempool: %v", err)
}
// Retrieve each htlc timeout txn from the mempool, and ensure it is
// well-formed. This entails verifying that each only spends from
// output, and that that output is from the commitment txn.
for _, htlcTxID := range htlcTxIDs {
// Fetch the sweep transaction, all input it's spending should
// be from the commitment transaction which was broadcast
// on-chain.
htlcTx, err := net.Miner.Node.GetRawTransaction(htlcTxID)
if err != nil {
t.Fatalf("unable to fetch sweep tx: %v", err)
}
// Ensure the htlc transaction only has one input.
if len(htlcTx.MsgTx().TxIn) != 1 {
t.Fatalf("htlc transaction should only have one txin, "+
"has %d", len(htlcTx.MsgTx().TxIn))
}
// Ensure the htlc transaction is spending from the commitment
// transaction.
txIn := htlcTx.MsgTx().TxIn[0]
if !closingTxID.IsEqual(&txIn.PreviousOutPoint.Hash) {
t.Fatalf("htlc transaction not spending from commit "+
"tx %v, instead spending %v",
closingTxID, txIn.PreviousOutPoint)
}
}
// With the htlc timeout txns still in the mempool, we restart Alice to
// verify that she can resume watching the htlc txns she broadcasted
// before crashing.
if err := net.RestartNode(net.Alice, nil); err != nil {
t.Fatalf("Node restart failed: %v", err)
}
time.Sleep(duration)
// Generate a block that mines the htlc timeout txns. Doing so now
// activates the 2nd-stage CSV delayed outputs.
blockHash, err = net.Miner.Node.Generate(1)
if err != nil {
t.Fatalf("unable to generate block: %v", err)
}
// This sleep gives Alice enough to time move the crib outputs into the
// kindergarten bucket.
time.Sleep(duration)
// Alice is restarted here to ensure that she promptly moved the crib
// outputs to the kindergarten bucket after the htlc timeout txns were
// confirmed.
if err := net.RestartNode(net.Alice, nil); err != nil {
t.Fatalf("Node restart failed: %v", err)
}
// Advance the chain until just before the 2nd-layer CSV delays expire.
blockHash, err = net.Miner.Node.Generate(defaultCSV - 1)
if err != nil {
t.Fatalf("unable to generate block: %v", err)
}
// Restart Alice to ensure that she can recover from a failure before
// having graduated the htlc outputs in the kindergarten bucket.
if err := net.RestartNode(net.Alice, nil); err != nil {
t.Fatalf("Node restart failed: %v", err)
}
// Now that the channel has been fully swept, it should no longer show
// incubated, check to see that Alice's node still reports the channel
// as pending 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)
assertCommitmentMaturity(t, forceClose, commCsvMaturityHeight,
-int32(cltvHeightDelta)-int32(defaultCSV)-2)
if forceClose.LimboBalance == 0 {
t.Fatalf("htlc funds should still be in limbo")
}
if forceClose.RecoveredBalance == 0 {
t.Fatalf("commitment funds should not be in limbo")
}
assertPendingChannelNumHtlcs(t, forceClose, numInvoices)
// Generate a block that causes Alice to sweep the htlc outputs in the
// kindergarten bucket.
blockHash, err = net.Miner.Node.Generate(1)
if err != nil {
t.Fatalf("unable to generate block: %v", err)
}
// Wait for the single sweep txn to appear in the mempool.
htlcSweepTxID, err := waitForTxInMempool(net.Miner.Node, 3*time.Second)
if err != nil {
t.Fatalf("failed to get sweep tx from mempool: %v", err)
}
// Construct a map of the already confirmed htlc timeout txids, that
// will count the number of times each is spent by the sweep txn. We
// prepopulate it in this way so that we can later detect if we are
// spending from an output that was not a confirmed htlc timeout txn.
var htlcTxIDSet = make(map[chainhash.Hash]int)
for _, htlcTxID := range htlcTxIDs {
htlcTxIDSet[*htlcTxID] = 0
}
// Fetch the htlc sweep transaction from the mempool.
htlcSweepTx, err := net.Miner.Node.GetRawTransaction(htlcSweepTxID)
if err != nil {
t.Fatalf("unable to fetch sweep tx: %v", err)
}
// Ensure the htlc sweep transaction only has one input for each htlc
// Alice extended before force closing.
if len(htlcSweepTx.MsgTx().TxIn) != numInvoices {
t.Fatalf("htlc transaction should have %d txin, "+
"has %d", numInvoices, len(htlcSweepTx.MsgTx().TxIn))
}
// Ensure that each output spends from exactly one htlc timeout txn.
for _, txIn := range htlcSweepTx.MsgTx().TxIn {
outpoint := txIn.PreviousOutPoint.Hash
// Check that the input is a confirmed htlc timeout txn.
if _, ok := htlcTxIDSet[outpoint]; !ok {
t.Fatalf("htlc sweep output not spending from htlc "+
"tx, instead spending output %v", outpoint)
}
// Increment our count for how many times this output was spent.
htlcTxIDSet[outpoint]++
// Check that each is only spent once.
if htlcTxIDSet[outpoint] > 1 {
t.Fatalf("htlc sweep tx has multiple spends from "+
"outpoint %v", outpoint)
}
}
// The following restart checks to ensure that the nursery store is
// storing the txid of the previously broadcast htlc sweep txn, and that
// it begins watching that txid after restarting.
if err := net.RestartNode(net.Alice, nil); err != nil {
t.Fatalf("Node restart failed: %v", err)
}
time.Sleep(duration)
// Now that the channel has been fully swept, it should no longer show
// incubated, check to see that Alice's node still reports the channel
// as pending 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)
// All htlcs should show zero blocks until maturity, as evidenced by
// having checked the sweep transaction in the mempool.
forceClose = findForceClosedChannel(t, pendingChanResp, &op)
assertPendingChannelNumHtlcs(t, forceClose, numInvoices)
assertPendingHtlcStageAndMaturity(t, forceClose, 2,
htlcCsvMaturityHeight, 0)
// Generate the final block that sweeps all htlc funds into the user's
// wallet.
blockHash, err = net.Miner.Node.Generate(1)
if err != nil {
t.Fatalf("unable to generate block: %v", err)
}
time.Sleep(3 * duration)
// Now that the channel has been fully swept, it should no longer show
// up within the pending channels RPC.
pendingChanResp, err = net.Alice.PendingChannels(ctxb, pendingChansRequest)
if err != nil {
t.Fatalf("unable to query for pending channels: %v", err)
}
assertNumForceClosedChannels(t, pendingChanResp, 0)
// In addition to there being no pending channels, we verify that
// pending channels does not report any money still in limbo.
if pendingChanResp.TotalLimboBalance != 0 {
t.Fatalf("no user funds should be left in limbo after incubation")
}
// At this point, Carol should now be aware of his new immediately
// spendable on-chain balance, as it was Alice who broadcast the
// commitment transaction.
bobBalResp, err = net.Bob.WalletBalance(ctxb, bobBalReq)
carolBalResp, err = net.Bob.WalletBalance(ctxb, carolBalReq)
if err != nil {
t.Fatalf("unable to get bob's balance: %v", err)
t.Fatalf("unable to get carol's balance: %v", err)
}
bobExpectedBalance := bobStartingBalance + pushAmt
if btcutil.Amount(bobBalResp.Balance*1e8) < bobExpectedBalance {
t.Fatalf("bob's balance is incorrect: expected %v got %v",
bobExpectedBalance, btcutil.Amount(bobBalResp.Balance*1e8))
carolExpectedBalance := carolStartingBalance + pushAmt
if btcutil.Amount(carolBalResp.Balance*1e8) < carolExpectedBalance {
t.Fatalf("carol's balance is incorrect: expected %v got %v",
carolExpectedBalance,
btcutil.Amount(carolBalResp.Balance*1e8))
}
}
@ -1984,6 +2494,36 @@ poll:
return txid, nil
}
// waitForNTxsInMempool polls until finding the desired number of transactions
// in the provided miner's mempool. An error is returned if the this number is
// not met after the given timeout.
func waitForNTxsInMempool(miner *rpcclient.Client, n int,
timeout time.Duration) ([]*chainhash.Hash, error) {
breakTimeout := time.After(timeout)
ticker := time.NewTicker(50 * time.Millisecond)
defer ticker.Stop()
var err error
var mempool []*chainhash.Hash
for {
select {
case <-breakTimeout:
return nil, fmt.Errorf("wanted %v, only found %v txs "+
"in mempool", n, len(mempool))
case <-ticker.C:
mempool, err = miner.GetRawMempool()
if err != nil {
return nil, err
}
if len(mempool) == n {
return mempool, nil
}
}
}
}
// testRevokedCloseRetributinPostBreachConf tests that Alice is able carry out
// retribution in the event that she fails immediately after detecting Bob's
// breach txn in the mempool.
@ -3613,7 +4153,7 @@ func testBidirectionalAsyncPayments(net *networkHarness, t *harnessTest) {
const (
timeout = time.Duration(time.Second * 5)
paymentAmt = 100
paymentAmt = 1000
)
// First establish a channel with a capacity equals to the overall

@ -51,6 +51,7 @@ It has these top-level messages:
PendingUpdate
OpenChannelRequest
OpenStatusUpdate
PendingHTLC
PendingChannelRequest
PendingChannelResponse
WalletBalanceRequest
@ -1660,13 +1661,78 @@ func _OpenStatusUpdate_OneofSizer(msg proto.Message) (n int) {
return n
}
type PendingHTLC struct {
// / The direction within the channel that the htlc was sent
Incoming bool `protobuf:"varint,1,opt,name=incoming" json:"incoming,omitempty"`
// / The total value of the htlc
Amount int64 `protobuf:"varint,2,opt,name=amount" json:"amount,omitempty"`
// / The final output to be swept back to the user's wallet
Outpoint string `protobuf:"bytes,3,opt,name=outpoint" json:"outpoint,omitempty"`
// / The next block height at which we can spend the current stage
MaturityHeight uint32 `protobuf:"varint,4,opt,name=maturity_height" json:"maturity_height,omitempty"`
// *
// The number of blocks remaining until the current stage can be swept.
// Negative values indicate how many blocks have passed since becoming
// mature.
BlocksTilMaturity int32 `protobuf:"varint,5,opt,name=blocks_til_maturity" json:"blocks_til_maturity,omitempty"`
// / Indicates whether the htlc is in its first or second stage of recovery
Stage uint32 `protobuf:"varint,6,opt,name=stage" json:"stage,omitempty"`
}
func (m *PendingHTLC) Reset() { *m = PendingHTLC{} }
func (m *PendingHTLC) String() string { return proto.CompactTextString(m) }
func (*PendingHTLC) ProtoMessage() {}
func (*PendingHTLC) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{43} }
func (m *PendingHTLC) GetIncoming() bool {
if m != nil {
return m.Incoming
}
return false
}
func (m *PendingHTLC) GetAmount() int64 {
if m != nil {
return m.Amount
}
return 0
}
func (m *PendingHTLC) GetOutpoint() string {
if m != nil {
return m.Outpoint
}
return ""
}
func (m *PendingHTLC) GetMaturityHeight() uint32 {
if m != nil {
return m.MaturityHeight
}
return 0
}
func (m *PendingHTLC) GetBlocksTilMaturity() int32 {
if m != nil {
return m.BlocksTilMaturity
}
return 0
}
func (m *PendingHTLC) GetStage() uint32 {
if m != nil {
return m.Stage
}
return 0
}
type PendingChannelRequest struct {
}
func (m *PendingChannelRequest) Reset() { *m = PendingChannelRequest{} }
func (m *PendingChannelRequest) String() string { return proto.CompactTextString(m) }
func (*PendingChannelRequest) ProtoMessage() {}
func (*PendingChannelRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{43} }
func (*PendingChannelRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{44} }
type PendingChannelResponse struct {
// / The balance in satoshis encumbered in pending channels
@ -1682,7 +1748,7 @@ type PendingChannelResponse struct {
func (m *PendingChannelResponse) Reset() { *m = PendingChannelResponse{} }
func (m *PendingChannelResponse) String() string { return proto.CompactTextString(m) }
func (*PendingChannelResponse) ProtoMessage() {}
func (*PendingChannelResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{44} }
func (*PendingChannelResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{45} }
func (m *PendingChannelResponse) GetTotalLimboBalance() int64 {
if m != nil {
@ -1724,7 +1790,7 @@ func (m *PendingChannelResponse_PendingChannel) Reset() { *m = PendingCh
func (m *PendingChannelResponse_PendingChannel) String() string { return proto.CompactTextString(m) }
func (*PendingChannelResponse_PendingChannel) ProtoMessage() {}
func (*PendingChannelResponse_PendingChannel) Descriptor() ([]byte, []int) {
return fileDescriptor0, []int{44, 0}
return fileDescriptor0, []int{45, 0}
}
func (m *PendingChannelResponse_PendingChannel) GetRemoteNodePub() string {
@ -1791,7 +1857,7 @@ func (m *PendingChannelResponse_PendingOpenChannel) Reset() {
func (m *PendingChannelResponse_PendingOpenChannel) String() string { return proto.CompactTextString(m) }
func (*PendingChannelResponse_PendingOpenChannel) ProtoMessage() {}
func (*PendingChannelResponse_PendingOpenChannel) Descriptor() ([]byte, []int) {
return fileDescriptor0, []int{44, 1}
return fileDescriptor0, []int{45, 1}
}
func (m *PendingChannelResponse_PendingOpenChannel) GetChannel() *PendingChannelResponse_PendingChannel {
@ -1847,7 +1913,7 @@ func (m *PendingChannelResponse_ClosedChannel) Reset() { *m = PendingCha
func (m *PendingChannelResponse_ClosedChannel) String() string { return proto.CompactTextString(m) }
func (*PendingChannelResponse_ClosedChannel) ProtoMessage() {}
func (*PendingChannelResponse_ClosedChannel) Descriptor() ([]byte, []int) {
return fileDescriptor0, []int{44, 2}
return fileDescriptor0, []int{45, 2}
}
func (m *PendingChannelResponse_ClosedChannel) GetChannel() *PendingChannelResponse_PendingChannel {
@ -1873,8 +1939,14 @@ type PendingChannelResponse_ForceClosedChannel struct {
LimboBalance int64 `protobuf:"varint,3,opt,name=limbo_balance" json:"limbo_balance,omitempty"`
// / The height at which funds can be sweeped into the wallet
MaturityHeight uint32 `protobuf:"varint,4,opt,name=maturity_height" json:"maturity_height,omitempty"`
// / Remaining # of blocks until funds can be sweeped into the wallet
BlocksTilMaturity uint32 `protobuf:"varint,5,opt,name=blocks_til_maturity" json:"blocks_til_maturity,omitempty"`
//
// Remaining # of blocks until the commitment output can be swept.
// Negative values indicate how many blocks have passed since becoming
// mature.
BlocksTilMaturity int32 `protobuf:"varint,5,opt,name=blocks_til_maturity" json:"blocks_til_maturity,omitempty"`
// / The total value of funds successfully recovered from this channel
RecoveredBalance int64 `protobuf:"varint,6,opt,name=recovered_balance" json:"recovered_balance,omitempty"`
PendingHtlcs []*PendingHTLC `protobuf:"bytes,8,rep,name=pending_htlcs" json:"pending_htlcs,omitempty"`
}
func (m *PendingChannelResponse_ForceClosedChannel) Reset() {
@ -1883,7 +1955,7 @@ func (m *PendingChannelResponse_ForceClosedChannel) Reset() {
func (m *PendingChannelResponse_ForceClosedChannel) String() string { return proto.CompactTextString(m) }
func (*PendingChannelResponse_ForceClosedChannel) ProtoMessage() {}
func (*PendingChannelResponse_ForceClosedChannel) Descriptor() ([]byte, []int) {
return fileDescriptor0, []int{44, 3}
return fileDescriptor0, []int{45, 3}
}
func (m *PendingChannelResponse_ForceClosedChannel) GetChannel() *PendingChannelResponse_PendingChannel {
@ -1914,13 +1986,27 @@ func (m *PendingChannelResponse_ForceClosedChannel) GetMaturityHeight() uint32 {
return 0
}
func (m *PendingChannelResponse_ForceClosedChannel) GetBlocksTilMaturity() uint32 {
func (m *PendingChannelResponse_ForceClosedChannel) GetBlocksTilMaturity() int32 {
if m != nil {
return m.BlocksTilMaturity
}
return 0
}
func (m *PendingChannelResponse_ForceClosedChannel) GetRecoveredBalance() int64 {
if m != nil {
return m.RecoveredBalance
}
return 0
}
func (m *PendingChannelResponse_ForceClosedChannel) GetPendingHtlcs() []*PendingHTLC {
if m != nil {
return m.PendingHtlcs
}
return nil
}
type WalletBalanceRequest struct {
// / If only witness outputs should be considered when calculating the wallet's balance
WitnessOnly bool `protobuf:"varint,1,opt,name=witness_only,json=witnessOnly" json:"witness_only,omitempty"`
@ -1929,7 +2015,7 @@ type WalletBalanceRequest struct {
func (m *WalletBalanceRequest) Reset() { *m = WalletBalanceRequest{} }
func (m *WalletBalanceRequest) String() string { return proto.CompactTextString(m) }
func (*WalletBalanceRequest) ProtoMessage() {}
func (*WalletBalanceRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{45} }
func (*WalletBalanceRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{46} }
func (m *WalletBalanceRequest) GetWitnessOnly() bool {
if m != nil {
@ -1946,7 +2032,7 @@ type WalletBalanceResponse struct {
func (m *WalletBalanceResponse) Reset() { *m = WalletBalanceResponse{} }
func (m *WalletBalanceResponse) String() string { return proto.CompactTextString(m) }
func (*WalletBalanceResponse) ProtoMessage() {}
func (*WalletBalanceResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{46} }
func (*WalletBalanceResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{47} }
func (m *WalletBalanceResponse) GetBalance() int64 {
if m != nil {
@ -1961,7 +2047,7 @@ type ChannelBalanceRequest struct {
func (m *ChannelBalanceRequest) Reset() { *m = ChannelBalanceRequest{} }
func (m *ChannelBalanceRequest) String() string { return proto.CompactTextString(m) }
func (*ChannelBalanceRequest) ProtoMessage() {}
func (*ChannelBalanceRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{47} }
func (*ChannelBalanceRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{48} }
type ChannelBalanceResponse struct {
// / Sum of channels balances denominated in satoshis
@ -1971,7 +2057,7 @@ type ChannelBalanceResponse struct {
func (m *ChannelBalanceResponse) Reset() { *m = ChannelBalanceResponse{} }
func (m *ChannelBalanceResponse) String() string { return proto.CompactTextString(m) }
func (*ChannelBalanceResponse) ProtoMessage() {}
func (*ChannelBalanceResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{48} }
func (*ChannelBalanceResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{49} }
func (m *ChannelBalanceResponse) GetBalance() int64 {
if m != nil {
@ -1990,7 +2076,7 @@ type QueryRoutesRequest struct {
func (m *QueryRoutesRequest) Reset() { *m = QueryRoutesRequest{} }
func (m *QueryRoutesRequest) String() string { return proto.CompactTextString(m) }
func (*QueryRoutesRequest) ProtoMessage() {}
func (*QueryRoutesRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{49} }
func (*QueryRoutesRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{50} }
func (m *QueryRoutesRequest) GetPubKey() string {
if m != nil {
@ -2013,7 +2099,7 @@ type QueryRoutesResponse struct {
func (m *QueryRoutesResponse) Reset() { *m = QueryRoutesResponse{} }
func (m *QueryRoutesResponse) String() string { return proto.CompactTextString(m) }
func (*QueryRoutesResponse) ProtoMessage() {}
func (*QueryRoutesResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{50} }
func (*QueryRoutesResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{51} }
func (m *QueryRoutesResponse) GetRoutes() []*Route {
if m != nil {
@ -2037,7 +2123,7 @@ type Hop struct {
func (m *Hop) Reset() { *m = Hop{} }
func (m *Hop) String() string { return proto.CompactTextString(m) }
func (*Hop) ProtoMessage() {}
func (*Hop) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{51} }
func (*Hop) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{52} }
func (m *Hop) GetChanId() uint64 {
if m != nil {
@ -2107,7 +2193,7 @@ type Route struct {
func (m *Route) Reset() { *m = Route{} }
func (m *Route) String() string { return proto.CompactTextString(m) }
func (*Route) ProtoMessage() {}
func (*Route) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{52} }
func (*Route) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{53} }
func (m *Route) GetTotalTimeLock() uint32 {
if m != nil {
@ -2145,7 +2231,7 @@ type NodeInfoRequest struct {
func (m *NodeInfoRequest) Reset() { *m = NodeInfoRequest{} }
func (m *NodeInfoRequest) String() string { return proto.CompactTextString(m) }
func (*NodeInfoRequest) ProtoMessage() {}
func (*NodeInfoRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{53} }
func (*NodeInfoRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{54} }
func (m *NodeInfoRequest) GetPubKey() string {
if m != nil {
@ -2168,7 +2254,7 @@ type NodeInfo struct {
func (m *NodeInfo) Reset() { *m = NodeInfo{} }
func (m *NodeInfo) String() string { return proto.CompactTextString(m) }
func (*NodeInfo) ProtoMessage() {}
func (*NodeInfo) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{54} }
func (*NodeInfo) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{55} }
func (m *NodeInfo) GetNode() *LightningNode {
if m != nil {
@ -2206,7 +2292,7 @@ type LightningNode struct {
func (m *LightningNode) Reset() { *m = LightningNode{} }
func (m *LightningNode) String() string { return proto.CompactTextString(m) }
func (*LightningNode) ProtoMessage() {}
func (*LightningNode) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{55} }
func (*LightningNode) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{56} }
func (m *LightningNode) GetLastUpdate() uint32 {
if m != nil {
@ -2244,7 +2330,7 @@ type NodeAddress struct {
func (m *NodeAddress) Reset() { *m = NodeAddress{} }
func (m *NodeAddress) String() string { return proto.CompactTextString(m) }
func (*NodeAddress) ProtoMessage() {}
func (*NodeAddress) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{56} }
func (*NodeAddress) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{57} }
func (m *NodeAddress) GetNetwork() string {
if m != nil {
@ -2270,7 +2356,7 @@ type RoutingPolicy struct {
func (m *RoutingPolicy) Reset() { *m = RoutingPolicy{} }
func (m *RoutingPolicy) String() string { return proto.CompactTextString(m) }
func (*RoutingPolicy) ProtoMessage() {}
func (*RoutingPolicy) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{57} }
func (*RoutingPolicy) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{58} }
func (m *RoutingPolicy) GetTimeLockDelta() uint32 {
if m != nil {
@ -2324,7 +2410,7 @@ type ChannelEdge struct {
func (m *ChannelEdge) Reset() { *m = ChannelEdge{} }
func (m *ChannelEdge) String() string { return proto.CompactTextString(m) }
func (*ChannelEdge) ProtoMessage() {}
func (*ChannelEdge) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{58} }
func (*ChannelEdge) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{59} }
func (m *ChannelEdge) GetChannelId() uint64 {
if m != nil {
@ -2388,7 +2474,7 @@ type ChannelGraphRequest struct {
func (m *ChannelGraphRequest) Reset() { *m = ChannelGraphRequest{} }
func (m *ChannelGraphRequest) String() string { return proto.CompactTextString(m) }
func (*ChannelGraphRequest) ProtoMessage() {}
func (*ChannelGraphRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{59} }
func (*ChannelGraphRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{60} }
// / Returns a new instance of the directed channel graph.
type ChannelGraph struct {
@ -2401,7 +2487,7 @@ type ChannelGraph struct {
func (m *ChannelGraph) Reset() { *m = ChannelGraph{} }
func (m *ChannelGraph) String() string { return proto.CompactTextString(m) }
func (*ChannelGraph) ProtoMessage() {}
func (*ChannelGraph) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{60} }
func (*ChannelGraph) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{61} }
func (m *ChannelGraph) GetNodes() []*LightningNode {
if m != nil {
@ -2428,7 +2514,7 @@ type ChanInfoRequest struct {
func (m *ChanInfoRequest) Reset() { *m = ChanInfoRequest{} }
func (m *ChanInfoRequest) String() string { return proto.CompactTextString(m) }
func (*ChanInfoRequest) ProtoMessage() {}
func (*ChanInfoRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{61} }
func (*ChanInfoRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{62} }
func (m *ChanInfoRequest) GetChanId() uint64 {
if m != nil {
@ -2443,7 +2529,7 @@ type NetworkInfoRequest struct {
func (m *NetworkInfoRequest) Reset() { *m = NetworkInfoRequest{} }
func (m *NetworkInfoRequest) String() string { return proto.CompactTextString(m) }
func (*NetworkInfoRequest) ProtoMessage() {}
func (*NetworkInfoRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{62} }
func (*NetworkInfoRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{63} }
type NetworkInfo struct {
GraphDiameter uint32 `protobuf:"varint,1,opt,name=graph_diameter" json:"graph_diameter,omitempty"`
@ -2460,7 +2546,7 @@ type NetworkInfo struct {
func (m *NetworkInfo) Reset() { *m = NetworkInfo{} }
func (m *NetworkInfo) String() string { return proto.CompactTextString(m) }
func (*NetworkInfo) ProtoMessage() {}
func (*NetworkInfo) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{63} }
func (*NetworkInfo) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{64} }
func (m *NetworkInfo) GetGraphDiameter() uint32 {
if m != nil {
@ -2531,7 +2617,7 @@ type StopRequest struct {
func (m *StopRequest) Reset() { *m = StopRequest{} }
func (m *StopRequest) String() string { return proto.CompactTextString(m) }
func (*StopRequest) ProtoMessage() {}
func (*StopRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{64} }
func (*StopRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{65} }
type StopResponse struct {
}
@ -2539,7 +2625,7 @@ type StopResponse struct {
func (m *StopResponse) Reset() { *m = StopResponse{} }
func (m *StopResponse) String() string { return proto.CompactTextString(m) }
func (*StopResponse) ProtoMessage() {}
func (*StopResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{65} }
func (*StopResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{66} }
type GraphTopologySubscription struct {
}
@ -2547,7 +2633,7 @@ type GraphTopologySubscription struct {
func (m *GraphTopologySubscription) Reset() { *m = GraphTopologySubscription{} }
func (m *GraphTopologySubscription) String() string { return proto.CompactTextString(m) }
func (*GraphTopologySubscription) ProtoMessage() {}
func (*GraphTopologySubscription) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{66} }
func (*GraphTopologySubscription) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{67} }
type GraphTopologyUpdate struct {
NodeUpdates []*NodeUpdate `protobuf:"bytes,1,rep,name=node_updates,json=nodeUpdates" json:"node_updates,omitempty"`
@ -2558,7 +2644,7 @@ type GraphTopologyUpdate struct {
func (m *GraphTopologyUpdate) Reset() { *m = GraphTopologyUpdate{} }
func (m *GraphTopologyUpdate) String() string { return proto.CompactTextString(m) }
func (*GraphTopologyUpdate) ProtoMessage() {}
func (*GraphTopologyUpdate) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{67} }
func (*GraphTopologyUpdate) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{68} }
func (m *GraphTopologyUpdate) GetNodeUpdates() []*NodeUpdate {
if m != nil {
@ -2591,7 +2677,7 @@ type NodeUpdate struct {
func (m *NodeUpdate) Reset() { *m = NodeUpdate{} }
func (m *NodeUpdate) String() string { return proto.CompactTextString(m) }
func (*NodeUpdate) ProtoMessage() {}
func (*NodeUpdate) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{68} }
func (*NodeUpdate) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{69} }
func (m *NodeUpdate) GetAddresses() []string {
if m != nil {
@ -2637,7 +2723,7 @@ type ChannelEdgeUpdate struct {
func (m *ChannelEdgeUpdate) Reset() { *m = ChannelEdgeUpdate{} }
func (m *ChannelEdgeUpdate) String() string { return proto.CompactTextString(m) }
func (*ChannelEdgeUpdate) ProtoMessage() {}
func (*ChannelEdgeUpdate) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{69} }
func (*ChannelEdgeUpdate) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{70} }
func (m *ChannelEdgeUpdate) GetChanId() uint64 {
if m != nil {
@ -2695,7 +2781,7 @@ type ClosedChannelUpdate struct {
func (m *ClosedChannelUpdate) Reset() { *m = ClosedChannelUpdate{} }
func (m *ClosedChannelUpdate) String() string { return proto.CompactTextString(m) }
func (*ClosedChannelUpdate) ProtoMessage() {}
func (*ClosedChannelUpdate) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{70} }
func (*ClosedChannelUpdate) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{71} }
func (m *ClosedChannelUpdate) GetChanId() uint64 {
if m != nil {
@ -2732,7 +2818,7 @@ type SetAliasRequest struct {
func (m *SetAliasRequest) Reset() { *m = SetAliasRequest{} }
func (m *SetAliasRequest) String() string { return proto.CompactTextString(m) }
func (*SetAliasRequest) ProtoMessage() {}
func (*SetAliasRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{71} }
func (*SetAliasRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{72} }
func (m *SetAliasRequest) GetNewAlias() string {
if m != nil {
@ -2747,7 +2833,7 @@ type SetAliasResponse struct {
func (m *SetAliasResponse) Reset() { *m = SetAliasResponse{} }
func (m *SetAliasResponse) String() string { return proto.CompactTextString(m) }
func (*SetAliasResponse) ProtoMessage() {}
func (*SetAliasResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{72} }
func (*SetAliasResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{73} }
type Invoice struct {
// *
@ -2793,7 +2879,7 @@ type Invoice struct {
func (m *Invoice) Reset() { *m = Invoice{} }
func (m *Invoice) String() string { return proto.CompactTextString(m) }
func (*Invoice) ProtoMessage() {}
func (*Invoice) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{73} }
func (*Invoice) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{74} }
func (m *Invoice) GetMemo() string {
if m != nil {
@ -2898,7 +2984,7 @@ type AddInvoiceResponse struct {
func (m *AddInvoiceResponse) Reset() { *m = AddInvoiceResponse{} }
func (m *AddInvoiceResponse) String() string { return proto.CompactTextString(m) }
func (*AddInvoiceResponse) ProtoMessage() {}
func (*AddInvoiceResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{74} }
func (*AddInvoiceResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{75} }
func (m *AddInvoiceResponse) GetRHash() []byte {
if m != nil {
@ -2926,7 +3012,7 @@ type PaymentHash struct {
func (m *PaymentHash) Reset() { *m = PaymentHash{} }
func (m *PaymentHash) String() string { return proto.CompactTextString(m) }
func (*PaymentHash) ProtoMessage() {}
func (*PaymentHash) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{75} }
func (*PaymentHash) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{76} }
func (m *PaymentHash) GetRHashStr() string {
if m != nil {
@ -2950,7 +3036,7 @@ type ListInvoiceRequest struct {
func (m *ListInvoiceRequest) Reset() { *m = ListInvoiceRequest{} }
func (m *ListInvoiceRequest) String() string { return proto.CompactTextString(m) }
func (*ListInvoiceRequest) ProtoMessage() {}
func (*ListInvoiceRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{76} }
func (*ListInvoiceRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{77} }
func (m *ListInvoiceRequest) GetPendingOnly() bool {
if m != nil {
@ -2966,7 +3052,7 @@ type ListInvoiceResponse struct {
func (m *ListInvoiceResponse) Reset() { *m = ListInvoiceResponse{} }
func (m *ListInvoiceResponse) String() string { return proto.CompactTextString(m) }
func (*ListInvoiceResponse) ProtoMessage() {}
func (*ListInvoiceResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{77} }
func (*ListInvoiceResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{78} }
func (m *ListInvoiceResponse) GetInvoices() []*Invoice {
if m != nil {
@ -2981,7 +3067,7 @@ type InvoiceSubscription struct {
func (m *InvoiceSubscription) Reset() { *m = InvoiceSubscription{} }
func (m *InvoiceSubscription) String() string { return proto.CompactTextString(m) }
func (*InvoiceSubscription) ProtoMessage() {}
func (*InvoiceSubscription) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{78} }
func (*InvoiceSubscription) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{79} }
type Payment struct {
// / The payment hash
@ -2999,7 +3085,7 @@ type Payment struct {
func (m *Payment) Reset() { *m = Payment{} }
func (m *Payment) String() string { return proto.CompactTextString(m) }
func (*Payment) ProtoMessage() {}
func (*Payment) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{79} }
func (*Payment) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{80} }
func (m *Payment) GetPaymentHash() string {
if m != nil {
@ -3042,7 +3128,7 @@ type ListPaymentsRequest struct {
func (m *ListPaymentsRequest) Reset() { *m = ListPaymentsRequest{} }
func (m *ListPaymentsRequest) String() string { return proto.CompactTextString(m) }
func (*ListPaymentsRequest) ProtoMessage() {}
func (*ListPaymentsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{80} }
func (*ListPaymentsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{81} }
type ListPaymentsResponse struct {
// / The list of payments
@ -3052,7 +3138,7 @@ type ListPaymentsResponse struct {
func (m *ListPaymentsResponse) Reset() { *m = ListPaymentsResponse{} }
func (m *ListPaymentsResponse) String() string { return proto.CompactTextString(m) }
func (*ListPaymentsResponse) ProtoMessage() {}
func (*ListPaymentsResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{81} }
func (*ListPaymentsResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{82} }
func (m *ListPaymentsResponse) GetPayments() []*Payment {
if m != nil {
@ -3067,7 +3153,7 @@ type DeleteAllPaymentsRequest struct {
func (m *DeleteAllPaymentsRequest) Reset() { *m = DeleteAllPaymentsRequest{} }
func (m *DeleteAllPaymentsRequest) String() string { return proto.CompactTextString(m) }
func (*DeleteAllPaymentsRequest) ProtoMessage() {}
func (*DeleteAllPaymentsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{82} }
func (*DeleteAllPaymentsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{83} }
type DeleteAllPaymentsResponse struct {
}
@ -3075,7 +3161,7 @@ type DeleteAllPaymentsResponse struct {
func (m *DeleteAllPaymentsResponse) Reset() { *m = DeleteAllPaymentsResponse{} }
func (m *DeleteAllPaymentsResponse) String() string { return proto.CompactTextString(m) }
func (*DeleteAllPaymentsResponse) ProtoMessage() {}
func (*DeleteAllPaymentsResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{83} }
func (*DeleteAllPaymentsResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{84} }
type DebugLevelRequest struct {
Show bool `protobuf:"varint,1,opt,name=show" json:"show,omitempty"`
@ -3085,7 +3171,7 @@ type DebugLevelRequest struct {
func (m *DebugLevelRequest) Reset() { *m = DebugLevelRequest{} }
func (m *DebugLevelRequest) String() string { return proto.CompactTextString(m) }
func (*DebugLevelRequest) ProtoMessage() {}
func (*DebugLevelRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{84} }
func (*DebugLevelRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{85} }
func (m *DebugLevelRequest) GetShow() bool {
if m != nil {
@ -3108,7 +3194,7 @@ type DebugLevelResponse struct {
func (m *DebugLevelResponse) Reset() { *m = DebugLevelResponse{} }
func (m *DebugLevelResponse) String() string { return proto.CompactTextString(m) }
func (*DebugLevelResponse) ProtoMessage() {}
func (*DebugLevelResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{85} }
func (*DebugLevelResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{86} }
func (m *DebugLevelResponse) GetSubSystems() string {
if m != nil {
@ -3125,7 +3211,7 @@ type PayReqString struct {
func (m *PayReqString) Reset() { *m = PayReqString{} }
func (m *PayReqString) String() string { return proto.CompactTextString(m) }
func (*PayReqString) ProtoMessage() {}
func (*PayReqString) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{86} }
func (*PayReqString) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{87} }
func (m *PayReqString) GetPayReq() string {
if m != nil {
@ -3149,7 +3235,7 @@ type PayReq struct {
func (m *PayReq) Reset() { *m = PayReq{} }
func (m *PayReq) String() string { return proto.CompactTextString(m) }
func (*PayReq) ProtoMessage() {}
func (*PayReq) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{87} }
func (*PayReq) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{88} }
func (m *PayReq) GetDestination() string {
if m != nil {
@ -3220,7 +3306,7 @@ type FeeReportRequest struct {
func (m *FeeReportRequest) Reset() { *m = FeeReportRequest{} }
func (m *FeeReportRequest) String() string { return proto.CompactTextString(m) }
func (*FeeReportRequest) ProtoMessage() {}
func (*FeeReportRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{88} }
func (*FeeReportRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{89} }
type ChannelFeeReport struct {
// / The channel that this fee report belongs to.
@ -3236,7 +3322,7 @@ type ChannelFeeReport struct {
func (m *ChannelFeeReport) Reset() { *m = ChannelFeeReport{} }
func (m *ChannelFeeReport) String() string { return proto.CompactTextString(m) }
func (*ChannelFeeReport) ProtoMessage() {}
func (*ChannelFeeReport) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{89} }
func (*ChannelFeeReport) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{90} }
func (m *ChannelFeeReport) GetChanPoint() string {
if m != nil {
@ -3274,7 +3360,7 @@ type FeeReportResponse struct {
func (m *FeeReportResponse) Reset() { *m = FeeReportResponse{} }
func (m *FeeReportResponse) String() string { return proto.CompactTextString(m) }
func (*FeeReportResponse) ProtoMessage() {}
func (*FeeReportResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{90} }
func (*FeeReportResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{91} }
func (m *FeeReportResponse) GetChannelFees() []*ChannelFeeReport {
if m != nil {
@ -3297,7 +3383,7 @@ type FeeUpdateRequest struct {
func (m *FeeUpdateRequest) Reset() { *m = FeeUpdateRequest{} }
func (m *FeeUpdateRequest) String() string { return proto.CompactTextString(m) }
func (*FeeUpdateRequest) ProtoMessage() {}
func (*FeeUpdateRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{91} }
func (*FeeUpdateRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{92} }
type isFeeUpdateRequest_Scope interface {
isFeeUpdateRequest_Scope()
@ -3427,7 +3513,7 @@ type FeeUpdateResponse struct {
func (m *FeeUpdateResponse) Reset() { *m = FeeUpdateResponse{} }
func (m *FeeUpdateResponse) String() string { return proto.CompactTextString(m) }
func (*FeeUpdateResponse) ProtoMessage() {}
func (*FeeUpdateResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{92} }
func (*FeeUpdateResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{93} }
func init() {
proto.RegisterType((*CreateWalletRequest)(nil), "lnrpc.CreateWalletRequest")
@ -3473,6 +3559,7 @@ func init() {
proto.RegisterType((*PendingUpdate)(nil), "lnrpc.PendingUpdate")
proto.RegisterType((*OpenChannelRequest)(nil), "lnrpc.OpenChannelRequest")
proto.RegisterType((*OpenStatusUpdate)(nil), "lnrpc.OpenStatusUpdate")
proto.RegisterType((*PendingHTLC)(nil), "lnrpc.PendingHTLC")
proto.RegisterType((*PendingChannelRequest)(nil), "lnrpc.PendingChannelRequest")
proto.RegisterType((*PendingChannelResponse)(nil), "lnrpc.PendingChannelResponse")
proto.RegisterType((*PendingChannelResponse_PendingChannel)(nil), "lnrpc.PendingChannelResponse.PendingChannel")
@ -5430,299 +5517,303 @@ var _Lightning_serviceDesc = grpc.ServiceDesc{
func init() { proto.RegisterFile("rpc.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 4700 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x3b, 0x5d, 0x6f, 0x1c, 0x4b,
0x56, 0xe9, 0xf1, 0x8c, 0xed, 0x39, 0x33, 0xe3, 0x8f, 0xf2, 0xd7, 0x64, 0x92, 0x9b, 0x4d, 0x6a,
0xa3, 0x1b, 0xe3, 0x5d, 0xd9, 0x89, 0x97, 0xbd, 0x64, 0x13, 0xe0, 0xca, 0xf9, 0xf4, 0x65, 0x7d,
0x73, 0xbd, 0xed, 0xe4, 0x06, 0x76, 0x85, 0x9a, 0xf6, 0x74, 0x79, 0xdc, 0x9b, 0x9e, 0xee, 0xbe,
0xdd, 0x35, 0x76, 0x66, 0xa3, 0x48, 0xe8, 0x82, 0xe0, 0x05, 0xb4, 0x0f, 0x8b, 0x40, 0x3c, 0x80,
0x56, 0x42, 0x3c, 0xc2, 0x1f, 0x40, 0xe2, 0x07, 0x20, 0x90, 0x90, 0xf6, 0x09, 0x09, 0xf1, 0xc4,
0x1f, 0xe0, 0x81, 0x77, 0x54, 0x9f, 0x5d, 0xd5, 0xdd, 0x4e, 0xb2, 0x02, 0xf1, 0xe4, 0xa9, 0x53,
0xa7, 0xcf, 0xa9, 0x3a, 0x75, 0xea, 0x7c, 0xd5, 0x31, 0xb4, 0xb3, 0x74, 0xb8, 0x9d, 0x66, 0x09,
0x4d, 0x50, 0x2b, 0x8a, 0xb3, 0x74, 0x38, 0xb8, 0x3a, 0x4a, 0x92, 0x51, 0x44, 0x76, 0xfc, 0x34,
0xdc, 0xf1, 0xe3, 0x38, 0xa1, 0x3e, 0x0d, 0x93, 0x38, 0x17, 0x48, 0xf8, 0x0e, 0xac, 0x3c, 0xcc,
0x88, 0x4f, 0xc9, 0x4b, 0x3f, 0x8a, 0x08, 0x75, 0xc9, 0x57, 0x13, 0x92, 0x53, 0x34, 0x80, 0xf9,
0xd4, 0xcf, 0xf3, 0xf3, 0x24, 0x0b, 0xfa, 0xce, 0x75, 0x67, 0xb3, 0xeb, 0xea, 0x31, 0x5e, 0x87,
0x55, 0xfb, 0x93, 0x3c, 0x4d, 0xe2, 0x9c, 0x30, 0x52, 0x2f, 0xe2, 0x28, 0x19, 0xbe, 0xfa, 0xa5,
0x48, 0xd9, 0x9f, 0x48, 0x52, 0xff, 0xe5, 0x40, 0xe7, 0x79, 0xe6, 0xc7, 0xb9, 0x3f, 0x64, 0x8b,
0x45, 0x7d, 0x98, 0xa3, 0xaf, 0xbd, 0x53, 0x3f, 0x3f, 0xe5, 0x24, 0xda, 0xae, 0x1a, 0xa2, 0x75,
0x98, 0xf5, 0xc7, 0xc9, 0x24, 0xa6, 0xfd, 0xc6, 0x75, 0x67, 0x73, 0xc6, 0x95, 0x23, 0xf4, 0x6d,
0x58, 0x8e, 0x27, 0x63, 0x6f, 0x98, 0xc4, 0x27, 0x61, 0x36, 0x16, 0x5b, 0xee, 0xcf, 0x5c, 0x77,
0x36, 0x5b, 0x6e, 0x75, 0x02, 0x5d, 0x03, 0x38, 0x66, 0xcb, 0x10, 0x2c, 0x9a, 0x9c, 0x85, 0x01,
0x41, 0x18, 0xba, 0x72, 0x44, 0xc2, 0xd1, 0x29, 0xed, 0xb7, 0x38, 0x21, 0x0b, 0xc6, 0x68, 0xd0,
0x70, 0x4c, 0xbc, 0x9c, 0xfa, 0xe3, 0xb4, 0x3f, 0xcb, 0x57, 0x63, 0x40, 0xf8, 0x7c, 0x42, 0xfd,
0xc8, 0x3b, 0x21, 0x24, 0xef, 0xcf, 0xc9, 0x79, 0x0d, 0xc1, 0x7d, 0x58, 0x7f, 0x4a, 0xa8, 0xb1,
0xeb, 0x5c, 0x4a, 0x10, 0x1f, 0x00, 0x32, 0xc0, 0x8f, 0x08, 0xf5, 0xc3, 0x28, 0x47, 0x9f, 0x40,
0x97, 0x1a, 0xc8, 0x7d, 0xe7, 0xfa, 0xcc, 0x66, 0x67, 0x17, 0x6d, 0xf3, 0x53, 0xdf, 0x36, 0x3e,
0x70, 0x2d, 0x3c, 0xfc, 0xaf, 0x0e, 0x74, 0x8e, 0x48, 0x1c, 0xa8, 0xf3, 0x41, 0xd0, 0x0c, 0x48,
0x4e, 0xe5, 0xd9, 0xf0, 0xdf, 0xe8, 0x1b, 0xd0, 0x61, 0x7f, 0xbd, 0x9c, 0x66, 0x61, 0x3c, 0xe2,
0xa2, 0x6d, 0xbb, 0xc0, 0x40, 0x47, 0x1c, 0x82, 0x96, 0x60, 0xc6, 0x1f, 0x53, 0x2e, 0xd0, 0x19,
0x97, 0xfd, 0x44, 0x37, 0xa0, 0x9b, 0xfa, 0xd3, 0x31, 0x89, 0x69, 0x21, 0xc4, 0xae, 0xdb, 0x91,
0xb0, 0x7d, 0x26, 0xc5, 0x6d, 0x58, 0x31, 0x51, 0x14, 0xf5, 0x16, 0xa7, 0xbe, 0x6c, 0x60, 0x4a,
0x26, 0xb7, 0x60, 0x51, 0xe1, 0x67, 0x62, 0xb1, 0x5c, 0xac, 0x6d, 0x77, 0x41, 0x82, 0x95, 0x80,
0xfe, 0xcc, 0x81, 0xae, 0xd8, 0x92, 0xd0, 0x1f, 0x74, 0x13, 0x7a, 0xea, 0x4b, 0x92, 0x65, 0x49,
0x26, 0xb5, 0xc6, 0x06, 0xa2, 0x2d, 0x58, 0x52, 0x80, 0x34, 0x23, 0xe1, 0xd8, 0x1f, 0x11, 0xbe,
0xd5, 0xae, 0x5b, 0x81, 0xa3, 0xdd, 0x82, 0x62, 0x96, 0x4c, 0x28, 0xe1, 0x5b, 0xef, 0xec, 0x76,
0xa5, 0xb8, 0x5d, 0x06, 0x73, 0x6d, 0x14, 0xfc, 0xb5, 0x03, 0xdd, 0x87, 0xa7, 0x7e, 0x1c, 0x93,
0xe8, 0x30, 0x09, 0x63, 0xca, 0xd4, 0xe8, 0x64, 0x12, 0x07, 0x61, 0x3c, 0xf2, 0xe8, 0xeb, 0x50,
0x5d, 0x07, 0x0b, 0xc6, 0x16, 0x65, 0x8e, 0x99, 0x90, 0xa4, 0xfc, 0x2b, 0x70, 0x46, 0x2f, 0x99,
0xd0, 0x74, 0x42, 0xbd, 0x30, 0x0e, 0xc8, 0x6b, 0xbe, 0xa6, 0x9e, 0x6b, 0xc1, 0xf0, 0x6f, 0xc2,
0xd2, 0x01, 0xd3, 0xcf, 0x38, 0x8c, 0x47, 0x7b, 0x41, 0x90, 0x91, 0x3c, 0x67, 0x97, 0x26, 0x9d,
0x1c, 0xbf, 0x22, 0x53, 0x29, 0x17, 0x39, 0x62, 0xaa, 0x70, 0x9a, 0xe4, 0x54, 0xf2, 0xe3, 0xbf,
0xf1, 0xcf, 0x1d, 0x58, 0x64, 0xb2, 0xfd, 0xdc, 0x8f, 0xa7, 0x4a, 0x65, 0x0e, 0xa0, 0xcb, 0x48,
0x3d, 0x4f, 0xf6, 0xc4, 0xd5, 0x13, 0xaa, 0xb7, 0x29, 0x65, 0x51, 0xc2, 0xde, 0x36, 0x51, 0x1f,
0xc7, 0x34, 0x9b, 0xba, 0xd6, 0xd7, 0x83, 0x4f, 0x61, 0xb9, 0x82, 0xc2, 0x14, 0xac, 0x58, 0x1f,
0xfb, 0x89, 0x56, 0xa1, 0x75, 0xe6, 0x47, 0x13, 0x22, 0x2f, 0xba, 0x18, 0xdc, 0x6b, 0xdc, 0x75,
0xf0, 0xc7, 0xb0, 0x54, 0xf0, 0x94, 0x1a, 0x80, 0xa0, 0xa9, 0x45, 0xdc, 0x76, 0xf9, 0x6f, 0x26,
0x0a, 0x86, 0xf7, 0x30, 0x09, 0xf5, 0xdd, 0x62, 0x78, 0x7e, 0x10, 0x28, 0x05, 0xe1, 0xbf, 0x2f,
0xb2, 0x29, 0xf8, 0x16, 0x2c, 0x1b, 0xdf, 0xbf, 0x83, 0xd1, 0x5f, 0x3b, 0xb0, 0xfc, 0x8c, 0x9c,
0x4b, 0x71, 0x2b, 0x56, 0x77, 0xa1, 0x49, 0xa7, 0x29, 0xe1, 0x98, 0x0b, 0xbb, 0x37, 0xa5, 0xb4,
0x2a, 0x78, 0xdb, 0x72, 0xf8, 0x7c, 0x9a, 0x12, 0x97, 0x7f, 0x81, 0xbf, 0x80, 0x8e, 0x01, 0x44,
0x1b, 0xb0, 0xf2, 0xf2, 0xb3, 0xe7, 0xcf, 0x1e, 0x1f, 0x1d, 0x79, 0x87, 0x2f, 0x1e, 0x7c, 0xff,
0xf1, 0xef, 0x78, 0xfb, 0x7b, 0x47, 0xfb, 0x4b, 0x97, 0xd0, 0x3a, 0xa0, 0x67, 0x8f, 0x8f, 0x9e,
0x3f, 0x7e, 0x64, 0xc1, 0x1d, 0xb4, 0x08, 0x1d, 0x13, 0xd0, 0xc0, 0x03, 0xe8, 0x3f, 0x23, 0xe7,
0x2f, 0x43, 0x1a, 0x93, 0x3c, 0xb7, 0xd9, 0xe3, 0x6d, 0x40, 0xe6, 0x9a, 0xe4, 0x36, 0xfb, 0x30,
0xe7, 0x0b, 0x90, 0xb2, 0xc0, 0x72, 0x88, 0x3f, 0x06, 0x74, 0x14, 0x8e, 0xe2, 0xcf, 0x49, 0x9e,
0xfb, 0x23, 0xa2, 0x36, 0xbb, 0x04, 0x33, 0xe3, 0x7c, 0x24, 0x35, 0x9c, 0xfd, 0xc4, 0xdf, 0x81,
0x15, 0x0b, 0x4f, 0x12, 0xbe, 0x0a, 0xed, 0x3c, 0x1c, 0xc5, 0x3e, 0x9d, 0x64, 0x44, 0x92, 0x2e,
0x00, 0xf8, 0x09, 0xac, 0x7e, 0x49, 0xb2, 0xf0, 0x64, 0xfa, 0x3e, 0xf2, 0x36, 0x9d, 0x46, 0x99,
0xce, 0x63, 0x58, 0x2b, 0xd1, 0x91, 0xec, 0x85, 0x56, 0xc9, 0xf3, 0x9b, 0x77, 0xc5, 0xc0, 0xb8,
0x20, 0x0d, 0xf3, 0x82, 0xe0, 0x17, 0x80, 0x1e, 0x26, 0x71, 0x4c, 0x86, 0xf4, 0x90, 0x90, 0x4c,
0x2d, 0xe6, 0x5b, 0x86, 0x0e, 0x75, 0x76, 0x37, 0xe4, 0xc1, 0x96, 0x6f, 0x9d, 0x54, 0x2e, 0x04,
0xcd, 0x94, 0x64, 0x63, 0x4e, 0x78, 0xde, 0xe5, 0xbf, 0xf1, 0x0e, 0xac, 0x58, 0x64, 0x0b, 0x99,
0xa7, 0x84, 0x64, 0x9e, 0x5c, 0x5d, 0xcb, 0x55, 0x43, 0x7c, 0x07, 0xd6, 0x1e, 0x85, 0xf9, 0xb0,
0xba, 0x14, 0xf6, 0xc9, 0xe4, 0xd8, 0x2b, 0xae, 0x8e, 0x1a, 0x32, 0xf7, 0x52, 0xfe, 0x44, 0x3a,
0xdb, 0x3f, 0x72, 0xa0, 0xb9, 0xff, 0xfc, 0xe0, 0x21, 0xf3, 0xd4, 0x61, 0x3c, 0x4c, 0xc6, 0xcc,
0x28, 0x0b, 0x71, 0xe8, 0xf1, 0x85, 0x7e, 0xf6, 0x2a, 0xb4, 0xb9, 0x2d, 0x67, 0x9e, 0x90, 0xdb,
0x9f, 0xae, 0x5b, 0x00, 0x98, 0x17, 0x26, 0xaf, 0xd3, 0x30, 0xe3, 0x6e, 0x56, 0x39, 0xcf, 0x26,
0xb7, 0x52, 0xd5, 0x09, 0xfc, 0xcf, 0x4d, 0xe8, 0xed, 0x0d, 0x69, 0x78, 0x46, 0xa4, 0xd5, 0xe4,
0x5c, 0x39, 0x40, 0xae, 0x47, 0x8e, 0x98, 0x7d, 0xcf, 0xc8, 0x38, 0xa1, 0xc4, 0xb3, 0x8e, 0xc9,
0x06, 0x32, 0xac, 0xa1, 0x20, 0xe4, 0xa5, 0xcc, 0xfe, 0xf2, 0xf5, 0xb5, 0x5d, 0x1b, 0xc8, 0x44,
0xc6, 0x00, 0x4c, 0xca, 0x6c, 0x65, 0x4d, 0x57, 0x0d, 0x99, 0x3c, 0x86, 0x7e, 0xea, 0x0f, 0x43,
0x3a, 0xe5, 0x4e, 0x6a, 0xc6, 0xd5, 0x63, 0x46, 0x3b, 0x4a, 0x86, 0x7e, 0xe4, 0x1d, 0xfb, 0x91,
0x1f, 0x0f, 0x89, 0x74, 0xf8, 0x36, 0x10, 0x7d, 0x0c, 0x0b, 0x72, 0x49, 0x0a, 0x4d, 0xf8, 0xfd,
0x12, 0x94, 0xc5, 0x06, 0xc3, 0x64, 0x3c, 0x0e, 0x29, 0x0b, 0x05, 0xfa, 0xf3, 0x22, 0x36, 0x28,
0x20, 0x7c, 0x27, 0x62, 0x74, 0x2e, 0x64, 0xd8, 0x16, 0xdc, 0x2c, 0x20, 0xa3, 0x72, 0x42, 0x88,
0x97, 0x92, 0xcc, 0x7b, 0x75, 0xde, 0x07, 0x41, 0xa5, 0x80, 0xb0, 0xd3, 0x98, 0xc4, 0x39, 0xa1,
0x34, 0x22, 0x81, 0x5e, 0x50, 0x87, 0xa3, 0x55, 0x27, 0xd0, 0x6d, 0x58, 0x11, 0xd1, 0x49, 0xee,
0xd3, 0x24, 0x3f, 0x0d, 0x73, 0x2f, 0x27, 0x31, 0xed, 0x77, 0x39, 0x7e, 0xdd, 0x14, 0xba, 0x0b,
0x1b, 0x25, 0x70, 0x46, 0x86, 0x24, 0x3c, 0x23, 0x41, 0xbf, 0xc7, 0xbf, 0xba, 0x68, 0x1a, 0x5d,
0x87, 0x0e, 0x0b, 0xca, 0x26, 0x69, 0xe0, 0x53, 0x92, 0xf7, 0x17, 0xf8, 0x39, 0x98, 0x20, 0x74,
0x07, 0x7a, 0x29, 0x11, 0xee, 0xef, 0x94, 0x46, 0xc3, 0xbc, 0xbf, 0xc8, 0x7d, 0x4e, 0x47, 0x5e,
0x36, 0xa6, 0xbf, 0xae, 0x8d, 0x81, 0xd7, 0x60, 0xe5, 0x20, 0xcc, 0xa9, 0xd4, 0x25, 0x6d, 0xdf,
0xf6, 0x61, 0xd5, 0x06, 0xcb, 0xdb, 0x76, 0x1b, 0xe6, 0xa5, 0x62, 0xe4, 0xfd, 0x0e, 0x27, 0xbe,
0x2a, 0x89, 0x5b, 0x3a, 0xe9, 0x6a, 0x2c, 0xfc, 0x87, 0x0d, 0x68, 0xb2, 0x9b, 0x74, 0xf1, 0xad,
0x33, 0xaf, 0x70, 0xc3, 0xba, 0xc2, 0xa6, 0x41, 0x9d, 0xb1, 0x0c, 0x2a, 0x0f, 0x46, 0xa7, 0x94,
0x48, 0x79, 0x0b, 0x9d, 0x34, 0x20, 0xc5, 0x7c, 0x46, 0x86, 0x67, 0x5c, 0x31, 0xf5, 0x3c, 0x83,
0x30, 0xb5, 0xcd, 0x7d, 0x2a, 0xbe, 0x16, 0x5a, 0xa9, 0xc7, 0x6a, 0x8e, 0x7f, 0x39, 0x57, 0xcc,
0xf1, 0xef, 0xfa, 0x30, 0x17, 0xc6, 0xc7, 0xc9, 0x24, 0x0e, 0xb8, 0x06, 0xce, 0xbb, 0x6a, 0xc8,
0x2e, 0x79, 0xca, 0x03, 0x8f, 0x70, 0x4c, 0xa4, 0xea, 0x15, 0x00, 0x8c, 0x58, 0x84, 0x91, 0x73,
0x9b, 0xa2, 0x85, 0xfc, 0x09, 0x2c, 0x1b, 0x30, 0x29, 0xe1, 0x1b, 0xd0, 0x62, 0xbb, 0x57, 0xa1,
0xaa, 0x3a, 0x3b, 0x6e, 0x8c, 0xc4, 0x0c, 0x5e, 0x82, 0x85, 0xa7, 0x84, 0x7e, 0x16, 0x9f, 0x24,
0x8a, 0xd2, 0x7f, 0x37, 0x60, 0x51, 0x83, 0x24, 0xa1, 0x4d, 0x58, 0x0c, 0x03, 0x12, 0xd3, 0x90,
0x4e, 0x3d, 0x2b, 0x90, 0x29, 0x83, 0x99, 0x79, 0xf7, 0xa3, 0xd0, 0xcf, 0xa5, 0x81, 0x10, 0x03,
0xb4, 0x0b, 0xab, 0x4c, 0xb7, 0x94, 0xba, 0xe8, 0x63, 0x17, 0xf1, 0x53, 0xed, 0x1c, 0xbb, 0x0e,
0x0c, 0x2e, 0x0c, 0x50, 0xf1, 0x89, 0x30, 0x66, 0x75, 0x53, 0x4c, 0x6a, 0x82, 0x12, 0xdb, 0x72,
0x8b, 0xe3, 0x15, 0x80, 0x4a, 0x4a, 0x31, 0x2b, 0x62, 0xb7, 0x72, 0x4a, 0x61, 0xa4, 0x25, 0xf3,
0x95, 0xb4, 0x64, 0x13, 0x16, 0xf3, 0x69, 0x3c, 0x24, 0x81, 0x47, 0x13, 0xc6, 0x37, 0x8c, 0xf9,
0xe9, 0xcc, 0xbb, 0x65, 0x30, 0x4f, 0xa0, 0x48, 0x4e, 0x63, 0x42, 0xb9, 0x5d, 0x98, 0x77, 0xd5,
0x90, 0x99, 0x58, 0x8e, 0x22, 0x94, 0xbe, 0xed, 0xca, 0x11, 0xfe, 0x09, 0x77, 0x75, 0x3a, 0x47,
0x7a, 0xc1, 0xef, 0x21, 0xba, 0x02, 0x6d, 0xc1, 0x3f, 0x3f, 0xf5, 0x55, 0x36, 0xc7, 0x01, 0x47,
0xa7, 0x3e, 0x4b, 0x01, 0xac, 0x2d, 0x09, 0x8d, 0xef, 0x70, 0xd8, 0xbe, 0xd8, 0xd1, 0x4d, 0x58,
0x50, 0xd9, 0x57, 0xee, 0x45, 0xe4, 0x84, 0xaa, 0x98, 0x35, 0x9e, 0x8c, 0x19, 0xbb, 0xfc, 0x80,
0x9c, 0x50, 0xfc, 0x0c, 0x96, 0xe5, 0x6d, 0xfb, 0x22, 0x25, 0x8a, 0xf5, 0xf7, 0xca, 0xd6, 0x5c,
0xb8, 0xdb, 0x15, 0xa9, 0x45, 0x66, 0xa0, 0x5d, 0x32, 0xf1, 0xd8, 0x05, 0x24, 0xa7, 0x1f, 0x46,
0x49, 0x4e, 0x24, 0x41, 0x0c, 0xdd, 0x61, 0x94, 0xe4, 0xe5, 0x68, 0xdc, 0x84, 0x31, 0xb9, 0xe5,
0x93, 0xe1, 0x90, 0xdd, 0x52, 0xe1, 0xb0, 0xd5, 0x10, 0x13, 0x58, 0xe1, 0xc4, 0x94, 0x59, 0xd0,
0x41, 0xde, 0x87, 0xaf, 0xb2, 0x3b, 0x34, 0x93, 0x83, 0x55, 0x68, 0x9d, 0x24, 0xd9, 0x90, 0x48,
0x46, 0x62, 0x80, 0xff, 0xcd, 0x81, 0x65, 0xce, 0xe7, 0x88, 0xfa, 0x74, 0x92, 0xcb, 0xa5, 0xff,
0x3a, 0xf4, 0xd8, 0x32, 0x89, 0x52, 0x53, 0xc9, 0x65, 0x55, 0xdf, 0x28, 0x0e, 0x15, 0xc8, 0xfb,
0x97, 0x5c, 0x1b, 0x19, 0x7d, 0x0a, 0x5d, 0x33, 0xfd, 0xe5, 0x0c, 0x3b, 0xbb, 0x97, 0xd5, 0x12,
0x2b, 0xa7, 0xbe, 0x7f, 0xc9, 0xb5, 0x3e, 0x40, 0xf7, 0x01, 0xb8, 0x8f, 0xe4, 0x64, 0x65, 0x26,
0x74, 0xd9, 0xde, 0xa1, 0x21, 0xe8, 0xfd, 0x4b, 0xae, 0x81, 0xfe, 0x60, 0x1e, 0x66, 0x85, 0x51,
0xc7, 0x4f, 0xa1, 0x67, 0xad, 0xd4, 0x8a, 0xa5, 0xbb, 0x22, 0x96, 0xae, 0xe4, 0x38, 0x8d, 0x9a,
0x1c, 0xe7, 0x3f, 0x1c, 0x40, 0x4c, 0x53, 0x4a, 0x67, 0xf1, 0x31, 0x2c, 0x50, 0x3f, 0x1b, 0x11,
0xea, 0xd9, 0x61, 0x54, 0x09, 0xca, 0xbd, 0x4f, 0x12, 0x58, 0xb1, 0x44, 0xd7, 0x35, 0x41, 0x68,
0x1b, 0x90, 0x31, 0x54, 0x89, 0xab, 0xb0, 0xdb, 0x35, 0x33, 0xcc, 0xc0, 0x88, 0x40, 0x40, 0xa5,
0x6c, 0x32, 0x76, 0x6a, 0x72, 0xdb, 0x59, 0x3b, 0xc7, 0xeb, 0x24, 0x13, 0x96, 0x15, 0xfb, 0x54,
0x45, 0x1b, 0x6a, 0x8c, 0x7f, 0xe1, 0xc0, 0x12, 0xdb, 0xa0, 0xa5, 0x04, 0xf7, 0x80, 0x2b, 0xd0,
0x07, 0xea, 0x80, 0x85, 0xfb, 0xbf, 0x57, 0x81, 0xbb, 0xd0, 0xe6, 0x04, 0x93, 0x94, 0xc4, 0x52,
0x03, 0xfa, 0xb6, 0x06, 0x14, 0x57, 0x77, 0xff, 0x92, 0x5b, 0x20, 0x1b, 0xe7, 0xbf, 0x01, 0x6b,
0x72, 0x95, 0xf6, 0xc1, 0xe1, 0x3f, 0x06, 0x58, 0x2f, 0xcf, 0x68, 0x2f, 0x2d, 0x43, 0x8f, 0x28,
0x1c, 0x1f, 0x27, 0x3a, 0x8a, 0x71, 0xcc, 0xa8, 0xc4, 0x9a, 0x42, 0x27, 0xb0, 0xa6, 0x8c, 0x39,
0xe3, 0x5f, 0x98, 0xee, 0x06, 0xf7, 0x42, 0xb7, 0x6d, 0x79, 0x95, 0xf8, 0x29, 0xb0, 0xa9, 0x5d,
0xf5, 0xe4, 0xd0, 0x08, 0xfa, 0xda, 0x69, 0x48, 0x13, 0x62, 0x38, 0x16, 0xc6, 0xea, 0x5b, 0xef,
0x66, 0xc5, 0xaf, 0x4c, 0xa0, 0xa0, 0x17, 0x12, 0x43, 0xaf, 0xe1, 0x9a, 0x9a, 0xe3, 0x36, 0xa2,
0xca, 0xae, 0xf9, 0x21, 0x3b, 0x7b, 0xc2, 0xbe, 0xb5, 0x79, 0xbe, 0x87, 0xee, 0xe0, 0x9f, 0x1c,
0x58, 0xb0, 0xa9, 0x31, 0x17, 0x24, 0x63, 0x59, 0x75, 0x0d, 0x94, 0x2b, 0x2e, 0x81, 0xab, 0xd1,
0x78, 0xa3, 0x2e, 0x1a, 0x37, 0x63, 0xee, 0x99, 0xf7, 0xc5, 0xdc, 0xcd, 0x0f, 0x8b, 0xb9, 0x5b,
0x75, 0x31, 0xf7, 0xe0, 0xe7, 0x0d, 0x40, 0xd5, 0xd3, 0x45, 0x4f, 0x44, 0x3a, 0x10, 0x93, 0x48,
0x5e, 0xa8, 0x6f, 0x7f, 0x90, 0x82, 0x28, 0xb0, 0xfa, 0x98, 0x29, 0xaa, 0x79, 0x61, 0x4c, 0x9f,
0xd8, 0x73, 0xeb, 0xa6, 0xd0, 0x16, 0x2c, 0x71, 0x57, 0x99, 0x7b, 0x34, 0x8c, 0xa2, 0xe2, 0x66,
0xf5, 0xdc, 0x0a, 0xbc, 0x94, 0x30, 0x34, 0xdf, 0x9f, 0x30, 0xb4, 0xde, 0x9f, 0x30, 0xcc, 0x96,
0x13, 0x86, 0xc1, 0x1b, 0xe8, 0x59, 0x0a, 0xf2, 0x7f, 0x26, 0x9c, 0xb2, 0xeb, 0x15, 0xaa, 0x60,
0xc1, 0x06, 0x5f, 0x37, 0x00, 0x55, 0x75, 0xf4, 0xff, 0x73, 0x09, 0x5c, 0xe1, 0x2c, 0x33, 0x33,
0x23, 0x15, 0xce, 0x32, 0x30, 0x9b, 0xb0, 0x38, 0xf6, 0xe9, 0x24, 0x63, 0x61, 0xa7, 0x95, 0xe2,
0x96, 0xc1, 0x4c, 0x27, 0x8a, 0x93, 0xf4, 0xd4, 0xac, 0x8c, 0x0d, 0xeb, 0xa6, 0xf0, 0xf7, 0x60,
0x55, 0x94, 0xc6, 0x1f, 0x08, 0x66, 0xca, 0xb5, 0xdd, 0x80, 0xee, 0xb9, 0xa8, 0xde, 0x78, 0x49,
0x1c, 0x4d, 0x65, 0x7a, 0xdc, 0x91, 0xb0, 0x2f, 0xe2, 0x68, 0x8a, 0xef, 0xc0, 0x5a, 0xe9, 0xd3,
0xa2, 0xac, 0x60, 0x9b, 0x4d, 0x35, 0x64, 0x06, 0x59, 0xca, 0xc9, 0x66, 0x87, 0x77, 0x61, 0xbd,
0x3c, 0xf1, 0x5e, 0x62, 0x9f, 0x02, 0xfa, 0xc1, 0x84, 0x64, 0x53, 0x5e, 0x1a, 0xd5, 0x45, 0xb0,
0x8d, 0x72, 0xaa, 0x34, 0x9b, 0x4e, 0x8e, 0xbf, 0x4f, 0xa6, 0xaa, 0xa2, 0xdc, 0xd0, 0x15, 0x65,
0x7c, 0x1f, 0x56, 0x2c, 0x02, 0xba, 0xb6, 0x3b, 0xcb, 0xcb, 0xab, 0x2a, 0x8d, 0xb0, 0x4b, 0xb0,
0x72, 0x0e, 0xff, 0x85, 0x03, 0x33, 0xfb, 0x49, 0x6a, 0x66, 0xf7, 0x8e, 0x9d, 0xdd, 0x4b, 0x7b,
0xe4, 0x69, 0x73, 0xd3, 0x90, 0x57, 0xc4, 0x04, 0x32, 0x6b, 0xe2, 0x8f, 0x29, 0x0b, 0xa4, 0x4f,
0x92, 0xec, 0xdc, 0xcf, 0x02, 0xa9, 0x03, 0x25, 0x28, 0x5b, 0x7e, 0x71, 0x13, 0xd9, 0x4f, 0x16,
0x58, 0xf3, 0x12, 0x87, 0x3a, 0x5f, 0x39, 0xc2, 0x3f, 0x75, 0xa0, 0xc5, 0xd7, 0xca, 0x14, 0x47,
0x38, 0x2c, 0xfe, 0x4a, 0xc0, 0x2b, 0x28, 0x8e, 0x50, 0x9c, 0x12, 0xb8, 0xf4, 0x76, 0xd0, 0x28,
0xbf, 0x1d, 0xb0, 0x54, 0x43, 0x8c, 0x8a, 0xa2, 0x7c, 0x01, 0x40, 0xd7, 0xa0, 0x79, 0x9a, 0xa4,
0xca, 0x2d, 0x80, 0x4a, 0x99, 0x93, 0xd4, 0xe5, 0x70, 0xbc, 0x05, 0x8b, 0xcf, 0x92, 0x80, 0x18,
0x59, 0xd7, 0x85, 0xc7, 0x84, 0x7f, 0xdf, 0x81, 0x79, 0x85, 0x8c, 0x36, 0xa1, 0xc9, 0xcc, 0x7b,
0x29, 0xf2, 0xd0, 0x85, 0x2f, 0x86, 0xe7, 0x72, 0x0c, 0x76, 0xdb, 0x78, 0xdc, 0x5f, 0xf8, 0x5e,
0x15, 0xf5, 0x17, 0x7e, 0x8d, 0x85, 0x6b, 0x7c, 0xcd, 0x25, 0x07, 0x50, 0x82, 0xe2, 0x9f, 0x39,
0xd0, 0xb3, 0x78, 0xb0, 0x00, 0x2e, 0xf2, 0x73, 0x2a, 0x8b, 0x05, 0x52, 0x88, 0x26, 0xc8, 0xcc,
0xd0, 0x1b, 0x76, 0x86, 0xae, 0x33, 0xc4, 0x19, 0x33, 0x43, 0xbc, 0x0d, 0x6d, 0x99, 0x8e, 0x13,
0x25, 0x37, 0xf5, 0xb2, 0xc2, 0x38, 0xaa, 0x92, 0x5e, 0x81, 0x84, 0xef, 0x43, 0xc7, 0x98, 0x61,
0x0c, 0x63, 0x42, 0xcf, 0x93, 0xec, 0x95, 0x2a, 0x09, 0xc8, 0xa1, 0xae, 0x38, 0x37, 0x8a, 0x8a,
0x33, 0xfe, 0x3b, 0x07, 0x7a, 0x4c, 0x27, 0xc2, 0x78, 0x74, 0x98, 0x44, 0xe1, 0x70, 0xca, 0x75,
0x43, 0x1d, 0xbf, 0x17, 0x90, 0x88, 0xfa, 0x5a, 0x37, 0x6c, 0x30, 0xf3, 0x98, 0xe3, 0x30, 0xe6,
0x35, 0x0f, 0xa9, 0x19, 0x7a, 0xcc, 0x74, 0x9c, 0x99, 0xf3, 0x63, 0x3f, 0x27, 0xde, 0x98, 0x05,
0x96, 0xd2, 0x80, 0x59, 0x40, 0x66, 0x96, 0x18, 0x20, 0xf3, 0x29, 0xf1, 0xc6, 0x61, 0x14, 0x85,
0x02, 0x57, 0xe8, 0x72, 0xdd, 0x14, 0xfe, 0x87, 0x06, 0x74, 0xa4, 0x41, 0x78, 0x1c, 0x8c, 0x44,
0xfd, 0x4a, 0xba, 0x71, 0x7d, 0xd1, 0x0c, 0x88, 0x9a, 0xb7, 0x1c, 0xbf, 0x01, 0x29, 0x1f, 0xe0,
0x4c, 0xf5, 0x00, 0x59, 0x32, 0x9d, 0x04, 0xe4, 0x0e, 0x8f, 0x30, 0xc4, 0x03, 0x5d, 0x01, 0x50,
0xb3, 0xbb, 0x7c, 0xb6, 0x55, 0xcc, 0x72, 0x80, 0x15, 0x53, 0xcc, 0x96, 0x62, 0x8a, 0xbb, 0xd0,
0x95, 0x64, 0xb8, 0xdc, 0x79, 0x51, 0xa4, 0x50, 0x65, 0xeb, 0x4c, 0x5c, 0x0b, 0x53, 0x7d, 0xb9,
0xab, 0xbe, 0x9c, 0x7f, 0xdf, 0x97, 0x0a, 0x13, 0xaf, 0xc1, 0x8a, 0x14, 0xde, 0xd3, 0xcc, 0x4f,
0x4f, 0x95, 0x91, 0x0d, 0xf4, 0x6b, 0x11, 0x07, 0xa3, 0x2d, 0x68, 0xb1, 0xcf, 0x94, 0x9d, 0xab,
0xbf, 0x5e, 0x02, 0x05, 0x6d, 0x42, 0x8b, 0x04, 0x23, 0xa2, 0x82, 0x5a, 0x64, 0x87, 0xe2, 0xec,
0x8c, 0x5c, 0x81, 0xc0, 0x2e, 0x3b, 0x83, 0x96, 0x2e, 0xbb, 0x6d, 0x23, 0x67, 0xd9, 0xf0, 0xb3,
0x00, 0xaf, 0x02, 0x7a, 0x26, 0xb4, 0xd6, 0xac, 0xc8, 0xfc, 0xc1, 0x0c, 0x74, 0x0c, 0x30, 0xbb,
0xb7, 0x23, 0xb6, 0x60, 0x2f, 0x08, 0xfd, 0x31, 0xa1, 0x24, 0x93, 0x9a, 0x5a, 0x82, 0x72, 0x53,
0x7a, 0x36, 0xf2, 0x92, 0x09, 0xf5, 0x02, 0x32, 0xca, 0x88, 0xc8, 0x74, 0x1d, 0xb7, 0x04, 0x65,
0x78, 0x63, 0xff, 0xb5, 0x89, 0x27, 0xf4, 0xa1, 0x04, 0x55, 0xf5, 0x15, 0x21, 0xa3, 0x66, 0x51,
0x5f, 0x11, 0x12, 0x29, 0x5b, 0x9c, 0x56, 0x8d, 0xc5, 0xf9, 0x04, 0xd6, 0x85, 0x6d, 0x91, 0x77,
0xd3, 0x2b, 0xa9, 0xc9, 0x05, 0xb3, 0x2c, 0x52, 0x63, 0x6b, 0x56, 0x0a, 0x9e, 0x87, 0x3f, 0x11,
0x85, 0x5d, 0xc7, 0xad, 0xc0, 0x19, 0x2e, 0xbb, 0x8e, 0x16, 0xae, 0x28, 0xf0, 0x56, 0xe0, 0x1c,
0xd7, 0x7f, 0x6d, 0xe3, 0xb6, 0x25, 0x6e, 0x09, 0x8e, 0x7b, 0xd0, 0x39, 0xa2, 0x49, 0xaa, 0x0e,
0x65, 0x01, 0xba, 0x62, 0x28, 0x8b, 0xfa, 0x57, 0xe0, 0x32, 0xd7, 0xa2, 0xe7, 0x49, 0x9a, 0x44,
0xc9, 0x68, 0x7a, 0x34, 0x39, 0xce, 0x87, 0x59, 0x98, 0xb2, 0x80, 0x13, 0xff, 0x8b, 0x03, 0x2b,
0xd6, 0xac, 0xcc, 0x28, 0x7f, 0x55, 0xa8, 0xb4, 0xae, 0xc3, 0x0a, 0xc5, 0x5b, 0x36, 0x0c, 0x9f,
0x40, 0x14, 0xc9, 0xf1, 0x0b, 0x59, 0x9a, 0xdd, 0x83, 0x45, 0xb5, 0x32, 0xf5, 0xa1, 0xd0, 0xc2,
0x7e, 0x55, 0x0b, 0xe5, 0xf7, 0x0b, 0xf2, 0x03, 0x45, 0xe2, 0x37, 0x44, 0x30, 0x46, 0x02, 0xbe,
0x47, 0x95, 0x2f, 0x0d, 0xd4, 0xf7, 0x66, 0x00, 0xa8, 0x56, 0x30, 0xd4, 0xc0, 0x1c, 0xff, 0x89,
0x03, 0x50, 0xac, 0x8e, 0x29, 0x46, 0x61, 0xbc, 0x1d, 0x5e, 0xd5, 0x2a, 0x00, 0x2c, 0x74, 0xd2,
0x55, 0xc2, 0xc2, 0x1f, 0x74, 0x14, 0x8c, 0xc5, 0x22, 0xb7, 0x60, 0x71, 0x14, 0x25, 0xc7, 0xdc,
0xbb, 0xf2, 0xf7, 0xa3, 0x5c, 0x3e, 0x6d, 0x2c, 0x08, 0xf0, 0x13, 0x09, 0x2d, 0x9c, 0x47, 0xd3,
0x70, 0x1e, 0xf8, 0x4f, 0x1b, 0xba, 0x7e, 0x55, 0xec, 0xf9, 0xc2, 0x5b, 0x86, 0x76, 0x2b, 0xc6,
0xf1, 0x82, 0x7a, 0x11, 0x4f, 0xa2, 0x0f, 0xdf, 0x9b, 0x26, 0xdd, 0x87, 0x85, 0x4c, 0x58, 0x1f,
0x65, 0x9a, 0x9a, 0xef, 0x30, 0x4d, 0xbd, 0xcc, 0xf2, 0x3b, 0xbf, 0x02, 0x4b, 0x7e, 0x70, 0x46,
0x32, 0x1a, 0xf2, 0x30, 0x98, 0xbb, 0x77, 0x61, 0x50, 0x17, 0x0d, 0x38, 0xf7, 0xba, 0xb7, 0x60,
0x51, 0x3e, 0x27, 0x69, 0x4c, 0xf9, 0x3c, 0x5f, 0x80, 0x19, 0x22, 0xfe, 0x1b, 0x47, 0xd6, 0xca,
0xec, 0x33, 0xbc, 0x58, 0x22, 0xe6, 0xee, 0x1a, 0xa5, 0xdd, 0x7d, 0x53, 0x96, 0xbe, 0x02, 0x15,
0x6b, 0xcb, 0x02, 0xa2, 0x00, 0xca, 0x32, 0xa3, 0x2d, 0xd2, 0xe6, 0x87, 0x88, 0x14, 0x6f, 0xc3,
0xe2, 0x11, 0xa1, 0x7b, 0xec, 0x04, 0x95, 0x61, 0xbc, 0x02, 0xed, 0x98, 0x9c, 0x7b, 0xe2, 0x88,
0x85, 0x1b, 0x9f, 0x8f, 0xc9, 0x39, 0xc7, 0xc1, 0x08, 0x96, 0x0a, 0x7c, 0x79, 0xeb, 0xfe, 0x6a,
0x06, 0xe6, 0x3e, 0x8b, 0xcf, 0x92, 0x70, 0xc8, 0x8b, 0x59, 0x63, 0x32, 0x4e, 0xd4, 0xc3, 0x30,
0xfb, 0xcd, 0xa2, 0x02, 0xfe, 0xe6, 0x91, 0x52, 0x59, 0x65, 0x52, 0x43, 0xe6, 0x21, 0xb3, 0xa2,
0x0b, 0x41, 0x68, 0x9b, 0x01, 0x61, 0xd1, 0x64, 0x66, 0x36, 0x56, 0xc8, 0x51, 0xf1, 0x2a, 0xde,
0x32, 0x5e, 0xc5, 0x79, 0xd9, 0x52, 0x3c, 0xe7, 0xf0, 0x23, 0x99, 0x77, 0xd5, 0x90, 0x47, 0xbd,
0x19, 0x11, 0x79, 0x27, 0xf7, 0xb5, 0x73, 0x32, 0xea, 0x35, 0x81, 0xcc, 0x1f, 0x8b, 0x0f, 0x04,
0x8e, 0xb0, 0x57, 0x26, 0x88, 0xc5, 0x27, 0xe5, 0xde, 0x8c, 0xb6, 0x50, 0x93, 0x12, 0x98, 0x19,
0xb5, 0x80, 0x68, 0xdb, 0x23, 0xf6, 0x00, 0xa2, 0xcb, 0xa2, 0x0c, 0x37, 0x62, 0x66, 0xf1, 0x2c,
0x25, 0x47, 0x3c, 0x8e, 0xf1, 0xa3, 0xe8, 0xd8, 0x1f, 0xbe, 0xf2, 0x78, 0xf0, 0xd4, 0x15, 0xb5,
0x03, 0x0b, 0xc8, 0x56, 0x3d, 0x8c, 0xe8, 0x99, 0x27, 0x49, 0xf4, 0xc4, 0x2b, 0x92, 0x01, 0xc2,
0x5f, 0x02, 0xda, 0x0b, 0x02, 0x79, 0x42, 0x3a, 0xa3, 0x28, 0x64, 0xeb, 0x58, 0xb2, 0xad, 0xd9,
0x63, 0xa3, 0x76, 0x8f, 0xf8, 0x31, 0x74, 0x0e, 0x8d, 0x46, 0x17, 0x7e, 0x98, 0xaa, 0xc5, 0x45,
0x2a, 0x80, 0x01, 0x31, 0x18, 0x36, 0x4c, 0x86, 0xf8, 0xd7, 0x00, 0x1d, 0x84, 0x39, 0xd5, 0xeb,
0xd3, 0xb9, 0x9e, 0xae, 0x38, 0x19, 0xb9, 0x9e, 0x84, 0xf1, 0x5c, 0x6f, 0x4f, 0x3c, 0x75, 0x95,
0x37, 0xb6, 0x05, 0xf3, 0xa1, 0x00, 0x29, 0x5b, 0xbe, 0x20, 0x2f, 0x81, 0xc2, 0xd4, 0xf3, 0x2c,
0x28, 0x91, 0x40, 0xcb, 0x55, 0xfc, 0xd4, 0x81, 0x39, 0xb9, 0x35, 0xe6, 0x52, 0xad, 0x16, 0x1f,
0xb1, 0x31, 0x0b, 0x56, 0xdf, 0xa5, 0x51, 0xd5, 0xba, 0x99, 0x3a, 0xad, 0x43, 0xd0, 0x4c, 0x7d,
0x7a, 0xca, 0xe3, 0xed, 0xb6, 0xcb, 0x7f, 0xab, 0xbc, 0xaa, 0xa5, 0xf3, 0x2a, 0xf5, 0xac, 0x27,
0x17, 0xa5, 0x5f, 0x9c, 0x1e, 0x88, 0x67, 0xbd, 0x02, 0x5c, 0xc8, 0x40, 0x2e, 0xb0, 0x2c, 0x03,
0x89, 0xea, 0xea, 0x79, 0x3c, 0x80, 0xfe, 0x23, 0x12, 0x11, 0x4a, 0xf6, 0xa2, 0xa8, 0x4c, 0xff,
0x0a, 0x5c, 0xae, 0x99, 0x93, 0xf7, 0xfe, 0x09, 0x2c, 0x3f, 0x22, 0xc7, 0x93, 0xd1, 0x01, 0x39,
0x2b, 0xca, 0xcf, 0x08, 0x9a, 0xf9, 0x69, 0x72, 0x2e, 0xcf, 0x8b, 0xff, 0x46, 0x1f, 0x01, 0x44,
0x0c, 0xc7, 0xcb, 0x53, 0x32, 0x54, 0x6d, 0x0a, 0x1c, 0x72, 0x94, 0x92, 0x21, 0xfe, 0x04, 0x90,
0x49, 0x47, 0x6e, 0x81, 0xdd, 0xc6, 0xc9, 0xb1, 0x97, 0x4f, 0x73, 0x4a, 0xc6, 0xca, 0x10, 0x99,
0x20, 0x7c, 0x0b, 0xba, 0x87, 0xfe, 0xd4, 0x25, 0x5f, 0xc9, 0xce, 0x29, 0x96, 0xbe, 0xf9, 0x53,
0xa6, 0x9e, 0x3a, 0x7d, 0xe3, 0xd3, 0xf8, 0x1f, 0x1b, 0x30, 0x2b, 0x30, 0x19, 0xd5, 0x80, 0xe4,
0x34, 0x8c, 0x45, 0x05, 0x58, 0x52, 0x35, 0x40, 0x95, 0xf3, 0x6e, 0xd4, 0x9c, 0xb7, 0x0c, 0xb3,
0xd4, 0x93, 0xae, 0x3c, 0x58, 0x0b, 0xc6, 0xb3, 0xd3, 0x70, 0x4c, 0x44, 0x63, 0x5c, 0x53, 0x66,
0xa7, 0x0a, 0x50, 0xca, 0x93, 0x8b, 0x3b, 0x2f, 0xd6, 0xa7, 0x14, 0x51, 0xba, 0x16, 0x13, 0x54,
0x6b, 0x59, 0xe6, 0x44, 0xab, 0x54, 0xc5, 0xb2, 0x54, 0x2c, 0xc8, 0xfc, 0x07, 0x58, 0x10, 0x11,
0x7b, 0x59, 0x16, 0x04, 0xc1, 0xd2, 0x13, 0x42, 0x5c, 0x92, 0x26, 0x99, 0x6e, 0x3f, 0xfb, 0x4b,
0x07, 0x96, 0xa4, 0x57, 0xd1, 0x73, 0xe8, 0x86, 0xe5, 0x82, 0x9c, 0xba, 0x5a, 0xe7, 0x4d, 0xe8,
0xf1, 0x24, 0x8c, 0x65, 0x58, 0x3c, 0xe3, 0x92, 0x15, 0x08, 0x0b, 0xc8, 0xd6, 0xa4, 0x4a, 0x72,
0xe3, 0x30, 0x92, 0x02, 0x36, 0x41, 0xcc, 0x5d, 0xaa, 0x24, 0x8d, 0x8b, 0xd7, 0x71, 0xf5, 0x18,
0x1f, 0xc2, 0xb2, 0xb1, 0x5e, 0xa9, 0x50, 0xf7, 0x41, 0x3d, 0x3d, 0x89, 0x82, 0x82, 0xb8, 0x17,
0x1b, 0xb6, 0x83, 0x2c, 0x3e, 0xb3, 0x90, 0xf1, 0xdf, 0x3b, 0x5c, 0x04, 0x32, 0x0e, 0xd3, 0x7d,
0x27, 0xb3, 0x22, 0x34, 0x12, 0xda, 0xbe, 0x7f, 0xc9, 0x95, 0x63, 0xf4, 0xdd, 0x0f, 0x8c, 0x6e,
0xf4, 0x2b, 0xd1, 0x05, 0xb2, 0x99, 0xa9, 0x93, 0xcd, 0x3b, 0x76, 0xfe, 0x60, 0x0e, 0x5a, 0xf9,
0x30, 0x49, 0x09, 0x5e, 0xe1, 0x22, 0x50, 0xeb, 0x15, 0x22, 0xd8, 0xfd, 0x77, 0x07, 0x16, 0x44,
0x79, 0x4c, 0x34, 0xa0, 0x92, 0x0c, 0xb1, 0xfc, 0xcb, 0xe8, 0x6b, 0x45, 0x3a, 0xfc, 0xac, 0xf6,
0xc7, 0x0e, 0xae, 0xd4, 0xce, 0xa9, 0xd8, 0xfb, 0xeb, 0x5f, 0xfc, 0xe7, 0xcf, 0x1a, 0x6b, 0x78,
0x69, 0xe7, 0xec, 0xce, 0x0e, 0x37, 0x71, 0xe4, 0x9c, 0x63, 0xdc, 0x73, 0xb6, 0x18, 0x17, 0xb3,
0xe5, 0x55, 0x73, 0xa9, 0x69, 0x9d, 0xd5, 0x5c, 0x6a, 0x7b, 0x64, 0x2d, 0x2e, 0x13, 0x8e, 0xa1,
0xb9, 0xec, 0xfe, 0xed, 0x15, 0x68, 0xeb, 0x44, 0x11, 0xfd, 0x18, 0x7a, 0x56, 0x29, 0x10, 0x29,
0xc2, 0x75, 0xb5, 0xc5, 0xc1, 0xd5, 0xfa, 0x49, 0xc9, 0xf6, 0x1a, 0x67, 0xdb, 0x47, 0xeb, 0x8c,
0xad, 0xac, 0xf5, 0xed, 0xf0, 0xd2, 0xa5, 0x78, 0x69, 0x7e, 0x05, 0x0b, 0x76, 0xa9, 0x10, 0x5d,
0xb5, 0x4f, 0xbb, 0xc4, 0xed, 0xa3, 0x0b, 0x66, 0x25, 0xbb, 0xab, 0x9c, 0xdd, 0x3a, 0x5a, 0x35,
0xd9, 0xe9, 0x04, 0x8e, 0xf0, 0xde, 0x00, 0xb3, 0x67, 0x16, 0x29, 0x7a, 0xf5, 0xbd, 0xb4, 0x83,
0xcb, 0xd5, 0xfe, 0x58, 0xd9, 0x50, 0x8b, 0xfb, 0x9c, 0x15, 0x42, 0x5c, 0xa0, 0x66, 0xcb, 0x2c,
0xfa, 0x11, 0xb4, 0x75, 0xe3, 0x1f, 0xda, 0x30, 0xda, 0x1c, 0xcd, 0x56, 0xc2, 0x41, 0xbf, 0x3a,
0x51, 0x77, 0x54, 0x26, 0x65, 0xa6, 0x10, 0x07, 0xb0, 0x26, 0x3d, 0xee, 0x31, 0xf9, 0x65, 0x76,
0x52, 0xd3, 0xe9, 0x7b, 0xdb, 0x41, 0xf7, 0x61, 0x5e, 0xf5, 0x42, 0xa2, 0xf5, 0xfa, 0x86, 0xcc,
0xc1, 0x46, 0x05, 0x2e, 0xed, 0xc2, 0x1e, 0x40, 0xd1, 0xfa, 0x87, 0xfa, 0x17, 0x75, 0x28, 0x6a,
0x21, 0xd6, 0xf4, 0x09, 0x8e, 0x78, 0xe7, 0xa3, 0xdd, 0x59, 0x88, 0xbe, 0x51, 0xe0, 0xd7, 0xf6,
0x1c, 0xbe, 0x83, 0x20, 0x5e, 0xe7, 0xb2, 0x5b, 0x42, 0x0b, 0x4c, 0x76, 0x31, 0x39, 0x57, 0x5d,
0x32, 0x8f, 0xa0, 0x63, 0xb4, 0x13, 0x22, 0x45, 0xa1, 0xda, 0x8a, 0x38, 0x18, 0xd4, 0x4d, 0xc9,
0xe5, 0xfe, 0x16, 0xf4, 0xac, 0xbe, 0x40, 0x7d, 0x33, 0xea, 0xba, 0x0e, 0xf5, 0xcd, 0xa8, 0x6f,
0x25, 0xfc, 0x21, 0x74, 0x8c, 0x2e, 0x3e, 0x64, 0x3c, 0xa6, 0x96, 0xba, 0xf4, 0xf4, 0x8a, 0x6a,
0x9a, 0xfe, 0xf0, 0x2a, 0xdf, 0xef, 0x02, 0x6e, 0xb3, 0xfd, 0xf2, 0x56, 0x11, 0xa6, 0x24, 0x3f,
0x86, 0x05, 0xbb, 0x7b, 0x4f, 0xdf, 0xaa, 0xda, 0x3e, 0x40, 0x7d, 0xab, 0x2e, 0x68, 0xf9, 0x93,
0x0a, 0xb9, 0xb5, 0xa2, 0x99, 0xec, 0xbc, 0x91, 0x05, 0xd1, 0xb7, 0xe8, 0x07, 0xcc, 0x74, 0xc8,
0xde, 0x1d, 0x54, 0x74, 0x33, 0xda, 0x1d, 0x3e, 0x5a, 0xdb, 0x2b, 0x6d, 0x3e, 0x78, 0x99, 0x13,
0xef, 0xa0, 0x62, 0x07, 0xe8, 0x73, 0x98, 0x93, 0x3d, 0x3c, 0x68, 0xad, 0xd0, 0x6a, 0xa3, 0xa8,
0x34, 0x58, 0x2f, 0x83, 0x25, 0xb1, 0x15, 0x4e, 0xac, 0x87, 0x3a, 0x8c, 0xd8, 0x88, 0xd0, 0x90,
0xd1, 0x88, 0x60, 0xd1, 0x7e, 0xd6, 0xc9, 0xb5, 0x38, 0x6a, 0x1f, 0x94, 0xb5, 0x38, 0xea, 0xdf,
0x88, 0x6c, 0x23, 0xa3, 0x8c, 0xcb, 0x8e, 0x7a, 0x2b, 0xff, 0x5d, 0xe8, 0x9a, 0x0d, 0x63, 0xda,
0x62, 0xd7, 0x34, 0x97, 0x69, 0x8b, 0x5d, 0xd7, 0x61, 0xa6, 0x8e, 0x16, 0x75, 0x4d, 0x36, 0xe8,
0x87, 0xb0, 0x68, 0xbc, 0x3f, 0x1e, 0x4d, 0xe3, 0xa1, 0x56, 0x9d, 0x6a, 0x4f, 0xc3, 0xa0, 0xce,
0x75, 0xe2, 0x0d, 0x4e, 0x78, 0x19, 0x5b, 0x84, 0x99, 0xda, 0x3c, 0x84, 0x8e, 0xf9, 0xb6, 0xf9,
0x0e, 0xba, 0x1b, 0xc6, 0x94, 0xd9, 0x65, 0x70, 0xdb, 0x41, 0x7f, 0xee, 0x40, 0xd7, 0x6c, 0x75,
0x41, 0x56, 0x5d, 0xa6, 0x44, 0xa7, 0x6f, 0xce, 0x99, 0x84, 0xf0, 0x33, 0xbe, 0xc8, 0xfd, 0xad,
0x27, 0x96, 0x90, 0xdf, 0x58, 0x21, 0xd1, 0xb6, 0xd9, 0xe2, 0xfe, 0xb6, 0x3c, 0x69, 0xf6, 0x7c,
0xbc, 0xbd, 0xed, 0xa0, 0x7b, 0xe2, 0x1f, 0x19, 0x54, 0x7a, 0x82, 0x0c, 0xb3, 0x56, 0x16, 0x97,
0xf9, 0xdf, 0x01, 0x9b, 0xce, 0x6d, 0x07, 0xfd, 0x9e, 0xe8, 0x6a, 0x97, 0xdf, 0x72, 0xa9, 0x7f,
0xe8, 0xf7, 0xf8, 0x26, 0xdf, 0xc9, 0x35, 0x7c, 0xd9, 0xda, 0x49, 0xd9, 0xae, 0x1f, 0x02, 0x14,
0xb9, 0x26, 0x2a, 0x25, 0x5e, 0xda, 0xe2, 0x55, 0xd3, 0x51, 0xfb, 0x34, 0x55, 0x7e, 0x26, 0x8c,
0x40, 0xd7, 0xc8, 0xf2, 0x72, 0x7d, 0x9c, 0xd5, 0x9c, 0x71, 0x30, 0xa8, 0x9b, 0x92, 0xf4, 0xbf,
0xc9, 0xe9, 0x7f, 0x84, 0xae, 0x98, 0xf4, 0x77, 0xde, 0x98, 0x39, 0xe6, 0x5b, 0xf4, 0x25, 0xf4,
0x0e, 0x92, 0xe4, 0xd5, 0x24, 0xd5, 0xe5, 0x0c, 0x3b, 0x6b, 0x62, 0x79, 0xee, 0xa0, 0xb4, 0x29,
0x7c, 0x83, 0x53, 0xbe, 0x82, 0x2e, 0xdb, 0x94, 0x8b, 0xcc, 0xf7, 0x2d, 0xf2, 0x61, 0x59, 0x7b,
0x3b, 0xbd, 0x91, 0x81, 0x4d, 0xc7, 0x4c, 0x40, 0x2b, 0x3c, 0xac, 0xf8, 0x43, 0xf3, 0xc8, 0x15,
0xcd, 0xdb, 0x0e, 0x3a, 0x84, 0xee, 0x23, 0x32, 0x4c, 0x02, 0x22, 0x13, 0x9d, 0x95, 0x62, 0xe5,
0x3a, 0x43, 0x1a, 0xf4, 0x2c, 0xa0, 0x6d, 0x01, 0x52, 0x7f, 0x9a, 0x91, 0xaf, 0x76, 0xde, 0xc8,
0x14, 0xea, 0xad, 0xb2, 0x00, 0x2a, 0xed, 0xb3, 0x2c, 0x40, 0x29, 0x4f, 0xb4, 0x2c, 0x40, 0x25,
0x4f, 0xb4, 0x2c, 0x80, 0x4a, 0x3b, 0x51, 0xc4, 0xb2, 0xc7, 0x52, 0x6a, 0xa9, 0x7d, 0xe6, 0x45,
0x09, 0xe9, 0xe0, 0xfa, 0xc5, 0x08, 0x36, 0xb7, 0x2d, 0x9b, 0xdb, 0x11, 0xf4, 0x1e, 0x11, 0x21,
0x2c, 0xf1, 0xce, 0x30, 0xb0, 0x4d, 0x8a, 0xf9, 0x26, 0x51, 0x36, 0x37, 0x7c, 0xce, 0x36, 0xf0,
0xbc, 0xc8, 0x8f, 0x7e, 0x04, 0x9d, 0xa7, 0x84, 0xaa, 0x87, 0x05, 0x1d, 0x79, 0x94, 0x5e, 0x1a,
0x06, 0x35, 0xef, 0x12, 0xf8, 0x3a, 0xa7, 0x36, 0x40, 0x7d, 0x4d, 0x6d, 0x87, 0x04, 0x23, 0x22,
0x2e, 0xbf, 0x17, 0x06, 0x6f, 0xd1, 0x6f, 0x73, 0xe2, 0xfa, 0xd5, 0x71, 0xdd, 0xa8, 0x47, 0x9b,
0xc4, 0x17, 0x4b, 0xf0, 0x3a, 0xca, 0x71, 0x12, 0x10, 0xc3, 0xd5, 0xc5, 0xd0, 0x31, 0x9e, 0x98,
0xf5, 0x85, 0xaa, 0xbe, 0x5b, 0xeb, 0x0b, 0x55, 0xf3, 0x22, 0x8d, 0x37, 0x39, 0x1f, 0x8c, 0xae,
0x17, 0x7c, 0xc4, 0x2b, 0x74, 0xc1, 0x69, 0xe7, 0x8d, 0x3f, 0xa6, 0x6f, 0xd1, 0x4b, 0xde, 0xde,
0x6a, 0x3e, 0x9e, 0x14, 0x91, 0x4f, 0xf9, 0x9d, 0x45, 0x0b, 0xcb, 0x98, 0xb2, 0xa3, 0x21, 0xc1,
0x8a, 0x7b, 0xc4, 0xef, 0x02, 0x1c, 0xd1, 0x24, 0x7d, 0xe4, 0x93, 0x71, 0x12, 0x17, 0x96, 0xac,
0x78, 0x20, 0x28, 0x2c, 0x99, 0xf1, 0x4a, 0x80, 0x5e, 0x1a, 0xb1, 0xa7, 0xf5, 0xf6, 0xa4, 0x94,
0xeb, 0xc2, 0x37, 0x04, 0x2d, 0x90, 0x9a, 0x77, 0x04, 0x15, 0x86, 0x8a, 0xe2, 0xa8, 0x11, 0x86,
0x5a, 0xd5, 0x55, 0x23, 0x0c, 0xb5, 0xab, 0xa8, 0x2c, 0x0c, 0x2d, 0xaa, 0x20, 0x3a, 0x0c, 0xad,
0x14, 0x58, 0xb4, 0x0d, 0xad, 0x29, 0x99, 0x1c, 0x42, 0xbb, 0x48, 0xc5, 0x15, 0xa3, 0x72, 0xe2,
0xae, 0x9d, 0x55, 0x25, 0x43, 0xc6, 0x4b, 0x5c, 0xce, 0x80, 0xe6, 0x99, 0x9c, 0xf9, 0x13, 0xfb,
0x73, 0x00, 0xb1, 0xbb, 0x27, 0x6c, 0x64, 0x90, 0xb4, 0x12, 0x61, 0x93, 0xa4, 0x9d, 0x71, 0xaa,
0x48, 0x06, 0x6b, 0x92, 0xf7, 0x9c, 0xad, 0xe3, 0x59, 0xfe, 0x5f, 0x98, 0xdf, 0xf9, 0x9f, 0x00,
0x00, 0x00, 0xff, 0xff, 0x4a, 0xab, 0x23, 0xa7, 0xb7, 0x39, 0x00, 0x00,
// 4765 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x5b, 0xdd, 0x6f, 0x1c, 0x4b,
0x56, 0x4f, 0x8f, 0xc7, 0xf6, 0xcc, 0x99, 0x19, 0x7f, 0x94, 0xbf, 0x26, 0x93, 0xdc, 0xbb, 0x49,
0x6d, 0x74, 0x63, 0xbc, 0x57, 0x76, 0xe2, 0x65, 0x2f, 0xd9, 0x04, 0xb8, 0x72, 0x3e, 0x7d, 0x59,
0xdf, 0x5c, 0x6f, 0x3b, 0xb9, 0x81, 0x5d, 0xa1, 0xa1, 0x3d, 0x53, 0x1e, 0xf7, 0xa6, 0xa7, 0xbb,
0x6f, 0x77, 0x8f, 0x9d, 0xd9, 0x28, 0x12, 0x5a, 0x10, 0x4f, 0xa0, 0x7d, 0x58, 0x04, 0xe2, 0x01,
0xb4, 0x12, 0xe2, 0x11, 0xfe, 0x01, 0x24, 0xfe, 0x00, 0x04, 0x02, 0x69, 0x9f, 0x90, 0x10, 0x4f,
0x3c, 0xf1, 0x82, 0x78, 0xe0, 0x1d, 0x9d, 0xfa, 0xea, 0xaa, 0xee, 0x76, 0x92, 0xe5, 0xeb, 0xc9,
0x53, 0xbf, 0xaa, 0x3e, 0x55, 0x75, 0xea, 0xd4, 0xf9, 0xaa, 0x63, 0x68, 0x26, 0xf1, 0x60, 0x3b,
0x4e, 0xa2, 0x2c, 0x22, 0xb3, 0x41, 0x98, 0xc4, 0x83, 0xde, 0xd5, 0x51, 0x14, 0x8d, 0x02, 0xb6,
0xe3, 0xc5, 0xfe, 0x8e, 0x17, 0x86, 0x51, 0xe6, 0x65, 0x7e, 0x14, 0xa6, 0x62, 0x10, 0xbd, 0x0d,
0x2b, 0x0f, 0x12, 0xe6, 0x65, 0xec, 0x85, 0x17, 0x04, 0x2c, 0x73, 0xd9, 0x57, 0x13, 0x96, 0x66,
0xa4, 0x07, 0x8d, 0xd8, 0x4b, 0xd3, 0xf3, 0x28, 0x19, 0x76, 0x9d, 0x6b, 0xce, 0x66, 0xdb, 0xd5,
0x6d, 0xba, 0x0e, 0xab, 0xf6, 0x27, 0x69, 0x1c, 0x85, 0x29, 0x43, 0x52, 0xcf, 0xc3, 0x20, 0x1a,
0xbc, 0xfc, 0xb9, 0x48, 0xd9, 0x9f, 0x48, 0x52, 0xff, 0xe1, 0x40, 0xeb, 0x59, 0xe2, 0x85, 0xa9,
0x37, 0xc0, 0xc5, 0x92, 0x2e, 0xcc, 0x67, 0xaf, 0xfa, 0xa7, 0x5e, 0x7a, 0xca, 0x49, 0x34, 0x5d,
0xd5, 0x24, 0xeb, 0x30, 0xe7, 0x8d, 0xa3, 0x49, 0x98, 0x75, 0x6b, 0xd7, 0x9c, 0xcd, 0x19, 0x57,
0xb6, 0xc8, 0xc7, 0xb0, 0x1c, 0x4e, 0xc6, 0xfd, 0x41, 0x14, 0x9e, 0xf8, 0xc9, 0x58, 0x6c, 0xb9,
0x3b, 0x73, 0xcd, 0xd9, 0x9c, 0x75, 0xcb, 0x1d, 0xe4, 0x43, 0x80, 0x63, 0x5c, 0x86, 0x98, 0xa2,
0xce, 0xa7, 0x30, 0x10, 0x42, 0xa1, 0x2d, 0x5b, 0xcc, 0x1f, 0x9d, 0x66, 0xdd, 0x59, 0x4e, 0xc8,
0xc2, 0x90, 0x46, 0xe6, 0x8f, 0x59, 0x3f, 0xcd, 0xbc, 0x71, 0xdc, 0x9d, 0xe3, 0xab, 0x31, 0x10,
0xde, 0x1f, 0x65, 0x5e, 0xd0, 0x3f, 0x61, 0x2c, 0xed, 0xce, 0xcb, 0x7e, 0x8d, 0xd0, 0x2e, 0xac,
0x3f, 0x61, 0x99, 0xb1, 0xeb, 0x54, 0x72, 0x90, 0x1e, 0x00, 0x31, 0xe0, 0x87, 0x2c, 0xf3, 0xfc,
0x20, 0x25, 0x9f, 0x40, 0x3b, 0x33, 0x06, 0x77, 0x9d, 0x6b, 0x33, 0x9b, 0xad, 0x5d, 0xb2, 0xcd,
0x4f, 0x7d, 0xdb, 0xf8, 0xc0, 0xb5, 0xc6, 0xd1, 0x7f, 0x74, 0xa0, 0x75, 0xc4, 0xc2, 0xa1, 0x3a,
0x1f, 0x02, 0xf5, 0x21, 0x4b, 0x33, 0x79, 0x36, 0xfc, 0x37, 0xf9, 0x1a, 0xb4, 0xf0, 0x6f, 0x3f,
0xcd, 0x12, 0x3f, 0x1c, 0x71, 0xd6, 0x36, 0x5d, 0x40, 0xe8, 0x88, 0x23, 0x64, 0x09, 0x66, 0xbc,
0x71, 0xc6, 0x19, 0x3a, 0xe3, 0xe2, 0x4f, 0x72, 0x1d, 0xda, 0xb1, 0x37, 0x1d, 0xb3, 0x30, 0xcb,
0x99, 0xd8, 0x76, 0x5b, 0x12, 0xdb, 0x47, 0x2e, 0x6e, 0xc3, 0x8a, 0x39, 0x44, 0x51, 0x9f, 0xe5,
0xd4, 0x97, 0x8d, 0x91, 0x72, 0x92, 0x9b, 0xb0, 0xa8, 0xc6, 0x27, 0x62, 0xb1, 0x9c, 0xad, 0x4d,
0x77, 0x41, 0xc2, 0x8a, 0x41, 0x7f, 0xe8, 0x40, 0x5b, 0x6c, 0x49, 0xc8, 0x0f, 0xb9, 0x01, 0x1d,
0xf5, 0x25, 0x4b, 0x92, 0x28, 0x91, 0x52, 0x63, 0x83, 0x64, 0x0b, 0x96, 0x14, 0x10, 0x27, 0xcc,
0x1f, 0x7b, 0x23, 0xc6, 0xb7, 0xda, 0x76, 0x4b, 0x38, 0xd9, 0xcd, 0x29, 0x26, 0xd1, 0x24, 0x63,
0x7c, 0xeb, 0xad, 0xdd, 0xb6, 0x64, 0xb7, 0x8b, 0x98, 0x6b, 0x0f, 0xa1, 0x3f, 0x72, 0xa0, 0xfd,
0xe0, 0xd4, 0x0b, 0x43, 0x16, 0x1c, 0x46, 0x7e, 0x98, 0xa1, 0x18, 0x9d, 0x4c, 0xc2, 0xa1, 0x1f,
0x8e, 0xfa, 0xd9, 0x2b, 0x5f, 0x5d, 0x07, 0x0b, 0xc3, 0x45, 0x99, 0x6d, 0x64, 0x92, 0xe4, 0x7f,
0x09, 0x47, 0x7a, 0xd1, 0x24, 0x8b, 0x27, 0x59, 0xdf, 0x0f, 0x87, 0xec, 0x15, 0x5f, 0x53, 0xc7,
0xb5, 0x30, 0xfa, 0xab, 0xb0, 0x74, 0x80, 0xf2, 0x19, 0xfa, 0xe1, 0x68, 0x6f, 0x38, 0x4c, 0x58,
0x9a, 0xe2, 0xa5, 0x89, 0x27, 0xc7, 0x2f, 0xd9, 0x54, 0xf2, 0x45, 0xb6, 0x50, 0x14, 0x4e, 0xa3,
0x34, 0x93, 0xf3, 0xf1, 0xdf, 0xf4, 0xa7, 0x0e, 0x2c, 0x22, 0x6f, 0x3f, 0xf7, 0xc2, 0xa9, 0x12,
0x99, 0x03, 0x68, 0x23, 0xa9, 0x67, 0xd1, 0x9e, 0xb8, 0x7a, 0x42, 0xf4, 0x36, 0x25, 0x2f, 0x0a,
0xa3, 0xb7, 0xcd, 0xa1, 0x8f, 0xc2, 0x2c, 0x99, 0xba, 0xd6, 0xd7, 0xbd, 0x4f, 0x61, 0xb9, 0x34,
0x04, 0x05, 0x2c, 0x5f, 0x1f, 0xfe, 0x24, 0xab, 0x30, 0x7b, 0xe6, 0x05, 0x13, 0x26, 0x2f, 0xba,
0x68, 0xdc, 0xad, 0xdd, 0x71, 0xe8, 0x47, 0xb0, 0x94, 0xcf, 0x29, 0x25, 0x80, 0x40, 0x5d, 0xb3,
0xb8, 0xe9, 0xf2, 0xdf, 0xc8, 0x0a, 0x1c, 0xf7, 0x20, 0xf2, 0xf5, 0xdd, 0xc2, 0x71, 0xde, 0x70,
0xa8, 0x04, 0x84, 0xff, 0xbe, 0x48, 0xa7, 0xd0, 0x9b, 0xb0, 0x6c, 0x7c, 0xff, 0x96, 0x89, 0xfe,
0xcc, 0x81, 0xe5, 0xa7, 0xec, 0x5c, 0xb2, 0x5b, 0x4d, 0x75, 0x07, 0xea, 0xd9, 0x34, 0x66, 0x7c,
0xe4, 0xc2, 0xee, 0x0d, 0xc9, 0xad, 0xd2, 0xb8, 0x6d, 0xd9, 0x7c, 0x36, 0x8d, 0x99, 0xcb, 0xbf,
0xa0, 0x5f, 0x40, 0xcb, 0x00, 0xc9, 0x06, 0xac, 0xbc, 0xf8, 0xec, 0xd9, 0xd3, 0x47, 0x47, 0x47,
0xfd, 0xc3, 0xe7, 0xf7, 0xbf, 0xf3, 0xe8, 0x37, 0xfa, 0xfb, 0x7b, 0x47, 0xfb, 0x4b, 0x97, 0xc8,
0x3a, 0x90, 0xa7, 0x8f, 0x8e, 0x9e, 0x3d, 0x7a, 0x68, 0xe1, 0x0e, 0x59, 0x84, 0x96, 0x09, 0xd4,
0x68, 0x0f, 0xba, 0x4f, 0xd9, 0xf9, 0x0b, 0x3f, 0x0b, 0x59, 0x9a, 0xda, 0xd3, 0xd3, 0x6d, 0x20,
0xe6, 0x9a, 0xe4, 0x36, 0xbb, 0x30, 0xef, 0x09, 0x48, 0x69, 0x60, 0xd9, 0xa4, 0x1f, 0x01, 0x39,
0xf2, 0x47, 0xe1, 0xe7, 0x2c, 0x4d, 0xbd, 0x11, 0x53, 0x9b, 0x5d, 0x82, 0x99, 0x71, 0x3a, 0x92,
0x12, 0x8e, 0x3f, 0xe9, 0x37, 0x61, 0xc5, 0x1a, 0x27, 0x09, 0x5f, 0x85, 0x66, 0xea, 0x8f, 0x42,
0x2f, 0x9b, 0x24, 0x4c, 0x92, 0xce, 0x01, 0xfa, 0x18, 0x56, 0xbf, 0x64, 0x89, 0x7f, 0x32, 0x7d,
0x17, 0x79, 0x9b, 0x4e, 0xad, 0x48, 0xe7, 0x11, 0xac, 0x15, 0xe8, 0xc8, 0xe9, 0x85, 0x54, 0xc9,
0xf3, 0x6b, 0xb8, 0xa2, 0x61, 0x5c, 0x90, 0x9a, 0x79, 0x41, 0xe8, 0x73, 0x20, 0x0f, 0xa2, 0x30,
0x64, 0x83, 0xec, 0x90, 0xb1, 0x44, 0x2d, 0xe6, 0x1b, 0x86, 0x0c, 0xb5, 0x76, 0x37, 0xe4, 0xc1,
0x16, 0x6f, 0x9d, 0x14, 0x2e, 0x02, 0xf5, 0x98, 0x25, 0x63, 0x4e, 0xb8, 0xe1, 0xf2, 0xdf, 0x74,
0x07, 0x56, 0x2c, 0xb2, 0x39, 0xcf, 0x63, 0xc6, 0x92, 0xbe, 0x5c, 0xdd, 0xac, 0xab, 0x9a, 0xf4,
0x36, 0xac, 0x3d, 0xf4, 0xd3, 0x41, 0x79, 0x29, 0xf8, 0xc9, 0xe4, 0xb8, 0x9f, 0x5f, 0x1d, 0xd5,
0x44, 0xf3, 0x52, 0xfc, 0x44, 0x1a, 0xdb, 0xdf, 0x73, 0xa0, 0xbe, 0xff, 0xec, 0xe0, 0x01, 0x5a,
0x6a, 0x3f, 0x1c, 0x44, 0x63, 0x54, 0xca, 0x82, 0x1d, 0xba, 0x7d, 0xa1, 0x9d, 0xbd, 0x0a, 0x4d,
0xae, 0xcb, 0xd1, 0x12, 0x72, 0xfd, 0xd3, 0x76, 0x73, 0x00, 0xad, 0x30, 0x7b, 0x15, 0xfb, 0x09,
0x37, 0xb3, 0xca, 0x78, 0xd6, 0xb9, 0x96, 0x2a, 0x77, 0xd0, 0xbf, 0xab, 0x43, 0x67, 0x6f, 0x90,
0xf9, 0x67, 0x4c, 0x6a, 0x4d, 0x3e, 0x2b, 0x07, 0xe4, 0x7a, 0x64, 0x0b, 0xf5, 0x7b, 0xc2, 0xc6,
0x51, 0xc6, 0xfa, 0xd6, 0x31, 0xd9, 0x20, 0x8e, 0x1a, 0x08, 0x42, 0xfd, 0x18, 0xf5, 0x2f, 0x5f,
0x5f, 0xd3, 0xb5, 0x41, 0x64, 0x19, 0x02, 0xc8, 0x65, 0x5c, 0x59, 0xdd, 0x55, 0x4d, 0xe4, 0xc7,
0xc0, 0x8b, 0xbd, 0x81, 0x9f, 0x4d, 0xb9, 0x91, 0x9a, 0x71, 0x75, 0x1b, 0x69, 0x07, 0xd1, 0xc0,
0x0b, 0xfa, 0xc7, 0x5e, 0xe0, 0x85, 0x03, 0x26, 0x0d, 0xbe, 0x0d, 0x92, 0x8f, 0x60, 0x41, 0x2e,
0x49, 0x0d, 0x13, 0x76, 0xbf, 0x80, 0xa2, 0x6f, 0x30, 0x88, 0xc6, 0x63, 0x3f, 0x43, 0x57, 0xa0,
0xdb, 0x10, 0xbe, 0x41, 0x8e, 0xf0, 0x9d, 0x88, 0xd6, 0xb9, 0xe0, 0x61, 0x53, 0xcc, 0x66, 0x81,
0x48, 0xe5, 0x84, 0xb1, 0x7e, 0xcc, 0x92, 0xfe, 0xcb, 0xf3, 0x2e, 0x08, 0x2a, 0x39, 0x82, 0xa7,
0x31, 0x09, 0x53, 0x96, 0x65, 0x01, 0x1b, 0xea, 0x05, 0xb5, 0xf8, 0xb0, 0x72, 0x07, 0xb9, 0x05,
0x2b, 0xc2, 0x3b, 0x49, 0xbd, 0x2c, 0x4a, 0x4f, 0xfd, 0xb4, 0x9f, 0xb2, 0x30, 0xeb, 0xb6, 0xf9,
0xf8, 0xaa, 0x2e, 0x72, 0x07, 0x36, 0x0a, 0x70, 0xc2, 0x06, 0xcc, 0x3f, 0x63, 0xc3, 0x6e, 0x87,
0x7f, 0x75, 0x51, 0x37, 0xb9, 0x06, 0x2d, 0x74, 0xca, 0x26, 0xf1, 0xd0, 0xcb, 0x58, 0xda, 0x5d,
0xe0, 0xe7, 0x60, 0x42, 0xe4, 0x36, 0x74, 0x62, 0x26, 0xcc, 0xdf, 0x69, 0x16, 0x0c, 0xd2, 0xee,
0x22, 0xb7, 0x39, 0x2d, 0x79, 0xd9, 0x50, 0x7e, 0x5d, 0x7b, 0x04, 0x5d, 0x83, 0x95, 0x03, 0x3f,
0xcd, 0xa4, 0x2c, 0x69, 0xfd, 0xb6, 0x0f, 0xab, 0x36, 0x2c, 0x6f, 0xdb, 0x2d, 0x68, 0x48, 0xc1,
0x48, 0xbb, 0x2d, 0x4e, 0x7c, 0x55, 0x12, 0xb7, 0x64, 0xd2, 0xd5, 0xa3, 0xe8, 0xef, 0xd6, 0xa0,
0x8e, 0x37, 0xe9, 0xe2, 0x5b, 0x67, 0x5e, 0xe1, 0x9a, 0x75, 0x85, 0x4d, 0x85, 0x3a, 0x63, 0x29,
0x54, 0xee, 0x8c, 0x4e, 0x33, 0x26, 0xf9, 0x2d, 0x64, 0xd2, 0x40, 0xf2, 0xfe, 0x84, 0x0d, 0xce,
0xb8, 0x60, 0xea, 0x7e, 0x44, 0x50, 0x6c, 0x53, 0x2f, 0x13, 0x5f, 0x0b, 0xa9, 0xd4, 0x6d, 0xd5,
0xc7, 0xbf, 0x9c, 0xcf, 0xfb, 0xf8, 0x77, 0x5d, 0x98, 0xf7, 0xc3, 0xe3, 0x68, 0x12, 0x0e, 0xb9,
0x04, 0x36, 0x5c, 0xd5, 0xc4, 0x4b, 0x1e, 0x73, 0xc7, 0xc3, 0x1f, 0x33, 0x29, 0x7a, 0x39, 0x40,
0x09, 0x7a, 0x18, 0x29, 0xd7, 0x29, 0x9a, 0xc9, 0x9f, 0xc0, 0xb2, 0x81, 0x49, 0x0e, 0x5f, 0x87,
0x59, 0xdc, 0xbd, 0x72, 0x55, 0xd5, 0xd9, 0x71, 0x65, 0x24, 0x7a, 0xe8, 0x12, 0x2c, 0x3c, 0x61,
0xd9, 0x67, 0xe1, 0x49, 0xa4, 0x28, 0xfd, 0x67, 0x0d, 0x16, 0x35, 0x24, 0x09, 0x6d, 0xc2, 0xa2,
0x3f, 0x64, 0x61, 0xe6, 0x67, 0xd3, 0xbe, 0xe5, 0xc8, 0x14, 0x61, 0x54, 0xef, 0x5e, 0xe0, 0x7b,
0xa9, 0x54, 0x10, 0xa2, 0x41, 0x76, 0x61, 0x15, 0x65, 0x4b, 0x89, 0x8b, 0x3e, 0x76, 0xe1, 0x3f,
0x55, 0xf6, 0xe1, 0x75, 0x40, 0x5c, 0x28, 0xa0, 0xfc, 0x13, 0xa1, 0xcc, 0xaa, 0xba, 0x90, 0x6b,
0x82, 0x12, 0x6e, 0x79, 0x96, 0x8f, 0xcb, 0x81, 0x52, 0x48, 0x31, 0x27, 0x7c, 0xb7, 0x62, 0x48,
0x61, 0x84, 0x25, 0x8d, 0x52, 0x58, 0xb2, 0x09, 0x8b, 0xe9, 0x34, 0x1c, 0xb0, 0x61, 0x3f, 0x8b,
0x70, 0x5e, 0x3f, 0xe4, 0xa7, 0xd3, 0x70, 0x8b, 0x30, 0x0f, 0xa0, 0x58, 0x9a, 0x85, 0x2c, 0xe3,
0x7a, 0xa1, 0xe1, 0xaa, 0x26, 0xaa, 0x58, 0x3e, 0x44, 0x08, 0x7d, 0xd3, 0x95, 0x2d, 0xfa, 0x43,
0x6e, 0xea, 0x74, 0x8c, 0xf4, 0x9c, 0xdf, 0x43, 0x72, 0x05, 0x9a, 0x62, 0xfe, 0xf4, 0xd4, 0x53,
0xd1, 0x1c, 0x07, 0x8e, 0x4e, 0x3d, 0x0c, 0x01, 0xac, 0x2d, 0x09, 0x89, 0x6f, 0x71, 0x6c, 0x5f,
0xec, 0xe8, 0x06, 0x2c, 0xa8, 0xe8, 0x2b, 0xed, 0x07, 0xec, 0x24, 0x53, 0x3e, 0x6b, 0x38, 0x19,
0xe3, 0x74, 0xe9, 0x01, 0x3b, 0xc9, 0xe8, 0x53, 0x58, 0x96, 0xb7, 0xed, 0x8b, 0x98, 0xa9, 0xa9,
0xbf, 0x5d, 0xd4, 0xe6, 0xc2, 0xdc, 0xae, 0x48, 0x29, 0x32, 0x1d, 0xed, 0x82, 0x8a, 0xa7, 0x2e,
0x10, 0xd9, 0xfd, 0x20, 0x88, 0x52, 0x26, 0x09, 0x52, 0x68, 0x0f, 0x82, 0x28, 0x2d, 0x7a, 0xe3,
0x26, 0x86, 0x7c, 0x4b, 0x27, 0x83, 0x01, 0xde, 0x52, 0x61, 0xb0, 0x55, 0x93, 0x32, 0x58, 0xe1,
0xc4, 0x94, 0x5a, 0xd0, 0x4e, 0xde, 0xfb, 0xaf, 0xb2, 0x3d, 0x30, 0x83, 0x83, 0x55, 0x98, 0x3d,
0x89, 0x92, 0x01, 0x93, 0x13, 0x89, 0x06, 0xfd, 0x27, 0x07, 0x96, 0xf9, 0x3c, 0x47, 0x99, 0x97,
0x4d, 0x52, 0xb9, 0xf4, 0x5f, 0x86, 0x0e, 0x2e, 0x93, 0x29, 0x31, 0x95, 0xb3, 0xac, 0xea, 0x1b,
0xc5, 0x51, 0x31, 0x78, 0xff, 0x92, 0x6b, 0x0f, 0x26, 0x9f, 0x42, 0xdb, 0x0c, 0x7f, 0xf9, 0x84,
0xad, 0xdd, 0xcb, 0x6a, 0x89, 0xa5, 0x53, 0xdf, 0xbf, 0xe4, 0x5a, 0x1f, 0x90, 0x7b, 0x00, 0xdc,
0x46, 0x72, 0xb2, 0x32, 0x12, 0xba, 0x6c, 0xef, 0xd0, 0x60, 0xf4, 0xfe, 0x25, 0xd7, 0x18, 0x7e,
0xbf, 0x01, 0x73, 0x42, 0xa9, 0xd3, 0x27, 0xd0, 0xb1, 0x56, 0x6a, 0xf9, 0xd2, 0x6d, 0xe1, 0x4b,
0x97, 0x62, 0x9c, 0x5a, 0x45, 0x8c, 0xf3, 0x2f, 0x0e, 0x10, 0x94, 0x94, 0xc2, 0x59, 0x7c, 0x04,
0x0b, 0x99, 0x97, 0x8c, 0x58, 0xd6, 0xb7, 0xdd, 0xa8, 0x02, 0xca, 0xad, 0x4f, 0x34, 0xb4, 0x7c,
0x89, 0xb6, 0x6b, 0x42, 0x64, 0x1b, 0x88, 0xd1, 0x54, 0x81, 0xab, 0xd0, 0xdb, 0x15, 0x3d, 0xa8,
0x60, 0x84, 0x23, 0xa0, 0x42, 0x36, 0xe9, 0x3b, 0xd5, 0xb9, 0xee, 0xac, 0xec, 0xe3, 0x79, 0x92,
0x09, 0x46, 0xc5, 0x5e, 0xa6, 0xbc, 0x0d, 0xd5, 0xa6, 0x3f, 0x73, 0x60, 0x09, 0x37, 0x68, 0x09,
0xc1, 0x5d, 0xe0, 0x02, 0xf4, 0x9e, 0x32, 0x60, 0x8d, 0xfd, 0x9f, 0x8b, 0xc0, 0x1d, 0x68, 0x72,
0x82, 0x51, 0xcc, 0x42, 0x29, 0x01, 0x5d, 0x5b, 0x02, 0xf2, 0xab, 0xbb, 0x7f, 0xc9, 0xcd, 0x07,
0x1b, 0xe7, 0xff, 0x0f, 0x0e, 0xb4, 0xe4, 0x32, 0xff, 0xdb, 0xfe, 0x67, 0x0f, 0x1a, 0x28, 0x0a,
0x86, 0x7b, 0xa7, 0xdb, 0xa8, 0x1e, 0xc7, 0xe8, 0xfe, 0xa3, 0x3d, 0xb0, 0x7c, 0xcf, 0x22, 0x8c,
0xca, 0x9d, 0x6b, 0xa9, 0xb4, 0x9f, 0xf9, 0x41, 0x5f, 0xf5, 0xca, 0x34, 0x4f, 0x55, 0x17, 0xde,
0xd6, 0x34, 0xf3, 0x46, 0x4c, 0xea, 0x6d, 0xd1, 0xa0, 0x1b, 0xb0, 0x26, 0x37, 0x64, 0x8b, 0x22,
0xfd, 0x77, 0x80, 0xf5, 0x62, 0x8f, 0xf6, 0x3b, 0xa4, 0x33, 0x15, 0xf8, 0xe3, 0xe3, 0x48, 0xfb,
0x65, 0x8e, 0xe9, 0x67, 0x59, 0x5d, 0xe4, 0x04, 0xd6, 0x94, 0x79, 0x42, 0x8e, 0xe6, 0xc6, 0xa8,
0xc6, 0xed, 0xea, 0x2d, 0x5b, 0x02, 0x0a, 0xf3, 0x29, 0xd8, 0xbc, 0x2f, 0xd5, 0xe4, 0xc8, 0x08,
0xba, 0xda, 0x0c, 0x4a, 0xa5, 0x68, 0x98, 0x4a, 0x9c, 0xea, 0x1b, 0x6f, 0x9f, 0x8a, 0x2b, 0x81,
0xa1, 0x42, 0x2f, 0x24, 0x46, 0x5e, 0xc1, 0x87, 0xaa, 0x8f, 0x6b, 0xbd, 0xf2, 0x74, 0xf5, 0xf7,
0xd9, 0xd9, 0x63, 0xfc, 0xd6, 0x9e, 0xf3, 0x1d, 0x74, 0x7b, 0x7f, 0xeb, 0xc0, 0x82, 0x4d, 0x0d,
0xa5, 0x46, 0x7a, 0xe7, 0xea, 0x62, 0x2b, 0xe7, 0xa2, 0x00, 0x97, 0xe3, 0x8b, 0x5a, 0x55, 0x7c,
0x61, 0x46, 0x11, 0x33, 0xef, 0x8a, 0x22, 0xea, 0xef, 0x17, 0x45, 0xcc, 0x56, 0x45, 0x11, 0xbd,
0x9f, 0xd6, 0x80, 0x94, 0x4f, 0x97, 0x3c, 0x16, 0x01, 0x4e, 0xc8, 0x02, 0xa9, 0x22, 0x3e, 0x7e,
0x2f, 0x01, 0x51, 0xb0, 0xfa, 0x18, 0x05, 0xd5, 0x54, 0x01, 0xa6, 0x95, 0xef, 0xb8, 0x55, 0x5d,
0x64, 0x0b, 0x96, 0xf2, 0xbb, 0x13, 0xe4, 0xba, 0xa2, 0xe3, 0x96, 0xf0, 0x42, 0x08, 0x54, 0x7f,
0x77, 0x08, 0x34, 0xfb, 0xee, 0x10, 0x68, 0xae, 0x18, 0x02, 0xf5, 0x5e, 0x43, 0xc7, 0x12, 0x90,
0xff, 0x35, 0xe6, 0x14, 0x9d, 0x09, 0x21, 0x0a, 0x16, 0xd6, 0xfb, 0xb7, 0x1a, 0x90, 0xb2, 0x8c,
0xfe, 0x7f, 0x2e, 0x81, 0x0b, 0x9c, 0xa5, 0x66, 0x66, 0xa4, 0xc0, 0x59, 0x0a, 0xe6, 0xff, 0x52,
0x71, 0x7e, 0x0c, 0xcb, 0x09, 0x1b, 0x44, 0x67, 0x2c, 0x31, 0x82, 0x50, 0x71, 0x50, 0xe5, 0x0e,
0x74, 0xa7, 0xec, 0xb0, 0xaf, 0x61, 0x65, 0xb9, 0x0d, 0xeb, 0x51, 0x8c, 0xfe, 0xbe, 0x0d, 0xab,
0xe2, 0x51, 0xe1, 0xbe, 0x20, 0xa5, 0x9c, 0x82, 0xeb, 0xd0, 0x3e, 0x17, 0x79, 0xaf, 0x7e, 0x14,
0x06, 0x53, 0x69, 0x68, 0x5a, 0x12, 0xfb, 0x22, 0x0c, 0xa6, 0xf4, 0x36, 0xac, 0x15, 0x3e, 0xcd,
0x13, 0x32, 0xb6, 0x7a, 0x56, 0x4d, 0x54, 0xfc, 0xf2, 0x3c, 0xec, 0xe9, 0xe8, 0x2e, 0xac, 0x17,
0x3b, 0xde, 0x49, 0xec, 0x53, 0x20, 0xdf, 0x9d, 0xb0, 0x64, 0xca, 0x93, 0xca, 0x3a, 0x7d, 0xb8,
0x51, 0x0c, 0x32, 0xe7, 0xe2, 0xc9, 0xf1, 0x77, 0xd8, 0x54, 0xe5, 0xe2, 0x6b, 0x3a, 0x17, 0x4f,
0xef, 0xc1, 0x8a, 0x45, 0x40, 0x67, 0xc5, 0xe7, 0x78, 0x62, 0x5a, 0x05, 0x60, 0x76, 0xf2, 0x5a,
0xf6, 0xd1, 0x3f, 0x76, 0x60, 0x66, 0x3f, 0x8a, 0xcd, 0xbc, 0x88, 0x63, 0xe7, 0x45, 0xa4, 0xde,
0xeb, 0x6b, 0xb5, 0x56, 0x93, 0x57, 0xd1, 0x04, 0x51, 0x6b, 0x79, 0xe3, 0x0c, 0x43, 0x90, 0x93,
0x28, 0x39, 0xf7, 0x92, 0xa1, 0x94, 0xb5, 0x02, 0x8a, 0xcb, 0xcf, 0x6f, 0x3c, 0xfe, 0x44, 0x5b,
0xcf, 0x93, 0x43, 0x53, 0x19, 0x35, 0xc9, 0x16, 0xfd, 0xb1, 0x03, 0xb3, 0x7c, 0xad, 0x28, 0xa0,
0xc2, 0x30, 0xf2, 0xf7, 0x15, 0x9e, 0x7b, 0x72, 0x84, 0x80, 0x16, 0xe0, 0xc2, 0xab, 0x4b, 0xad,
0xf8, 0xea, 0x82, 0x41, 0x9a, 0x68, 0xe5, 0xcf, 0x19, 0x39, 0x40, 0x3e, 0x84, 0xfa, 0x69, 0x14,
0x2b, 0xf3, 0x03, 0x2a, 0xd9, 0x10, 0xc5, 0x2e, 0xc7, 0xe9, 0x16, 0x2c, 0x3e, 0x8d, 0x86, 0xcc,
0x88, 0x57, 0x2f, 0x3c, 0x26, 0xfa, 0xdb, 0x0e, 0x34, 0xd4, 0x60, 0xb2, 0x09, 0x75, 0x34, 0x23,
0x05, 0x9f, 0x4d, 0xa7, 0x0c, 0x71, 0x9c, 0xcb, 0x47, 0xe0, 0xad, 0xe6, 0x11, 0x53, 0x6e, 0xe3,
0x55, 0xbc, 0x94, 0xdb, 0x4f, 0x74, 0x74, 0xf9, 0x9a, 0x0b, 0x86, 0xa6, 0x80, 0xd2, 0x9f, 0x38,
0xd0, 0xb1, 0xe6, 0x40, 0xd7, 0x37, 0xf0, 0xd2, 0x4c, 0xa6, 0x59, 0x24, 0x13, 0x4d, 0xc8, 0xcc,
0x6d, 0xd4, 0xec, 0xdc, 0x86, 0x8e, 0xad, 0x67, 0xcc, 0xd8, 0xfa, 0x16, 0x34, 0x65, 0x22, 0x83,
0x29, 0xbe, 0xa9, 0xdb, 0x8a, 0x33, 0xaa, 0x64, 0x68, 0x3e, 0x88, 0xde, 0x83, 0x96, 0xd1, 0x83,
0x13, 0x86, 0x2c, 0x3b, 0x8f, 0x92, 0x97, 0x2a, 0x99, 0x22, 0x9b, 0x3a, 0x57, 0x5f, 0xcb, 0x73,
0xf5, 0xf4, 0x2f, 0x1d, 0xe8, 0xa0, 0x4c, 0xf8, 0xe1, 0xe8, 0x30, 0x0a, 0xfc, 0xc1, 0x94, 0xcb,
0x86, 0x3a, 0xfe, 0xfe, 0x90, 0x05, 0x99, 0xa7, 0x65, 0xc3, 0x86, 0xd1, 0x32, 0x8f, 0xfd, 0x90,
0xeb, 0x0b, 0x29, 0x19, 0xba, 0x8d, 0x32, 0x8e, 0x66, 0xe3, 0xd8, 0x4b, 0x59, 0x7f, 0x8c, 0x2e,
0xb9, 0x54, 0x94, 0x16, 0x88, 0xea, 0x0f, 0x81, 0xc4, 0xcb, 0x58, 0x7f, 0xec, 0x07, 0x81, 0x2f,
0xc6, 0x0a, 0x59, 0xae, 0xea, 0xa2, 0x7f, 0x5d, 0x83, 0x96, 0x54, 0x08, 0x8f, 0x86, 0x23, 0x91,
0xf9, 0x93, 0xee, 0x82, 0xbe, 0x68, 0x06, 0xa2, 0xfa, 0x2d, 0x07, 0xc3, 0x40, 0x8a, 0x07, 0x38,
0x53, 0x3e, 0xc0, 0xab, 0xd0, 0x44, 0x41, 0xba, 0xcd, 0x3d, 0x19, 0xf1, 0xb4, 0x99, 0x03, 0xaa,
0x77, 0x97, 0xf7, 0xce, 0xe6, 0xbd, 0x1c, 0xb0, 0x7c, 0x97, 0xb9, 0x82, 0xef, 0x72, 0x07, 0xda,
0x92, 0x0c, 0xe7, 0x3b, 0x4f, 0x27, 0xe5, 0xa2, 0x6c, 0x9d, 0x89, 0x6b, 0x8d, 0x54, 0x5f, 0xee,
0xaa, 0x2f, 0x1b, 0xef, 0xfa, 0x52, 0x8d, 0xa4, 0x6b, 0xb0, 0x22, 0x99, 0xf7, 0x24, 0xf1, 0xe2,
0x53, 0xa5, 0x64, 0x87, 0xfa, 0x9d, 0x8d, 0xc3, 0x64, 0x0b, 0x66, 0xf1, 0x33, 0xa5, 0xe7, 0xaa,
0xaf, 0x97, 0x18, 0x42, 0x36, 0x61, 0x96, 0x0d, 0x47, 0x4c, 0x39, 0xcf, 0xc4, 0x0e, 0x62, 0xf0,
0x8c, 0x5c, 0x31, 0x00, 0x2f, 0x3b, 0xa2, 0x85, 0xcb, 0x6e, 0xeb, 0xc8, 0x39, 0x6c, 0x7e, 0x36,
0xa4, 0xab, 0x40, 0x9e, 0x0a, 0xa9, 0x35, 0x73, 0x59, 0xbf, 0x33, 0x03, 0x2d, 0x03, 0xc6, 0x7b,
0x3b, 0xc2, 0x05, 0xf7, 0x87, 0xbe, 0x37, 0x66, 0x19, 0x4b, 0xa4, 0xa4, 0x16, 0x50, 0xae, 0x4a,
0xcf, 0x46, 0xfd, 0x68, 0x92, 0xf5, 0x87, 0x6c, 0x94, 0x30, 0x91, 0x23, 0x70, 0xdc, 0x02, 0x8a,
0xe3, 0xc6, 0xde, 0x2b, 0x73, 0x9c, 0x90, 0x87, 0x02, 0xaa, 0x32, 0x53, 0x82, 0x47, 0xf5, 0x3c,
0x33, 0x25, 0x38, 0x52, 0xd4, 0x38, 0xb3, 0x15, 0x1a, 0xe7, 0x13, 0x58, 0x17, 0xba, 0x45, 0xde,
0xcd, 0x7e, 0x41, 0x4c, 0x2e, 0xe8, 0x45, 0x8f, 0x10, 0xd7, 0xac, 0x04, 0x3c, 0xf5, 0x7f, 0x28,
0x52, 0xe2, 0x8e, 0x5b, 0xc2, 0x71, 0x2c, 0x5e, 0x47, 0x6b, 0xac, 0x48, 0x8d, 0x97, 0x70, 0x3e,
0xd6, 0x7b, 0x65, 0x8f, 0x6d, 0xca, 0xb1, 0x05, 0x9c, 0x76, 0xa0, 0x75, 0x94, 0x45, 0xb1, 0x3a,
0x94, 0x05, 0x68, 0x8b, 0xa6, 0x7c, 0x0e, 0xb9, 0x02, 0x97, 0xb9, 0x14, 0x3d, 0x8b, 0xe2, 0x28,
0x88, 0x46, 0xd3, 0xa3, 0xc9, 0x71, 0x3a, 0x48, 0xfc, 0x18, 0x1d, 0x5b, 0xfa, 0xf7, 0x0e, 0xac,
0x58, 0xbd, 0x32, 0x16, 0xff, 0x45, 0x21, 0xd2, 0x3a, 0x83, 0x2d, 0x04, 0x6f, 0xd9, 0x50, 0x7c,
0x62, 0xa0, 0x48, 0x2b, 0x3c, 0x97, 0x49, 0xed, 0x3d, 0x58, 0x54, 0x2b, 0x53, 0x1f, 0x0a, 0x29,
0xec, 0x96, 0xa5, 0x50, 0x7e, 0xbf, 0x20, 0x3f, 0x50, 0x24, 0x7e, 0x45, 0x38, 0x7d, 0x6c, 0xc8,
0xf7, 0xa8, 0xe2, 0xb2, 0x9e, 0xfa, 0xde, 0x74, 0x34, 0xd5, 0x0a, 0x06, 0x1a, 0x4c, 0xe9, 0xef,
0x3b, 0x00, 0xf9, 0xea, 0x50, 0x30, 0x72, 0xe5, 0xed, 0xf0, 0x7c, 0x60, 0x0e, 0xa0, 0xeb, 0xa4,
0xf3, 0xab, 0xb9, 0x3d, 0x68, 0x29, 0x0c, 0x7d, 0x91, 0x9b, 0xb0, 0x38, 0x0a, 0xa2, 0x63, 0x6e,
0x5d, 0xf9, 0xcb, 0x5b, 0x2a, 0x1f, 0x85, 0x16, 0x04, 0xfc, 0x58, 0xa2, 0xb9, 0xf1, 0xa8, 0x1b,
0xc6, 0x83, 0xfe, 0x41, 0x4d, 0x67, 0xfe, 0xf2, 0x3d, 0x5f, 0x78, 0xcb, 0xc8, 0x6e, 0x49, 0x39,
0x5e, 0x90, 0x69, 0xe3, 0xe9, 0x87, 0xc3, 0x77, 0x86, 0x63, 0xf7, 0x60, 0x21, 0x11, 0xda, 0x47,
0xa9, 0xa6, 0xfa, 0x5b, 0x54, 0x53, 0x27, 0xb1, 0xec, 0xce, 0x2f, 0xc0, 0x92, 0x37, 0x3c, 0x63,
0x49, 0xe6, 0x73, 0x77, 0x9b, 0x9b, 0x77, 0xa1, 0x50, 0x17, 0x0d, 0x9c, 0x5b, 0xdd, 0x9b, 0xb0,
0x28, 0x1f, 0xe2, 0xf4, 0x48, 0x59, 0xd8, 0x90, 0xc3, 0x38, 0x90, 0xfe, 0xb9, 0x23, 0xb3, 0x8c,
0xf6, 0x19, 0x5e, 0xcc, 0x11, 0x73, 0x77, 0xb5, 0xc2, 0xee, 0xbe, 0x2e, 0x93, 0x86, 0x43, 0xe5,
0xd3, 0xcb, 0xd4, 0xab, 0x00, 0x65, 0x82, 0xd6, 0x66, 0x69, 0xfd, 0x7d, 0x58, 0x4a, 0xb7, 0x61,
0xf1, 0x88, 0x65, 0x7b, 0x78, 0x82, 0x4a, 0x31, 0x5e, 0x81, 0x66, 0xc8, 0xce, 0xfb, 0xe2, 0x88,
0x85, 0x19, 0x6f, 0x84, 0xec, 0x9c, 0x8f, 0xa1, 0x04, 0x96, 0xf2, 0xf1, 0xf2, 0xd6, 0xfd, 0xe9,
0x0c, 0xcc, 0x7f, 0x16, 0x9e, 0x45, 0xfe, 0x80, 0xa7, 0x01, 0xc7, 0x6c, 0x1c, 0xa9, 0x27, 0x75,
0xfc, 0x8d, 0x5e, 0x01, 0x7f, 0x2d, 0x8a, 0x33, 0x99, 0x9f, 0x53, 0x4d, 0xb4, 0x90, 0x49, 0x5e,
0xbf, 0x21, 0xa4, 0xcd, 0x40, 0xd0, 0x9b, 0x4c, 0xcc, 0x92, 0x14, 0xd9, 0xca, 0xeb, 0x09, 0x66,
0x8d, 0x7a, 0x02, 0x9e, 0xf0, 0x15, 0x0f, 0x61, 0xfc, 0x48, 0x1a, 0xae, 0x6a, 0x72, 0xaf, 0x37,
0x61, 0x22, 0xbe, 0xe5, 0xb6, 0x76, 0x5e, 0x7a, 0xbd, 0x26, 0x88, 0xf6, 0x58, 0x7c, 0x20, 0xc6,
0x08, 0x7d, 0x65, 0x42, 0xe8, 0x9f, 0x14, 0xab, 0x5a, 0x9a, 0x42, 0x4c, 0x0a, 0x30, 0x2a, 0xb5,
0x21, 0xd3, 0xba, 0x47, 0xec, 0x01, 0x44, 0x7d, 0x4a, 0x11, 0x37, 0x7c, 0x66, 0xf1, 0xa0, 0x27,
0x5b, 0xdc, 0x8f, 0xf1, 0x82, 0xe0, 0xd8, 0x1b, 0xbc, 0xec, 0x73, 0xe7, 0xa9, 0x2d, 0x72, 0x14,
0x16, 0x88, 0xab, 0x1e, 0x04, 0xd9, 0x59, 0x5f, 0x92, 0xe8, 0x88, 0xf7, 0x37, 0x03, 0xa2, 0x5f,
0x02, 0xd9, 0x1b, 0x0e, 0xe5, 0x09, 0xe9, 0x88, 0x22, 0xe7, 0xad, 0x63, 0xf1, 0xb6, 0x62, 0x8f,
0xb5, 0xca, 0x3d, 0xd2, 0x47, 0xd0, 0x3a, 0x34, 0x4a, 0x84, 0xf8, 0x61, 0xaa, 0xe2, 0x20, 0x29,
0x00, 0x06, 0x62, 0x4c, 0x58, 0x33, 0x27, 0xa4, 0xbf, 0x04, 0xe4, 0xc0, 0x4f, 0x33, 0xbd, 0x3e,
0x1d, 0xeb, 0xe9, 0xcc, 0x96, 0x11, 0xeb, 0x49, 0x8c, 0xc7, 0x7a, 0x7b, 0xe2, 0x91, 0xb0, 0xb8,
0xb1, 0x2d, 0x68, 0xf8, 0x02, 0x52, 0xba, 0x7c, 0x41, 0x5e, 0x02, 0x35, 0x52, 0xf7, 0xa3, 0x53,
0x22, 0x41, 0xcb, 0x54, 0xfc, 0xd8, 0x81, 0x79, 0xb9, 0x35, 0x34, 0xa9, 0x56, 0x71, 0x94, 0xd8,
0x98, 0x85, 0x55, 0xd7, 0xb7, 0x94, 0xa5, 0x6e, 0xa6, 0x4a, 0xea, 0x08, 0xd4, 0x63, 0x2f, 0x3b,
0xe5, 0xfe, 0x76, 0xd3, 0xe5, 0xbf, 0x55, 0x5c, 0x35, 0xab, 0xe3, 0x2a, 0xf5, 0x20, 0x2a, 0x17,
0xa5, 0xdf, 0xea, 0xee, 0x8b, 0x07, 0xd1, 0x1c, 0xce, 0x79, 0x20, 0x17, 0x58, 0xe4, 0x81, 0x1c,
0xea, 0xea, 0x7e, 0xda, 0x83, 0xee, 0x43, 0x16, 0xb0, 0x8c, 0xed, 0x05, 0x41, 0x91, 0xfe, 0x15,
0xb8, 0x5c, 0xd1, 0x27, 0xef, 0xfd, 0x63, 0x58, 0x7e, 0xc8, 0x8e, 0x27, 0xa3, 0x03, 0x76, 0x96,
0x27, 0xee, 0x09, 0xd4, 0xd3, 0xd3, 0xe8, 0x5c, 0x9e, 0x17, 0xff, 0x4d, 0x3e, 0x00, 0x08, 0x70,
0x4c, 0x3f, 0x8d, 0xd9, 0x40, 0x15, 0x78, 0x70, 0xe4, 0x28, 0x66, 0x03, 0xfa, 0x09, 0x10, 0x93,
0x8e, 0xdc, 0x02, 0xde, 0xc6, 0xc9, 0x71, 0x3f, 0x9d, 0xa6, 0x19, 0x1b, 0x2b, 0x45, 0x64, 0x42,
0xf4, 0x26, 0xb4, 0x0f, 0xbd, 0xa9, 0xcb, 0xbe, 0x92, 0x35, 0x67, 0x18, 0xbe, 0x79, 0x53, 0x14,
0x4f, 0x1d, 0xbe, 0xf1, 0x6e, 0xfa, 0x37, 0x35, 0x98, 0x13, 0x23, 0x91, 0xea, 0x90, 0xa5, 0x99,
0x1f, 0x8a, 0xdc, 0xb9, 0xa4, 0x6a, 0x40, 0xa5, 0xf3, 0xae, 0x55, 0x9c, 0xb7, 0x74, 0xb3, 0xd4,
0x63, 0xb8, 0x3c, 0x58, 0x0b, 0xe3, 0xd1, 0xa9, 0x3f, 0x66, 0xa2, 0xa4, 0xb0, 0x2e, 0xa3, 0x53,
0x05, 0x14, 0xe2, 0xe4, 0xfc, 0xce, 0x8b, 0xf5, 0x29, 0x41, 0x94, 0xa6, 0xc5, 0x84, 0x2a, 0x35,
0xcb, 0xbc, 0x28, 0x32, 0x2b, 0x69, 0x96, 0x92, 0x06, 0x69, 0xbc, 0x87, 0x06, 0x11, 0xbe, 0x97,
0xa5, 0x41, 0x08, 0x2c, 0x3d, 0x66, 0xcc, 0x65, 0x71, 0x94, 0xe8, 0xc2, 0xbd, 0x3f, 0x71, 0x60,
0x49, 0x5a, 0x15, 0xdd, 0x47, 0xae, 0x5b, 0x26, 0xc8, 0xa9, 0xca, 0xa9, 0xde, 0x80, 0x0e, 0x0f,
0xc2, 0x30, 0xc2, 0xe2, 0x11, 0x97, 0xcc, 0x40, 0x58, 0x20, 0xae, 0x49, 0xa5, 0xfe, 0xc6, 0x7e,
0x20, 0x19, 0x6c, 0x42, 0x68, 0x2e, 0x55, 0x90, 0xc6, 0xd9, 0xeb, 0xb8, 0xba, 0x4d, 0x0f, 0x61,
0xd9, 0x58, 0xaf, 0x14, 0xa8, 0x7b, 0xa0, 0x1e, 0xed, 0x44, 0x42, 0x41, 0xdc, 0x8b, 0x0d, 0xdb,
0x40, 0xe6, 0x9f, 0x59, 0x83, 0xe9, 0x5f, 0x39, 0x9c, 0x05, 0xd2, 0x0f, 0xd3, 0x15, 0x3b, 0x73,
0xc2, 0x35, 0x12, 0xd2, 0xbe, 0x7f, 0xc9, 0x95, 0x6d, 0xf2, 0xad, 0xf7, 0xf4, 0x6e, 0xf4, 0xfb,
0xda, 0x05, 0xbc, 0x99, 0xa9, 0xe2, 0xcd, 0x5b, 0x76, 0x7e, 0x7f, 0x1e, 0x66, 0xd3, 0x41, 0x14,
0x33, 0xba, 0xc2, 0x59, 0xa0, 0xd6, 0x2b, 0x58, 0xb0, 0xfb, 0xcf, 0x0e, 0x2c, 0x88, 0xf4, 0x98,
0x28, 0xdd, 0x65, 0x09, 0xc1, 0xf8, 0xcb, 0xa8, 0x08, 0x26, 0xda, 0xfd, 0x2c, 0x57, 0x16, 0xf7,
0xae, 0x54, 0xf6, 0x29, 0xdf, 0xfb, 0x47, 0x3f, 0xfb, 0xd7, 0x9f, 0xd4, 0xd6, 0xe8, 0xd2, 0xce,
0xd9, 0xed, 0x1d, 0xae, 0xe2, 0xd8, 0x39, 0x1f, 0x71, 0xd7, 0xd9, 0xc2, 0x59, 0xcc, 0x62, 0x61,
0x3d, 0x4b, 0x45, 0xd1, 0xb1, 0x9e, 0xa5, 0xb2, 0xba, 0xd8, 0x9a, 0x65, 0xc2, 0x47, 0xe8, 0x59,
0x76, 0xff, 0xe2, 0x0a, 0x34, 0x75, 0xa0, 0x48, 0x7e, 0x00, 0x1d, 0x2b, 0x15, 0x48, 0x14, 0xe1,
0xaa, 0xdc, 0x62, 0xef, 0x6a, 0x75, 0xa7, 0x9c, 0xf6, 0x43, 0x3e, 0x6d, 0x97, 0xac, 0xe3, 0xb4,
0x32, 0xd7, 0xb7, 0xc3, 0x53, 0xa4, 0xe2, 0x8d, 0xfe, 0x25, 0x2c, 0xd8, 0xa9, 0x42, 0x72, 0xd5,
0x3e, 0xed, 0xc2, 0x6c, 0x1f, 0x5c, 0xd0, 0x2b, 0xa7, 0xbb, 0xca, 0xa7, 0x5b, 0x27, 0xab, 0xe6,
0x74, 0x3a, 0x80, 0x63, 0xbc, 0xaa, 0xc2, 0xac, 0x36, 0x26, 0x8a, 0x5e, 0x75, 0x15, 0x72, 0xef,
0x72, 0xb9, 0xb2, 0x58, 0x96, 0x22, 0xd3, 0x2e, 0x9f, 0x8a, 0x10, 0xce, 0x50, 0xb3, 0xd8, 0x98,
0x7c, 0x1f, 0x9a, 0xba, 0x64, 0x92, 0x6c, 0x18, 0x05, 0xa2, 0x66, 0x11, 0x66, 0xaf, 0x5b, 0xee,
0xa8, 0x3a, 0x2a, 0x93, 0x32, 0x0a, 0xc4, 0x01, 0xac, 0x49, 0x8b, 0x7b, 0xcc, 0x7e, 0x9e, 0x9d,
0x54, 0xd4, 0x48, 0xdf, 0x72, 0xc8, 0x3d, 0x68, 0xa8, 0x2a, 0x52, 0xb2, 0x5e, 0x5d, 0xca, 0xda,
0xdb, 0x28, 0xe1, 0x52, 0x2f, 0xec, 0x01, 0xe4, 0x45, 0x93, 0xa4, 0x7b, 0x51, 0x6d, 0xa7, 0x66,
0x62, 0x45, 0x85, 0xe5, 0x88, 0xd7, 0x8c, 0xda, 0x35, 0x99, 0xe4, 0x6b, 0xf9, 0xf8, 0xca, 0x6a,
0xcd, 0xb7, 0x10, 0xa4, 0xeb, 0x9c, 0x77, 0x4b, 0x64, 0x01, 0x79, 0x17, 0xb2, 0x73, 0x55, 0x5f,
0xf4, 0x10, 0x5a, 0x46, 0x21, 0x26, 0x51, 0x14, 0xca, 0x45, 0x9c, 0xbd, 0x5e, 0x55, 0x97, 0x5c,
0xee, 0xaf, 0x41, 0xc7, 0xaa, 0xa8, 0xd4, 0x37, 0xa3, 0xaa, 0x5e, 0x53, 0xdf, 0x8c, 0xea, 0x22,
0xcc, 0xef, 0x41, 0xcb, 0xa8, 0x7f, 0x24, 0xc6, 0x33, 0x74, 0xa1, 0xbe, 0x51, 0xaf, 0xa8, 0xa2,
0x5c, 0x92, 0xae, 0xf2, 0xfd, 0x2e, 0xd0, 0x26, 0xee, 0x97, 0x17, 0xd9, 0xa0, 0x90, 0xfc, 0x00,
0x16, 0xec, 0xba, 0x47, 0x7d, 0xab, 0x2a, 0x2b, 0x28, 0xf5, 0xad, 0xba, 0xa0, 0x58, 0x52, 0x0a,
0xe4, 0xd6, 0x8a, 0x9e, 0x64, 0xe7, 0xb5, 0x4c, 0x88, 0xbe, 0x21, 0xdf, 0x45, 0xd5, 0x21, 0xab,
0x9e, 0x48, 0x5e, 0x07, 0x6a, 0xd7, 0x46, 0x69, 0x69, 0x2f, 0x15, 0x48, 0xd1, 0x65, 0x4e, 0xbc,
0x45, 0xf2, 0x1d, 0x90, 0xcf, 0x61, 0x5e, 0x56, 0x3f, 0x91, 0xb5, 0x5c, 0xaa, 0x8d, 0xa4, 0x52,
0x6f, 0xbd, 0x08, 0x4b, 0x62, 0x2b, 0x9c, 0x58, 0x87, 0xb4, 0x90, 0xd8, 0x88, 0x65, 0x3e, 0xd2,
0x08, 0x60, 0xd1, 0x7e, 0x3e, 0x4a, 0x35, 0x3b, 0x2a, 0x1f, 0xae, 0x35, 0x3b, 0xaa, 0xdf, 0xa2,
0x6c, 0x25, 0xa3, 0x94, 0xcb, 0x8e, 0xaa, 0x32, 0xf8, 0x4d, 0x68, 0x9b, 0xa5, 0x76, 0x5a, 0x63,
0x57, 0x94, 0xe5, 0x69, 0x8d, 0x5d, 0x55, 0x9b, 0xa7, 0x8e, 0x96, 0xb4, 0xcd, 0x69, 0xc8, 0xf7,
0x60, 0xd1, 0x78, 0xe7, 0x3c, 0x9a, 0x86, 0x03, 0x2d, 0x3a, 0xe5, 0x6a, 0x90, 0x5e, 0x95, 0xe9,
0xa4, 0x1b, 0x9c, 0xf0, 0x32, 0xb5, 0x08, 0xa3, 0xd8, 0x3c, 0x80, 0x96, 0xf9, 0x86, 0xfa, 0x16,
0xba, 0x1b, 0x46, 0x97, 0x59, 0x9f, 0x71, 0xcb, 0x21, 0x7f, 0xe4, 0x40, 0xdb, 0x2c, 0x12, 0x22,
0x56, 0x5e, 0xa6, 0x40, 0xa7, 0x6b, 0xf6, 0x99, 0x84, 0xe8, 0x53, 0xbe, 0xc8, 0xfd, 0xad, 0xc7,
0x16, 0x93, 0x5f, 0x5b, 0x2e, 0xd1, 0xb6, 0xf9, 0xcf, 0x01, 0x6f, 0x8a, 0x9d, 0x66, 0xb5, 0xcc,
0x9b, 0x5b, 0x0e, 0xb9, 0x2b, 0xfe, 0x05, 0x44, 0x85, 0x27, 0xc4, 0x50, 0x6b, 0x45, 0x76, 0x99,
0xff, 0x57, 0xb1, 0xe9, 0xdc, 0x72, 0xc8, 0x6f, 0x89, 0xff, 0x07, 0x90, 0xdf, 0x72, 0xae, 0xbf,
0xef, 0xf7, 0xf4, 0x06, 0xdf, 0xc9, 0x87, 0xf4, 0xb2, 0xb5, 0x93, 0xa2, 0x5e, 0x3f, 0x04, 0xc8,
0x63, 0x4d, 0x52, 0x08, 0xbc, 0xb4, 0xc6, 0x2b, 0x87, 0xa3, 0xf6, 0x69, 0xaa, 0xf8, 0x4c, 0x28,
0x81, 0xb6, 0x11, 0xe5, 0xa5, 0xfa, 0x38, 0xcb, 0x31, 0x63, 0xaf, 0x57, 0xd5, 0x25, 0xe9, 0x7f,
0x9d, 0xd3, 0xff, 0x80, 0x5c, 0x31, 0xe9, 0xef, 0xbc, 0x36, 0x63, 0xcc, 0x37, 0xe4, 0x4b, 0xe8,
0x1c, 0x44, 0xd1, 0xcb, 0x49, 0xac, 0xd3, 0x19, 0x76, 0xd4, 0x84, 0x71, 0x6e, 0xaf, 0xb0, 0x29,
0x7a, 0x9d, 0x53, 0xbe, 0x42, 0x2e, 0xdb, 0x94, 0xf3, 0xc8, 0xf7, 0x0d, 0xf1, 0x60, 0x59, 0x5b,
0x3b, 0xbd, 0x91, 0x9e, 0x4d, 0xc7, 0x0c, 0x40, 0x4b, 0x73, 0x58, 0xfe, 0x87, 0x9e, 0x23, 0x55,
0x34, 0x6f, 0x39, 0xe4, 0x10, 0xda, 0x0f, 0xd9, 0x20, 0x1a, 0x32, 0x19, 0xe8, 0xac, 0xe4, 0x2b,
0xd7, 0x11, 0x52, 0xaf, 0x63, 0x81, 0xb6, 0x06, 0x88, 0xbd, 0x69, 0xc2, 0xbe, 0xda, 0x79, 0x2d,
0x43, 0xa8, 0x37, 0x4a, 0x03, 0xa8, 0xb0, 0xcf, 0xd2, 0x00, 0x85, 0x38, 0xd1, 0xd2, 0x00, 0xa5,
0x38, 0xd1, 0xd2, 0x00, 0x2a, 0xec, 0x24, 0x01, 0x46, 0x8f, 0x85, 0xd0, 0x52, 0xdb, 0xcc, 0x8b,
0x02, 0xd2, 0xde, 0xb5, 0x8b, 0x07, 0xd8, 0xb3, 0x6d, 0xd9, 0xb3, 0x1d, 0x41, 0xe7, 0x21, 0x13,
0xcc, 0x12, 0xef, 0x0c, 0x3d, 0x5b, 0xa5, 0x98, 0x6f, 0x12, 0x45, 0x75, 0xc3, 0xfb, 0x6c, 0x05,
0xcf, 0x93, 0xfc, 0xe4, 0xfb, 0xd0, 0x7a, 0xc2, 0x32, 0xf5, 0xb0, 0xa0, 0x3d, 0x8f, 0xc2, 0x4b,
0x43, 0xaf, 0xe2, 0x5d, 0x82, 0x5e, 0xe3, 0xd4, 0x7a, 0xa4, 0xab, 0xa9, 0xed, 0xb0, 0xe1, 0x88,
0x89, 0xcb, 0xdf, 0xf7, 0x87, 0x6f, 0xc8, 0xaf, 0x73, 0xe2, 0xfa, 0xd5, 0x71, 0xdd, 0xc8, 0x47,
0x9b, 0xc4, 0x17, 0x0b, 0x78, 0x15, 0xe5, 0x30, 0x1a, 0x32, 0xc3, 0xd4, 0x85, 0xd0, 0x32, 0x9e,
0x98, 0xf5, 0x85, 0x2a, 0xbf, 0x5b, 0xeb, 0x0b, 0x55, 0xf1, 0x22, 0x4d, 0x37, 0xf9, 0x3c, 0x94,
0x5c, 0xcb, 0xe7, 0x11, 0xaf, 0xd0, 0xf9, 0x4c, 0x3b, 0xaf, 0xbd, 0x71, 0xf6, 0x86, 0xbc, 0xe0,
0x85, 0xc1, 0xe6, 0xe3, 0x49, 0xee, 0xf9, 0x14, 0xdf, 0x59, 0x34, 0xb3, 0x8c, 0x2e, 0xdb, 0x1b,
0x12, 0x53, 0x71, 0x8b, 0xf8, 0x2d, 0x80, 0xa3, 0x2c, 0x8a, 0x1f, 0x7a, 0x6c, 0x1c, 0x85, 0xb9,
0x26, 0xcb, 0x1f, 0x08, 0x72, 0x4d, 0x66, 0xbc, 0x12, 0x90, 0x17, 0x86, 0xef, 0x69, 0xbd, 0x3d,
0x29, 0xe1, 0xba, 0xf0, 0x0d, 0x41, 0x33, 0xa4, 0xe2, 0x1d, 0x41, 0xb9, 0xa1, 0x22, 0x39, 0x6a,
0xb8, 0xa1, 0x56, 0x76, 0xd5, 0x70, 0x43, 0xed, 0x2c, 0x2a, 0xba, 0xa1, 0x79, 0x16, 0x44, 0xbb,
0xa1, 0xa5, 0x04, 0x8b, 0xd6, 0xa1, 0x15, 0x29, 0x93, 0x43, 0x68, 0xe6, 0xa1, 0xb8, 0x9a, 0xa8,
0x18, 0xb8, 0x6b, 0x63, 0x55, 0x8a, 0x90, 0xe9, 0x12, 0xe7, 0x33, 0x90, 0x06, 0xf2, 0x99, 0x3f,
0xb1, 0x3f, 0x03, 0x10, 0xbb, 0x7b, 0x8c, 0x2d, 0x83, 0xa4, 0x15, 0x08, 0x9b, 0x24, 0xed, 0x88,
0x53, 0x79, 0x32, 0x54, 0x93, 0xbc, 0xeb, 0x6c, 0x1d, 0xcf, 0xf1, 0xff, 0x5f, 0xfd, 0xe6, 0x7f,
0x05, 0x00, 0x00, 0xff, 0xff, 0x5e, 0x53, 0x68, 0x69, 0xf1, 0x3a, 0x00, 0x00,
}

@ -564,7 +564,15 @@ func RegisterWalletUnlockerHandlerFromEndpoint(ctx context.Context, mux *runtime
// RegisterWalletUnlockerHandler registers the http handlers for service WalletUnlocker to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func RegisterWalletUnlockerHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
client := NewWalletUnlockerClient(conn)
return RegisterWalletUnlockerHandlerClient(ctx, mux, NewWalletUnlockerClient(conn))
}
// RegisterWalletUnlockerHandler registers the http handlers for service WalletUnlocker to "mux".
// The handlers forward requests to the grpc endpoint over the given implementation of "WalletUnlockerClient".
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "WalletUnlockerClient"
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
// "WalletUnlockerClient" to call the correct interceptors.
func RegisterWalletUnlockerHandlerClient(ctx context.Context, mux *runtime.ServeMux, client WalletUnlockerClient) error {
mux.Handle("POST", pattern_WalletUnlocker_CreateWallet_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(ctx)
@ -667,7 +675,15 @@ func RegisterLightningHandlerFromEndpoint(ctx context.Context, mux *runtime.Serv
// RegisterLightningHandler registers the http handlers for service Lightning to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func RegisterLightningHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
client := NewLightningClient(conn)
return RegisterLightningHandlerClient(ctx, mux, NewLightningClient(conn))
}
// RegisterLightningHandler registers the http handlers for service Lightning to "mux".
// The handlers forward requests to the grpc endpoint over the given implementation of "LightningClient".
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "LightningClient"
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
// "LightningClient" to call the correct interceptors.
func RegisterLightningHandlerClient(ctx context.Context, mux *runtime.ServeMux, client LightningClient) error {
mux.Handle("GET", pattern_Lightning_WalletBalance_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(ctx)

@ -840,6 +840,31 @@ message OpenStatusUpdate {
}
}
message PendingHTLC {
/// The direction within the channel that the htlc was sent
bool incoming = 1 [ json_name = "incoming" ];
/// The total value of the htlc
int64 amount = 2 [ json_name = "amount" ];
/// The final output to be swept back to the user's wallet
string outpoint = 3 [ json_name = "outpoint" ];
/// The next block height at which we can spend the current stage
uint32 maturity_height = 4 [ json_name = "maturity_height" ];
/**
The number of blocks remaining until the current stage can be swept.
Negative values indicate how many blocks have passed since becoming
mature.
*/
int32 blocks_til_maturity = 5 [ json_name = "blocks_til_maturity" ];
/// Indicates whether the htlc is in its first or second stage of recovery
uint32 stage = 6 [ json_name = "stage" ];
}
message PendingChannelRequest {}
message PendingChannelResponse {
message PendingChannel {
@ -894,8 +919,6 @@ message PendingChannelResponse {
/// The pending channel to be force closed
PendingChannel channel = 1 [ json_name = "channel" ];
// TODO(roasbeef): HTLC's as well?
/// The transaction id of the closing transaction
string closing_txid = 2 [ json_name = "closing_txid" ];
@ -905,8 +928,17 @@ message PendingChannelResponse {
/// The height at which funds can be sweeped into the wallet
uint32 maturity_height = 4 [ json_name = "maturity_height" ];
/// Remaining # of blocks until funds can be sweeped into the wallet
uint32 blocks_til_maturity = 5 [ json_name = "blocks_til_maturity" ];
/*
Remaining # of blocks until the commitment output can be swept.
Negative values indicate how many blocks have passed since becoming
mature.
*/
int32 blocks_til_maturity = 5 [ json_name = "blocks_til_maturity" ];
/// The total value of funds successfully recovered from this channel
int64 recovered_balance = 6 [ json_name = "recovered_balance" ];
repeated PendingHTLC pending_htlcs = 8 [ json_name = "pending_htlcs" ];
}
/// The balance in satoshis encumbered in pending channels

@ -732,8 +732,19 @@
},
"blocks_til_maturity": {
"type": "integer",
"format": "int32",
"description": "Remaining # of blocks until the commitment output can be swept.\nNegative values indicate how many blocks have passed since becoming\nmature."
},
"recovered_balance": {
"type": "string",
"format": "int64",
"title": "/ Remaining # of blocks until funds can be sweeped into the wallet"
"title": "/ The total value of funds successfully recovered from this channel"
},
"pending_htlcs": {
"type": "array",
"items": {
"$ref": "#/definitions/lnrpcPendingHTLC"
}
}
}
},
@ -1743,6 +1754,40 @@
}
}
},
"lnrpcPendingHTLC": {
"type": "object",
"properties": {
"incoming": {
"type": "boolean",
"format": "boolean",
"title": "/ The direction within the channel that the htlc was sent"
},
"amount": {
"type": "string",
"format": "int64",
"title": "/ The total value of the htlc"
},
"outpoint": {
"type": "string",
"title": "/ The final output to be swept back to the user's wallet"
},
"maturity_height": {
"type": "integer",
"format": "int64",
"title": "/ The next block height at which we can spend the current stage"
},
"blocks_til_maturity": {
"type": "integer",
"format": "int32",
"description": "*\nThe number of blocks remaining until the current stage can be swept.\nNegative values indicate how many blocks have passed since becoming\nmature."
},
"stage": {
"type": "integer",
"format": "int64",
"title": "/ Indicates whether the htlc is in its first or second stage of recovery"
}
}
},
"lnrpcPendingUpdate": {
"type": "object",
"properties": {

@ -809,6 +809,34 @@ func htlcSpendSuccess(signer Signer, signDesc *SignDescriptor,
return witnessStack, nil
}
// HtlcSpendSuccess exposes the public witness generation function for spending
// an HTLC success transaction, either due to an expiring time lock or having
// had the payment preimage.
// NOTE: The caller MUST set the txn version, sequence number, and sign
// descriptor's sig hash cache before invocation.
func HtlcSpendSuccess(signer Signer, signDesc *SignDescriptor,
sweepTx *wire.MsgTx) (wire.TxWitness, error) {
// With the proper sequence an version set, we'll now sign the timeout
// transaction using the passed signed descriptor. In order to generate
// a valid signature, then signDesc should be using the base delay
// public key, and the proper single tweak bytes.
sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc)
if err != nil {
return nil, err
}
// We set a zero as the first element the witness stack (ignoring the
// witness script), in order to force execution to the second portion
// of the if clause.
witnessStack := wire.TxWitness(make([][]byte, 3))
witnessStack[0] = append(sweepSig, byte(txscript.SigHashAll))
witnessStack[1] = nil
witnessStack[2] = signDesc.WitnessScript
return witnessStack, nil
}
// htlcTimeoutRevoke spends a second-level HTLC output. This function is to be
// used by the sender or receiver of an HTLC to claim the HTLC after a revoked
// commitment transaction was broadcast.

@ -186,20 +186,29 @@ const (
// weight limits.
MaxHTLCNumber = 966
// ToLocalPenaltyScriptSize 83 bytes
// ToLocalScriptSize 83 bytes
// - OP_IF: 1 byte
// - OP_DATA: 1 byte (revocationkey length)
// - revocationkey: 33 bytes
// - OP_CHECKSIG: 1 byte
// - OP_ELSE: 1 byte
// - OP_DATA: 1 byte (localkey length)
// - localkey: 33 bytes
// - local_delay_key: 33 bytes
// - OP_CHECKSIG_VERIFY: 1 byte
// - OP_DATA: 1 byte (delay length)
// - delay: 8 bytes
// -OP_CHECKSEQUENCEVERIFY: 1 byte
// - OP_ENDIF: 1 byte
ToLocalPenaltyScriptSize = 1 + 1 + 33 + 1 + 1 + 1 + 33 + 1 + 1 + 8 + 1 + 1
ToLocalScriptSize = 1 + 1 + 33 + 1 + 1 + 1 + 33 + 1 + 1 + 8 + 1 + 1
// ToLocalTimeoutWitnessSize x bytes
// - number_of_witness_elements: 1 byte
// - local_delay_sig_length: 1 byte
// - local_delay_sig: 73 bytes
// - zero_length: 1 byte
// - witness_script_length: 1 byte
// - witness_script (to_local_script)
ToLocalTimeoutWitnessSize = 1 + 1 + 73 + 1 + 1 + ToLocalScriptSize
// ToLocalPenaltyWitnessSize 160 bytes
// - number_of_witness_elements: 1 byte
@ -208,9 +217,9 @@ const (
// - one_length: 1 byte
// - witness_script_length: 1 byte
// - witness_script (to_local_script)
ToLocalPenaltyWitnessSize = 1 + 1 + 73 + 1 + 1 + ToLocalPenaltyScriptSize
ToLocalPenaltyWitnessSize = 1 + 1 + 73 + 1 + 1 + ToLocalScriptSize
// AcceptedHtlcPenaltyScriptSize 139 bytes
// AcceptedHtlcScriptSize 139 bytes
// - OP_DUP: 1 byte
// - OP_HASH160: 1 byte
// - OP_DATA: 1 byte (RIPEMD160(SHA256(revocationkey)) length)
@ -245,9 +254,22 @@ const (
// - OP_CHECKSIG: 1 byte
// - OP_ENDIF: 1 byte
// - OP_ENDIF: 1 byte
AcceptedHtlcPenaltyScriptSize = 3*1 + 20 + 5*1 + 33 + 7*1 + 20 + 4*1 +
AcceptedHtlcScriptSize = 3*1 + 20 + 5*1 + 33 + 7*1 + 20 + 4*1 +
33 + 5*1 + 4 + 5*1
// AcceptedHtlcSuccessWitnessSize 325 bytes
// - number_of_witness_elements: 1 byte
// - nil_length: 1 byte
// - sig_alice_length: 1 byte
// - sig_alice: 73 bytes
// - sig_bob_length: 1 byte
// - sig_bob: 73 bytes
// - preimage_length: 1 byte
// - preimage: 32 bytes
// - witness_script_length: 1 byte
// - witness_script (accepted_htlc_script)
AcceptedHtlcSuccessWitnessSize = 1 + 1 + 73 + 1 + 73 + 1 + 32 + 1 + AcceptedHtlcScriptSize
// AcceptedHtlcPenaltyWitnessSize 249 bytes
// - number_of_witness_elements: 1 byte
// - revocation_sig_length: 1 byte
@ -256,8 +278,7 @@ const (
// - revocation_key: 33 bytes
// - witness_script_length: 1 byte
// - witness_script (accepted_htlc_script)
AcceptedHtlcPenaltyWitnessSize = 1 + 1 + 73 + 1 + 33 + 1 +
AcceptedHtlcPenaltyScriptSize
AcceptedHtlcPenaltyWitnessSize = 1 + 1 + 73 + 1 + 33 + 1 + AcceptedHtlcScriptSize
// OfferedHtlcScriptSize 133 bytes
// - OP_DUP: 1 byte
@ -303,6 +324,18 @@ const (
// - witness_script (offered_htlc_script)
OfferedHtlcWitnessSize = 1 + 1 + 73 + 1 + 33 + 1 + OfferedHtlcScriptSize
// OfferedHtlcTimeoutWitnessSize 285 bytes
// - number_of_witness_elements: 1 byte
// - nil_length: 1 byte
// - sig_alice_length: 1 byte
// - sig_alice: 73 bytes
// - sig_bob_length: 1 byte
// - sig_bob: 73 bytes
// - nil_length: 1 byte
// - witness_script_length: 1 byte
// - witness_script (offered_htlc_script)
OfferedHtlcTimeoutWitnessSize = 1 + 1 + 1 + 73 + 1 + 73 + 1 + 1 + OfferedHtlcScriptSize
// OfferedHtlcPenaltyWitnessSize 243 bytes
// - number_of_witness_elements: 1 byte
// - revocation_sig_length: 1 byte

@ -13,17 +13,18 @@ import (
type WitnessType uint16
const (
// CommitmentTimeLock is a witness that allows us to spend the output of a
// commitment transaction after a relative lock-time lockout.
// CommitmentTimeLock is a witness that allows us to spend the output of
// a commitment transaction after a relative lock-time lockout.
CommitmentTimeLock WitnessType = 0
// CommitmentNoDelay is a witness that allows us to spend a settled no-delay
// output immediately on a counterparty's commitment transaction.
// CommitmentNoDelay is a witness that allows us to spend a settled
// no-delay output immediately on a counterparty's commitment
// transaction.
CommitmentNoDelay WitnessType = 1
// CommitmentRevoke is a witness that allows us to sweep the settled output
// of a malicious counterparty's who broadcasts a revoked commitment
// transaction.
// CommitmentRevoke is a witness that allows us to sweep the settled
// output of a malicious counterparty's who broadcasts a revoked
// commitment transaction.
CommitmentRevoke WitnessType = 2
// HtlcOfferedRevoke is a witness that allows us to sweep an HTLC
@ -33,6 +34,15 @@ const (
// HtlcAcceptedRevoke is a witness that allows us to sweep an HTLC
// output that we accepted from the counterparty.
HtlcAcceptedRevoke WitnessType = 4
// HtlcOfferedTimeout is a witness that allows us to sweep an HTLC
// output that we extended to a party, but was never fulfilled.
HtlcOfferedTimeout WitnessType = 5
// HtlcAcceptedSuccess is a witness that allows us to sweep an HTLC
// output that was offered to us, and for which we have a payment
// preimage.
HtlcAcceptedSuccess WitnessType = 6
)
// WitnessGenerator represents a function which is able to generate the final
@ -64,6 +74,8 @@ func (wt WitnessType) GenWitnessFunc(signer Signer,
return ReceiverHtlcSpendRevoke(signer, desc, tx)
case HtlcAcceptedRevoke:
return SenderHtlcSpendRevoke(signer, desc, tx)
case HtlcOfferedTimeout:
return HtlcSpendSuccess(signer, desc, tx)
default:
return nil, fmt.Errorf("unknown witness type: %v", wt)
}

@ -25,19 +25,18 @@ import (
// |
// | LAST PURGED + FINALIZED HEIGHTS
// |
// | Each nursery store tracks a "last purged height", which records the
// | most recent block height for which the nursery store has purged all
// | state. This value lags behind the best block height for reorg safety,
// | and serves as a starting height for rescans after a restart. It also
// | tracks a "last finalized height", which records the last block height
// | that the nursery attempted to graduate. If a finalized height has
// | kindergarten outputs, the sweep txn for these outputs will be stored in
// | the height bucket. This ensure that the same txid will be used after
// | restarts. Otherwise, the nursery will be unable to recover the txid
// | of kindergarten sweep transaction it has already broadcast.
// | Each nursery store tracks a "last graduated height", which records the
// | most recent block height for which the nursery store has successfully
// | graduated all outputs. It also tracks a "last finalized height", which
// | records the last block height that the nursery attempted to graduate
// | If a finalized height has kindergarten outputs, the sweep txn for these
// | outputs will be stored in the height bucket. This ensure that the same
// | txid will be used after restarts. Otherwise, the nursery will be unable
// | to recover the txid of kindergarten sweep transaction it has already
// | broadcast.
// |
// ├── last-purged-height-key: <last-purged-height>
// ├── last-finalized-height-key: <last-finalized-height>
// ├── last-graduated-height-key: <last-graduated-height>
// |
// | CHANNEL INDEX
// |
@ -142,13 +141,17 @@ type NurseryStore interface {
// nursery store finalized a kindergarten class.
LastFinalizedHeight() (uint32, error)
// PurgeHeight deletes specified the height bucket if it exists, and
// records it as that last purged height.
PurgeHeight(height uint32) error
// GraduateHeight records the provided height as the last height for
// which the nursery store successfully graduated all outputs.
GraduateHeight(height uint32) error
// LastPurgedHeight returns the last block height for which the nursery
// store has purged all persistent state.
LastPurgedHeight() (uint32, error)
// LastGraduatedHeight returns the last block height for which the
// nursery store successfully graduated all outputs.
LastGraduatedHeight() (uint32, error)
// HeightsBelowOrEqual returns the lowest non-empty heights in the
// height index, that exist at or below the provided upper bound.
HeightsBelowOrEqual(height uint32) ([]uint32, error)
// ForChanOutputs iterates over all outputs being incubated for a
// particular channel point. This method accepts a callback that allows
@ -179,9 +182,9 @@ var (
// last finalized height.
lastFinalizedHeightKey = []byte("last-finalized-height")
// lastPurgedHeightKey is a static key used to retrieve the height of
// the last bucket that was purged.
lastPurgedHeightKey = []byte("last-purged-height")
// lastGraduatedHeightKey is a static key used to retrieve the height of
// the last bucket that successfully graduated all outputs.
lastGraduatedHeightKey = []byte("last-graduated-height")
// channelIndexKey is a static key used to lookup the bucket containing
// all of the nursery's active channels.
@ -557,10 +560,10 @@ func (ns *nurseryStore) GraduateKinder(height uint32) error {
})
}
// FinalizeKinder accepts a block height as a parameter and purges its
// persistent state for all outputs at that height. During a restart, the utxo
// nursery will begin it's recovery procedure from the next height that has
// yet to be finalized.
// FinalizeKinder accepts a block height and a finalized kindergarten sweep
// transaction, persisting the transaction at the appropriate height bucket. The
// nursery store's last finalized height is also updated with the provided
// height.
func (ns *nurseryStore) FinalizeKinder(height uint32,
finalTx *wire.MsgTx) error {
@ -569,17 +572,12 @@ func (ns *nurseryStore) FinalizeKinder(height uint32,
})
}
// PurgeHeight accepts a block height as a parameter and purges its persistent
// state for all outputs at that height. During a restart, the utxo nursery will
// begin it's recovery procedure from the next height that has yet to be
// finalized.
func (ns *nurseryStore) PurgeHeight(height uint32) error {
return ns.db.Update(func(tx *bolt.Tx) error {
if err := ns.purgeHeightBucket(tx, height); err != nil {
return err
}
// GraduateHeight persists the provided height as the nursery store's last
// graduated height.
func (ns *nurseryStore) GraduateHeight(height uint32) error {
return ns.putLastPurgedHeight(tx, height)
return ns.db.Update(func(tx *bolt.Tx) error {
return ns.putLastGraduatedHeight(tx, height)
})
}
@ -725,6 +723,45 @@ func (ns *nurseryStore) FetchPreschools() ([]kidOutput, error) {
return kids, nil
}
// HeightsBelowOrEqual returns a slice of all non-empty heights in the height
// index at or below the provided upper bound.
func (ns *nurseryStore) HeightsBelowOrEqual(height uint32) ([]uint32, error) {
var activeHeights []uint32
err := ns.db.View(func(tx *bolt.Tx) error {
// Ensure that the chain bucket for this nursery store exists.
chainBucket := tx.Bucket(ns.pfxChainKey)
if chainBucket == nil {
return nil
}
// Ensure that the height index has been properly initialized for this
// chain.
hghtIndex := chainBucket.Bucket(heightIndexKey)
if hghtIndex == nil {
return nil
}
// Serialize the provided height, as this will form the name of the
// bucket.
var lower, upper [4]byte
byteOrder.PutUint32(upper[:], height)
c := hghtIndex.Cursor()
for k, _ := c.Seek(lower[:]); bytes.Compare(k, upper[:]) <= 0 &&
len(k) == 4; k, _ = c.Next() {
activeHeights = append(activeHeights, byteOrder.Uint32(k))
}
return nil
})
if err != nil {
return nil, err
}
return activeHeights, nil
}
// ForChanOutputs iterates over all outputs being incubated for a particular
// channel point. This method accepts a callback that allows the caller to
// process each key-value pair. The key will be a prefixed outpoint, and the
@ -863,8 +900,7 @@ func (ns *nurseryStore) RemoveChannel(chanPoint *wire.OutPoint) error {
}
// LastFinalizedHeight returns the last block height for which the nursery
// store has purged all persistent state. This occurs after a fixed interval
// for reorg safety.
// store has finalized a kindergarten class.
func (ns *nurseryStore) LastFinalizedHeight() (uint32, error) {
var lastFinalizedHeight uint32
err := ns.db.View(func(tx *bolt.Tx) error {
@ -876,18 +912,17 @@ func (ns *nurseryStore) LastFinalizedHeight() (uint32, error) {
return lastFinalizedHeight, err
}
// LastPurgedHeight returns the last block height for which the nursery store
// has purged all persistent state. This occurs after a fixed interval for reorg
// safety.
func (ns *nurseryStore) LastPurgedHeight() (uint32, error) {
var lastPurgedHeight uint32
// LastGraduatedHeight returns the last block height for which the nursery
// store has successfully graduated all outputs.
func (ns *nurseryStore) LastGraduatedHeight() (uint32, error) {
var lastGraduatedHeight uint32
err := ns.db.View(func(tx *bolt.Tx) error {
var err error
lastPurgedHeight, err = ns.getLastPurgedHeight(tx)
lastGraduatedHeight, err = ns.getLastGraduatedHeight(tx)
return err
})
return lastPurgedHeight, err
return lastGraduatedHeight, err
}
// Helper Methods
@ -1091,24 +1126,6 @@ func (ns *nurseryStore) getHeightBucket(tx *bolt.Tx,
return hghtBucket
}
// purgeHeightBucket ensures that the height bucket at the provided index is
// purged from the nursery store.
func (ns *nurseryStore) purgeHeightBucket(tx *bolt.Tx, height uint32) error {
// Ensure that the height bucket already exists.
_, hghtIndex, hghtBucket := ns.getHeightBucketPath(tx, height)
if hghtBucket == nil {
return nil
}
// Serialize the provided height, as this will form the name of the
// bucket.
var heightBytes [4]byte
byteOrder.PutUint32(heightBytes[:], height)
// Finally, delete the bucket in question.
return removeBucketIfExists(hghtIndex, heightBytes[:])
}
// createHeightChanBucket creates or retrieves an existing height-channel bucket
// for the provided block height and channel point. This method will attempt to
// instantiate all buckets along the path if required.
@ -1365,29 +1382,29 @@ func (ns *nurseryStore) getFinalizedTxn(tx *bolt.Tx,
return txn, nil
}
// getLastPurgedHeight is a helper method that retrieves the last height for
// which the database purged its persistent state.
func (ns *nurseryStore) getLastPurgedHeight(tx *bolt.Tx) (uint32, error) {
// getLastGraduatedHeight is a helper method that retrieves the last height for
// which the database graduated all outputs successfully.
func (ns *nurseryStore) getLastGraduatedHeight(tx *bolt.Tx) (uint32, error) {
// Retrieve the chain bucket associated with the given nursery store.
chainBucket := tx.Bucket(ns.pfxChainKey)
if chainBucket == nil {
return 0, nil
}
// Lookup the last purged height in the top-level chain bucket.
heightBytes := chainBucket.Get(lastPurgedHeightKey)
// Lookup the last graduated height in the top-level chain bucket.
heightBytes := chainBucket.Get(lastGraduatedHeightKey)
if heightBytes == nil {
// We have never purged before, return height 0.
// We have never graduated before, return height 0.
return 0, nil
}
// Otherwise, parse the bytes and return the last purged height.
// Otherwise, parse the bytes and return the last graduated height.
return byteOrder.Uint32(heightBytes), nil
}
// pubLastPurgedHeight is a helper method that writes the provided height under
// the last purged height key.
func (ns *nurseryStore) putLastPurgedHeight(tx *bolt.Tx, height uint32) error {
// pubLastGraduatedHeight is a helper method that writes the provided height under
// the last graduated height key.
func (ns *nurseryStore) putLastGraduatedHeight(tx *bolt.Tx, height uint32) error {
// Ensure that the chain bucket for this nursery store exists.
chainBucket, err := tx.CreateBucketIfNotExists(ns.pfxChainKey)
@ -1395,12 +1412,12 @@ func (ns *nurseryStore) putLastPurgedHeight(tx *bolt.Tx, height uint32) error {
return err
}
// Serialize the provided last-purged height, and store it in the
// Serialize the provided last-gradauted height, and store it in the
// top-level chain bucket for this nursery store.
var lastHeightBytes [4]byte
byteOrder.PutUint32(lastHeightBytes[:], height)
return chainBucket.Put(lastPurgedHeightKey, lastHeightBytes[:])
return chainBucket.Put(lastGraduatedHeightKey, lastHeightBytes[:])
}
// errBucketNotEmpty signals that an attempt to prune a particular

754
nursery_store_test.go Normal file

@ -0,0 +1,754 @@
// +build !rpctest
package main
import (
"io/ioutil"
"os"
"reflect"
"testing"
"github.com/btcsuite/btclog"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/roasbeef/btcd/wire"
)
func init() {
// Disable logging to prevent panics bc. of global state
channeldb.UseLogger(btclog.Disabled)
utxnLog = btclog.Disabled
}
// makeTestDB creates a new instance of the ChannelDB for testing purposes. A
// callback which cleans up the created temporary directories is also returned
// and intended to be executed after the test completes.
func makeTestDB() (*channeldb.DB, func(), error) {
// First, create a temporary directory to be used for the duration of
// this test.
tempDirName, err := ioutil.TempDir("", "channeldb")
if err != nil {
return nil, nil, err
}
// Next, create channeldb for the first time.
cdb, err := channeldb.Open(tempDirName)
if err != nil {
return nil, nil, err
}
cleanUp := func() {
cdb.Close()
os.RemoveAll(tempDirName)
}
return cdb, cleanUp, nil
}
type incubateTest struct {
nOutputs int
chanPoint *wire.OutPoint
commOutput *kidOutput
htlcOutputs []babyOutput
err error
}
// incubateTests holds the test vectors used to test the state transitions of
// outputs stored in the nursery store.
var incubateTests []incubateTest
// initIncubateTests instantiates the test vectors during package init, which
// properly captures the sign descriptors and public keys.
func initIncubateTests() {
incubateTests = []incubateTest{
{
nOutputs: 0,
chanPoint: &outPoints[3],
},
{
nOutputs: 1,
chanPoint: &outPoints[0],
commOutput: &kidOutputs[0],
},
{
nOutputs: 4,
chanPoint: &outPoints[0],
commOutput: &kidOutputs[0],
htlcOutputs: babyOutputs,
},
}
}
// TestNurseryStoreInit verifies basic properties of the nursery store before
// any modifying calls are made.
func TestNurseryStoreInit(t *testing.T) {
cdb, cleanUp, err := makeTestDB()
if err != nil {
t.Fatalf("unable to open channel db: %v", err)
}
defer cleanUp()
ns, err := newNurseryStore(&bitcoinGenesis, cdb)
if err != nil {
t.Fatalf("unable to open nursery store: %v", err)
}
assertNumChannels(t, ns, 0)
assertNumPreschools(t, ns, 0)
assertLastFinalizedHeight(t, ns, 0)
assertLastGraduatedHeight(t, ns, 0)
}
// TestNurseryStoreIncubate tests the primary state transitions taken by outputs
// in the nursery store. The test is designed to walk both commitment or htlc
// outputs through the nursery store, verifying the properties of the
// intermediate states.
func TestNurseryStoreIncubate(t *testing.T) {
cdb, cleanUp, err := makeTestDB()
if err != nil {
t.Fatalf("unable to open channel db: %v", err)
}
defer cleanUp()
ns, err := newNurseryStore(&bitcoinGenesis, cdb)
if err != nil {
t.Fatalf("unable to open nursery store: %v", err)
}
for i, test := range incubateTests {
// At the beginning of each test, we do not expect to the
// nursery store to be tracking any outputs for this channel
// point.
assertNumChanOutputs(t, ns, test.chanPoint, 0)
// Nursery store should be completely empty.
assertNumChannels(t, ns, 0)
assertNumPreschools(t, ns, 0)
// Begin incubating all of the outputs provided in this test
// vector.
err = ns.Incubate(test.commOutput, test.htlcOutputs)
if err != nil {
t.Fatalf("unable to incubate outputs"+
"on test #%d: %v", i, err)
}
// Now that the outputs have been inserted, the nursery store
// should see exactly that many outputs under this channel
// point.
// NOTE: This property should remain intact after every state
// change until the channel has been completely removed.
assertNumChanOutputs(t, ns, test.chanPoint, test.nOutputs)
// If there were no inputs to be incubated, just check that the
// no trace of the channel was left.
if test.nOutputs == 0 {
assertNumChannels(t, ns, 0)
continue
}
// The test vector has a non-zero number of outputs, we will
// expect to only see the one channel from this test case.
assertNumChannels(t, ns, 1)
// The channel should be shown as immature, since none of the
// outputs should be graduated directly after being inserted.
// It should also be impossible to remove the channel, if it is
// also immature.
// NOTE: These two tests properties should hold between every
// state change until all outputs have been fully graduated.
assertChannelMaturity(t, ns, test.chanPoint, false)
assertCanRemoveChannel(t, ns, test.chanPoint, false)
// Verify that the htlc outputs, if any, reside in the height
// index at their first stage CLTV's expiry height.
for _, htlcOutput := range test.htlcOutputs {
assertCribAtExpiryHeight(t, ns, &htlcOutput)
}
// If the commitment output was not dust, we will move it from
// the preschool bucket to the kindergarten bucket.
if test.commOutput != nil {
// If the commitment output was not considered dust, we
// should see exactly one preschool output in the
// nursery store.
assertNumPreschools(t, ns, 1)
// Now, move the commitment output to the kindergarten
// bucket.
err = ns.PreschoolToKinder(test.commOutput)
if err != test.err {
t.Fatalf("unable to move commitment output from "+
"pscl to kndr: %v", err)
}
// The total number of outputs for this channel should
// not have changed, and the kindergarten output should
// reside at it's maturity height.
assertNumChanOutputs(t, ns, test.chanPoint, test.nOutputs)
assertKndrAtMaturityHeight(t, ns, test.commOutput)
// The total number of channels should not have changed.
assertNumChannels(t, ns, 1)
// Channel maturity and removal should reflect that the
// channel still has non-graduated outputs.
assertChannelMaturity(t, ns, test.chanPoint, false)
assertCanRemoveChannel(t, ns, test.chanPoint, false)
// Moving the preschool output should have no effect on
// the placement of crib outputs in the height index.
for _, htlcOutput := range test.htlcOutputs {
assertCribAtExpiryHeight(t, ns, &htlcOutput)
}
}
// At this point, we should see no more preschool outputs in the
// nursery store. Either it was moved to the kindergarten
// bucket, or never inserted.
assertNumPreschools(t, ns, 0)
// If the commitment output is not-dust, we will graduate the
// class at its maturity height.
if test.commOutput != nil {
// Compute the commitment output's maturity height, and
// move proceed to graduate that class.
maturityHeight := test.commOutput.ConfHeight() +
test.commOutput.BlocksToMaturity()
err = ns.GraduateKinder(maturityHeight)
if err != nil {
t.Fatalf("unable to graduate kindergarten class at "+
"height %d: %v", maturityHeight, err)
}
// The total number of outputs for this channel should
// not have changed, but the kindergarten output should
// have been removed from it's maturity height.
assertNumChanOutputs(t, ns, test.chanPoint, test.nOutputs)
assertKndrNotAtMaturityHeight(t, ns, test.commOutput)
// The total number of channels should not have changed.
assertNumChannels(t, ns, 1)
// Moving the preschool output should have no effect on
// the placement of crib outputs in the height index.
for _, htlcOutput := range test.htlcOutputs {
assertCribAtExpiryHeight(t, ns, &htlcOutput)
}
}
// If there are any htlc outputs to incubate, we will walk them
// through their two-stage incubation process.
if len(test.htlcOutputs) > 0 {
for i, htlcOutput := range test.htlcOutputs {
// Begin by moving each htlc output from the
// crib to kindergarten state.
err = ns.CribToKinder(&htlcOutput)
if err != nil {
t.Fatalf("unable to move htlc output from "+
"crib to kndr: %v", err)
}
// Number of outputs for this channel should
// remain unchanged.
assertNumChanOutputs(t, ns, test.chanPoint,
test.nOutputs)
// If the output hasn't moved to kndr, it should
// be at it's crib expiry height, otherwise is
// should have been removed.
for j := range test.htlcOutputs {
if j > i {
assertCribAtExpiryHeight(t, ns,
&test.htlcOutputs[j])
assertKndrNotAtMaturityHeight(t,
ns, &test.htlcOutputs[j].kidOutput)
} else {
assertCribNotAtExpiryHeight(t, ns,
&test.htlcOutputs[j])
assertKndrAtMaturityHeight(t,
ns, &test.htlcOutputs[j].kidOutput)
}
}
}
// Total number of channels in the nursery store should
// be the same, no outputs should be marked as
// preschool.
assertNumChannels(t, ns, 1)
assertNumPreschools(t, ns, 0)
// Channel should also not be mature, as it we should
// still have outputs in kindergarten.
assertChannelMaturity(t, ns, test.chanPoint, false)
assertCanRemoveChannel(t, ns, test.chanPoint, false)
// Now, graduate each htlc kindergarten output,
// asserting the invariant number of outputs being
// tracked in this channel
for _, htlcOutput := range test.htlcOutputs {
maturityHeight := htlcOutput.ConfHeight() +
htlcOutput.BlocksToMaturity()
err = ns.GraduateKinder(maturityHeight)
if err != nil {
t.Fatalf("unable to graduate htlc output "+
"from kndr to grad: %v", err)
}
assertNumChanOutputs(t, ns, test.chanPoint,
test.nOutputs)
}
}
// All outputs have been advanced through the nursery store, but
// no attempt has been made to clean up this channel. We expect
// to see the same channel remaining, and no kindergarten
// outputs.
assertNumChannels(t, ns, 1)
assertNumPreschools(t, ns, 0)
// Since all outputs have now been graduated, the nursery store
// should recognize that the channel is mature, and attempting
// to remove it should succeed.
assertChannelMaturity(t, ns, test.chanPoint, true)
assertCanRemoveChannel(t, ns, test.chanPoint, true)
// Now that the channel has been removed, the nursery store
// should be no channels in the nursery store, and no outputs
// being tracked for this channel point.
assertNumChannels(t, ns, 0)
assertNumChanOutputs(t, ns, test.chanPoint, 0)
// If we had a commitment output, ensure it was removed from the
// height index.
if test.commOutput != nil {
assertKndrNotAtMaturityHeight(t, ns, test.commOutput)
}
// Check that all htlc outputs are no longer stored in their
// crib or kindergarten height buckets.
for _, htlcOutput := range test.htlcOutputs {
assertCribNotAtExpiryHeight(t, ns, &htlcOutput)
assertKndrNotAtMaturityHeight(t, ns, &htlcOutput.kidOutput)
}
// Lastly, there should be no lingering preschool outputs.
assertNumPreschools(t, ns, 0)
}
}
// TestNurseryStoreFinalize tests that kindergarten sweep transactions are
// properly persistted, and that the last finalized height is being set
// accordingly.
func TestNurseryStoreFinalize(t *testing.T) {
cdb, cleanUp, err := makeTestDB()
if err != nil {
t.Fatalf("unable to open channel db: %v", err)
}
defer cleanUp()
ns, err := newNurseryStore(&bitcoinGenesis, cdb)
if err != nil {
t.Fatalf("unable to open nursery store: %v", err)
}
kid := &kidOutputs[3]
// Compute the maturity height at which to enter the commitment output.
maturityHeight := kid.ConfHeight() + kid.BlocksToMaturity()
// Since we haven't finalized before, we should see a last finalized
// height of 0.
assertLastFinalizedHeight(t, ns, 0)
// Begin incubating the commitment output, which will be placed in the
// preschool bucket.
err = ns.Incubate(kid, nil)
if err != nil {
t.Fatalf("unable to incubate commitment output: %v", err)
}
// Then move the commitment output to the kindergarten bucket, so that
// the output is registered in the height index.
err = ns.PreschoolToKinder(kid)
if err != nil {
t.Fatalf("unable to move pscl output to kndr: %v", err)
}
// We should still see a last finalized height of 0, since no classes
// have been graduated.
assertLastFinalizedHeight(t, ns, 0)
// Now, iteratively finalize all heights below the maturity height,
// ensuring that the last finalized height is properly persisted, and
// that the finalized transactions are all nil.
for i := 0; i < int(maturityHeight); i++ {
err = ns.FinalizeKinder(uint32(i), nil)
if err != nil {
t.Fatalf("unable to finalize kndr at height=%d: %v",
i, err)
}
assertLastFinalizedHeight(t, ns, uint32(i))
assertFinalizedTxn(t, ns, uint32(i), nil)
}
// As we have now finalized all heights below the maturity height, we
// should still see the commitment output in the kindergarten bucket at
// its maturity height.
assertKndrAtMaturityHeight(t, ns, kid)
// Now, finalize the kindergarten sweep transaction at the maturity
// height.
err = ns.FinalizeKinder(maturityHeight, timeoutTx)
if err != nil {
t.Fatalf("unable to finalize kndr at height=%d: %v",
maturityHeight, err)
}
// The nursery store should now see the maturity height finalized, and
// the finalized kindergarten sweep txn should be returned at this
// height.
assertLastFinalizedHeight(t, ns, maturityHeight)
assertFinalizedTxn(t, ns, maturityHeight, timeoutTx)
// Lastly, continue to finalize heights above the maturity height. Each
// should report having a nil finalized kindergarten sweep txn.
for i := maturityHeight + 1; i < maturityHeight+10; i++ {
err = ns.FinalizeKinder(uint32(i), nil)
if err != nil {
t.Fatalf("unable to finalize kndr at height=%d: %v",
i, err)
}
assertLastFinalizedHeight(t, ns, uint32(i))
assertFinalizedTxn(t, ns, uint32(i), nil)
}
}
// TestNurseryStoreGraduate verifies that the nursery store properly removes
// populated entries from the height index as it is purged, and that the last
// purged height is set appropriately.
func TestNurseryStoreGraduate(t *testing.T) {
cdb, cleanUp, err := makeTestDB()
if err != nil {
t.Fatalf("unable to open channel db: %v", err)
}
defer cleanUp()
ns, err := newNurseryStore(&bitcoinGenesis, cdb)
if err != nil {
t.Fatalf("unable to open nursery store: %v", err)
}
kid := &kidOutputs[3]
// Compute the height at which this output will be inserted in the
// height index.
maturityHeight := kid.ConfHeight() + kid.BlocksToMaturity()
// Since we have never purged, the last purged height should be 0.
assertLastGraduatedHeight(t, ns, 0)
// First, add a commitment output to the nursery store, which is
// initially inserted in the preschool bucket.
err = ns.Incubate(kid, nil)
if err != nil {
t.Fatalf("unable to incubate commitment output: %v", err)
}
// Then, move the commitment output to the kindergarten bucket, such
// that it resides in the height index at it's maturity height.
err = ns.PreschoolToKinder(kid)
if err != nil {
t.Fatalf("unable to move pscl output to kndr: %v", err)
}
// Now, iteratively purge all height below the target maturity height,
// checking that each class is now empty, and that the last purged
// height is set correctly.
for i := 0; i < int(maturityHeight); i++ {
err = ns.GraduateHeight(uint32(i))
if err != nil {
t.Fatalf("unable to purge height=%d: %v", i, err)
}
assertLastGraduatedHeight(t, ns, uint32(i))
assertHeightIsPurged(t, ns, uint32(i))
}
// Check that the commitment output currently exists at it's maturity
// height.
assertKndrAtMaturityHeight(t, ns, kid)
// Finalize the kindergarten transaction, ensuring that it is a non-nil
// value.
err = ns.FinalizeKinder(maturityHeight, timeoutTx)
if err != nil {
t.Fatalf("unable to finalize kndr at height=%d: %v",
maturityHeight, err)
}
// Verify that the maturity height has now been finalized.
assertLastFinalizedHeight(t, ns, maturityHeight)
assertFinalizedTxn(t, ns, maturityHeight, timeoutTx)
// Finally, purge the non-empty maturity height, and check that returned
// class is empty.
err = ns.GraduateHeight(maturityHeight)
if err != nil {
t.Fatalf("unable to set graduated height=%d: %v", maturityHeight,
err)
}
err = ns.GraduateKinder(maturityHeight)
if err != nil {
t.Fatalf("unable to graduate kindergarten outputs at height=%d: "+
"%v", maturityHeight, err)
}
assertHeightIsPurged(t, ns, maturityHeight)
}
// assertNumChanOutputs checks that the channel bucket has the expected number
// of outputs.
func assertNumChanOutputs(t *testing.T, ns NurseryStore,
chanPoint *wire.OutPoint, expectedNum int) {
var count int
err := ns.ForChanOutputs(chanPoint, func([]byte, []byte) error {
count++
return nil
})
if count == 0 && err == ErrContractNotFound {
return
} else if err != nil {
t.Fatalf("unable to count num outputs for channel %v: %v",
chanPoint, err)
}
if count != expectedNum {
t.Fatalf("nursery store should have %d outputs, found %d",
expectedNum, count)
}
}
// assertLastFinalizedHeight checks that the nursery stores last finalized
// height matches the expected height.
func assertLastFinalizedHeight(t *testing.T, ns NurseryStore,
expected uint32) {
lfh, err := ns.LastFinalizedHeight()
if err != nil {
t.Fatalf("unable to get last finalized height: %v", err)
}
if lfh != expected {
t.Fatalf("expected last finalized height to be %d, got %d",
expected, lfh)
}
}
// assertLastGraduatedHeight checks that the nursery stores last purged height
// matches the expected height.
func assertLastGraduatedHeight(t *testing.T, ns NurseryStore, expected uint32) {
lgh, err := ns.LastGraduatedHeight()
if err != nil {
t.Fatalf("unable to get last graduated height: %v", err)
}
if lgh != expected {
t.Fatalf("expected last graduated height to be %d, got %d",
expected, lgh)
}
}
// assertNumPreschools loads all preschool outputs and verifies their count
// matches the expected number.
func assertNumPreschools(t *testing.T, ns NurseryStore, expected int) {
psclOutputs, err := ns.FetchPreschools()
if err != nil {
t.Fatalf("unable to retrieve preschool outputs: %v", err)
}
if len(psclOutputs) != expected {
t.Fatalf("expected number of pscl outputs to be %d, got %v",
expected, len(psclOutputs))
}
}
// assertNumChannels checks that the nursery has a given number of active
// channels.
func assertNumChannels(t *testing.T, ns NurseryStore, expected int) {
channels, err := ns.ListChannels()
if err != nil {
t.Fatalf("unable to fetch channels from nursery store: %v",
err)
}
if len(channels) != expected {
t.Fatalf("expected number of active channels to be %d, got %d",
expected, len(channels))
}
}
// assertHeightIsPurged checks that the finalized transaction, kindergarten, and
// htlc outputs at a particular height are all nil.
func assertHeightIsPurged(t *testing.T, ns NurseryStore,
height uint32) {
finalTx, kndrOutputs, cribOutputs, err := ns.FetchClass(height)
if err != nil {
t.Fatalf("unable to retrieve class at height=%d: %v",
height, err)
}
if finalTx != nil {
t.Fatalf("height=%d not purged, final txn should be nil", height)
}
if kndrOutputs != nil {
t.Fatalf("height=%d not purged, kndr outputs should be nil", height)
}
if cribOutputs != nil {
t.Fatalf("height=%d not purged, crib outputs should be nil", height)
}
}
// assertCribAtExpiryHeight loads the class at the given height, and verifies
// that the given htlc output is one of the crib outputs.
func assertCribAtExpiryHeight(t *testing.T, ns NurseryStore,
htlcOutput *babyOutput) {
expiryHeight := htlcOutput.expiry
_, _, cribOutputs, err := ns.FetchClass(expiryHeight)
if err != nil {
t.Fatalf("unable to retrieve class at height=%d: %v",
expiryHeight, err)
}
for _, crib := range cribOutputs {
if reflect.DeepEqual(&crib, htlcOutput) {
return
}
}
t.Fatalf("could not find crib output %v at height %d",
htlcOutput.OutPoint(), expiryHeight)
}
// assertCribNotAtExpiryHeight loads the class at the given height, and verifies
// that the given htlc output is not one of the crib outputs.
func assertCribNotAtExpiryHeight(t *testing.T, ns NurseryStore,
htlcOutput *babyOutput) {
expiryHeight := htlcOutput.expiry
_, _, cribOutputs, err := ns.FetchClass(expiryHeight)
if err != nil {
t.Fatalf("unable to retrieve class at height %d: %v",
expiryHeight, err)
}
for _, crib := range cribOutputs {
if reflect.DeepEqual(&crib, htlcOutput) {
t.Fatalf("found find crib output %v at height %d",
htlcOutput.OutPoint(), expiryHeight)
}
}
}
// assertFinalizedTxn loads the class at the given height and compares the
// returned finalized txn to that in the class. It is safe to presented a nil
// expected transaction.
func assertFinalizedTxn(t *testing.T, ns NurseryStore, height uint32,
exFinalTx *wire.MsgTx) {
finalTx, _, _, err := ns.FetchClass(height)
if err != nil {
t.Fatalf("unable to fetch class at height=%d: %v", height,
err)
}
if !reflect.DeepEqual(finalTx, exFinalTx) {
t.Fatalf("expected finalized txn at height=%d "+
"to be %v, got %v", height, finalTx.TxHash(),
exFinalTx.TxHash())
}
}
// assertKndrAtMaturityHeight loads the class at the provided height and
// verifies that the provided kid output is one of the kindergarten outputs
// returned.
func assertKndrAtMaturityHeight(t *testing.T, ns NurseryStore,
kndrOutput *kidOutput) {
maturityHeight := kndrOutput.ConfHeight() +
kndrOutput.BlocksToMaturity()
_, kndrOutputs, _, err := ns.FetchClass(maturityHeight)
if err != nil {
t.Fatalf("unable to retrieve class at height %d: %v",
maturityHeight, err)
}
for _, kndr := range kndrOutputs {
if reflect.DeepEqual(&kndr, kndrOutput) {
return
}
}
t.Fatalf("could not find kndr output %v at height %d",
kndrOutput.OutPoint(), maturityHeight)
}
// assertKndrNotAtMaturityHeight loads the class at the provided height and
// verifies that the provided kid output is not one of the kindergarten outputs
// returned.
func assertKndrNotAtMaturityHeight(t *testing.T, ns NurseryStore,
kndrOutput *kidOutput) {
maturityHeight := kndrOutput.ConfHeight() +
kndrOutput.BlocksToMaturity()
_, kndrOutputs, _, err := ns.FetchClass(maturityHeight)
if err != nil {
t.Fatalf("unable to retrieve class at height %d: %v",
maturityHeight, err)
}
for _, kndr := range kndrOutputs {
if reflect.DeepEqual(&kndr, kndrOutput) {
t.Fatalf("found find kndr output %v at height %d",
kndrOutput.OutPoint(), maturityHeight)
}
}
}
// assertChannelMaturity queries the nursery store for the maturity of the given
// channel, failing if the result does not match the expectedMaturity.
func assertChannelMaturity(t *testing.T, ns NurseryStore,
chanPoint *wire.OutPoint, expectedMaturity bool) {
isMature, err := ns.IsMatureChannel(chanPoint)
if err != nil {
t.Fatalf("unable to fetch channel maturity: %v", err)
}
if isMature != expectedMaturity {
t.Fatalf("expected channel maturity: %v, actual: %v",
expectedMaturity, isMature)
}
}
// assertCanRemoveChannel tries to remove a channel from the nursery store,
// failing if the result does match expected canRemove.
func assertCanRemoveChannel(t *testing.T, ns NurseryStore,
chanPoint *wire.OutPoint, canRemove bool) {
err := ns.RemoveChannel(chanPoint)
if canRemove && err != nil {
t.Fatalf("expected nil when removing active channel, got: %v",
err)
} else if !canRemove && err != ErrImmatureChannel {
t.Fatalf("expected ErrImmatureChannel when removing "+
"active channel: %v", err)
}
}

@ -942,7 +942,9 @@ func (r *rpcServer) forceCloseChan(channel *lnwallet.LightningChannel) (*chainha
// Send the closed channel summary over to the utxoNursery in order to
// have its outputs swept back into the wallet once they're mature.
r.server.utxoNursery.IncubateOutputs(closeSummary)
if err := r.server.utxoNursery.IncubateOutputs(closeSummary); err != nil {
return nil, nil, err
}
return &txid, closeSummary, nil
}
@ -1241,13 +1243,37 @@ func (r *rpcServer) PendingChannels(ctx context.Context,
// we can ultimately sweep the funds into the wallet.
if nurseryInfo != nil {
forceClose.LimboBalance = int64(nurseryInfo.limboBalance)
forceClose.RecoveredBalance = int64(nurseryInfo.recoveredBalance)
forceClose.MaturityHeight = nurseryInfo.maturityHeight
// If the transaction has been confirmed, then
// we can compute how many blocks it has left.
if forceClose.MaturityHeight != 0 {
forceClose.BlocksTilMaturity = (forceClose.MaturityHeight -
uint32(currentHeight))
forceClose.BlocksTilMaturity =
int32(forceClose.MaturityHeight) -
currentHeight
}
for _, htlcReport := range nurseryInfo.htlcs {
// TODO(conner) set incoming flag
// appropriately after handling incoming
// incubation
htlc := &lnrpc.PendingHTLC{
Incoming: false,
Amount: int64(htlcReport.amount),
Outpoint: htlcReport.outpoint.String(),
MaturityHeight: htlcReport.maturityHeight,
Stage: htlcReport.stage,
}
if htlc.MaturityHeight != 0 {
htlc.BlocksTilMaturity =
int32(htlc.MaturityHeight) -
currentHeight
}
forceClose.PendingHtlcs = append(forceClose.PendingHtlcs,
htlc)
}
resp.TotalLimboBalance += int64(nurseryInfo.limboBalance)

@ -136,8 +136,6 @@ func newServer(listenAddrs []string, chanDB *channeldb.DB, cc *chainControl,
invoices: newInvoiceRegistry(chanDB),
utxoNursery: newUtxoNursery(chanDB, cc.chainNotifier, cc.wallet),
identityPriv: privKey,
nodeSigner: newNodeSigner(privKey),
@ -306,6 +304,26 @@ func newServer(listenAddrs []string, chanDB *channeldb.DB, cc *chainControl,
return nil, err
}
utxnStore, err := newNurseryStore(&bitcoinGenesis, chanDB)
if err != nil {
srvrLog.Errorf("unable to create nursery store: %v", err)
return nil, err
}
s.utxoNursery = newUtxoNursery(&NurseryConfig{
ChainIO: cc.chainIO,
ConfDepth: 1,
DB: chanDB,
Estimator: cc.feeEstimator,
GenSweepScript: func() ([]byte, error) {
return newSweepPkScript(cc.wallet)
},
Notifier: cc.chainNotifier,
PublishTransaction: cc.wallet.PublishTransaction,
Signer: cc.wallet.Cfg.Signer,
Store: utxnStore,
})
// Construct a closure that wraps the htlcswitch's CloseLink method.
closeLink := func(chanPoint *wire.OutPoint,
closureType htlcswitch.ChannelCloseType) {
@ -315,17 +333,17 @@ func newServer(listenAddrs []string, chanDB *channeldb.DB, cc *chainControl,
}
s.breachArbiter = newBreachArbiter(&BreachConfig{
Signer: cc.wallet.Cfg.Signer,
DB: chanDB,
PublishTransaction: cc.wallet.PublishTransaction,
Notifier: cc.chainNotifier,
ChainIO: s.cc.chainIO,
Estimator: s.cc.feeEstimator,
CloseLink: closeLink,
Store: newRetributionStore(chanDB),
ChainIO: s.cc.chainIO,
CloseLink: closeLink,
DB: chanDB,
Estimator: s.cc.feeEstimator,
GenSweepScript: func() ([]byte, error) {
return newSweepPkScript(cc.wallet)
},
Notifier: cc.chainNotifier,
PublishTransaction: cc.wallet.PublishTransaction,
Signer: cc.wallet.Cfg.Signer,
Store: newRetributionStore(chanDB),
})
// Create the connection manager which will be responsible for

@ -3,71 +3,165 @@ package main
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"strings"
"sync"
"sync/atomic"
"github.com/boltdb/bolt"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/roasbeef/btcd/blockchain"
"github.com/roasbeef/btcd/txscript"
"github.com/roasbeef/btcd/wire"
"github.com/roasbeef/btcutil"
)
var (
// preschoolBucket stores outputs from commitment transactions that
// have been broadcast, but not yet confirmed. This set of outputs is
// persisted in case the system is shut down between the time when the
// commitment has been broadcast and the time the transaction has been
// confirmed on the blockchain.
// TODO(roasbeef): modify schema later to be:
// * chanPoint ->
// {outpoint1} -> info
// {outpoint2} -> info
preschoolBucket = []byte("psc")
// SUMMARY OF OUTPUT STATES
//
// - CRIB
// - SerializedType: babyOutput
// - OriginalOutputType: HTLC
// - Awaiting: First-stage HTLC CLTV expiry
// - HeightIndexEntry: Absolute block height of CLTV expiry.
// - NextState: KNDR
// - PSCL
// - SerializedType: kidOutput
// - OriginalOutputType: Commitment
// - Awaiting: Confirmation of commitment txn
// - HeightIndexEntry: None.
// - NextState: KNDR
// - KNDR
// - SerializedType: kidOutput
// - OriginalOutputType: Commitment or HTLC
// - Awaiting: Commitment CSV expiry or second-stage HTLC CSV expiry.
// - HeightIndexEntry: Input confirmation height + relative CSV delay
// - NextState: GRAD
// - GRAD:
// - SerializedType: kidOutput
// - OriginalOutputType: Commitment or HTLC
// - Awaiting: All other outputs in channel to become GRAD.
// - NextState: Mark channel fully closed in channeldb and remove.
//
// DESCRIPTION OF OUTPUT STATES
//
// - CRIB (babyOutput) outputs are two-stage htlc outputs that are initially
// locked using a CLTV delay, followed by a CSV delay. The first stage of a
// crib output requires broadcasting a presigned htlc timeout txn generated
// by the wallet after an absolute expiry height. Since the timeout txns are
// predetermined, they cannot be batched after-the-fact, meaning that all
// CRIB outputs are broadcast and confirmed independently. After the first
// stage is complete, a CRIB output is moved to the KNDR state, which will
// finishing sweeping the second-layer CSV delay.
//
// - PSCL (kidOutput) outputs are commitment outputs locked under a CSV delay.
// These outputs are stored temporarily in this state until the commitment
// transaction confirms, as this solidifies an absolute height that the
// relative time lock will expire. Once this maturity height is determined,
// the PSCL output is moved into KNDR.
//
// - KNDR (kidOutput) outputs are CSV delayed outputs for which the maturity
// height has been fully determined. This results from having received
// confirmation of the UTXO we are trying to spend, contained in either the
// commitment txn or htlc timeout txn. Once the maturity height is reached,
// the utxo nursery will sweep all KNDR outputs scheduled for that height
// using a single txn.
//
// NOTE: Due to the fact that KNDR outputs can be dynamically aggregated and
// swept, we make precautions to finalize the KNDR outputs at a particular
// height on our first attempt to sweep it. Finalizing involves signing the
// sweep transaction and persisting it in the nursery store, and recording
// the last finalized height. Any attempts to replay an already finalized
// height will result in broadcasting the already finalized txn, ensuring the
// nursery does not broadcast different txids for the same batch of KNDR
// outputs. The reason txids may change is due to the probabilistic nature of
// generating the pkscript in the sweep txn's output, even if the set of
// inputs remains static across attempts.
//
// - GRAD (kidOutput) outputs are KNDR outputs that have successfully been
// swept into the user's wallet. A channel is considered mature once all of
// its outputs, including two-stage htlcs, have entered the GRAD state,
// indicating that it safe to mark the channel as fully closed.
//
//
// OUTPUT STATE TRANSITIONS IN UTXO NURSERY
//
// ┌────────────────┐ ┌──────────────┐
// │ Commit Outputs │ │ HTLC Outputs │
// └────────────────┘ └──────────────┘
// │ │
// │ │
// │ │ UTXO NURSERY
// ┌───────────┼────────────────┬───────────┼───────────────────────────────┐
// │ │ │ │
// │ │ │ │ │
// │ │ │ CLTV-Delayed │
// │ │ │ V babyOutputs │
// │ │ ┌──────┐ │
// │ │ │ │ CRIB │ │
// │ │ └──────┘ │
// │ │ │ │ │
// │ │ │ │
// │ │ │ | │
// │ │ V Wait CLTV │
// │ │ │ [ ] + │
// │ │ | Publish Txn │
// │ │ │ │ │
// │ │ │ │
// │ │ │ V ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─┐ │
// │ │ ( ) waitForTimeoutConf │
// │ │ │ | └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─┘ │
// │ │ │ │
// │ │ │ │ │
// │ │ │ │
// │ V │ │ │
// │ ┌──────┐ │ │
// │ │ PSCL │ └ ── ── ─┼ ── ── ── ── ── ── ── ─┤
// │ └──────┘ │ │
// │ │ │ │
// │ │ │ │
// │ V ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─┐ │ CSV-Delayed │
// │ ( ) waitForCommitConf │ kidOutputs │
// │ | └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─┘ │ │
// │ │ │ │
// │ │ │ │
// │ │ V │
// │ │ ┌──────┐ │
// │ └─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─▶│ KNDR │ │
// │ └──────┘ │
// │ │ │
// │ │ │
// │ | │
// │ V Wait CSV │
// │ [ ] + │
// │ | Publish Txn │
// │ │ │
// │ │ │
// │ V ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │
// │ ( ) waitForSweepConf │
// │ | └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │
// │ │ │
// │ │ │
// │ V │
// │ ┌──────┐ │
// │ │ GRAD │ │
// │ └──────┘ │
// │ │ │
// │ │ │
// │ │ │
// └────────────────────────────────────────┼───────────────────────────────┘
// │
// │
// │
// │
// V
// ┌────────────────┐
// │ Wallet Outputs │
// └────────────────┘
// preschoolIndex is an index that maps original chanPoint that created
// the channel to all the active time-locked outpoints for that
// channel.
preschoolIndex = []byte("preschool-index")
// kindergartenBucket stores outputs from commitment transactions that
// have received an initial confirmation, but which aren't yet
// spendable because they require additional confirmations enforced by
// CheckSequenceVerify. Once required additional confirmations have
// been reported, a sweep transaction will be created to move the funds
// out of these outputs. After a further six confirmations have been
// reported, the outputs will be deleted from this bucket. The purpose
// of this additional wait time is to ensure that a block
// reorganization doesn't result in the sweep transaction getting
// re-organized out of the chain.
// TODO(roasbeef): modify schema later to be:
// * height ->
// {chanPoint} -> info
kindergartenBucket = []byte("kdg")
// contractIndex is an index that maps a contract's channel point to
// the current information pertaining to the maturity of outputs within
// that contract. Items are inserted into this index once they've been
// accepted to pre-school and deleted after the output has been fully
// swept.
//
// mapping: chanPoint -> graduationHeight || byte-offset-in-kindergartenBucket
contractIndex = []byte("contract-index")
// lastGraduatedHeightKey is used to persist the last block height that
// has been checked for graduating outputs. When the nursery is
// restarted, lastGraduatedHeightKey is used to determine the point
// from which it's necessary to catch up.
lastGraduatedHeightKey = []byte("lgh")
byteOrder = binary.BigEndian
)
var byteOrder = binary.BigEndian
var (
// ErrContractNotFound is returned when the nursery is unable to
@ -75,6 +169,47 @@ var (
ErrContractNotFound = fmt.Errorf("unable to locate contract")
)
// NurseryConfig abstracts the required subsystems used by the utxo nursery. An
// instance of NurseryConfig is passed to newUtxoNursery during instantiationn.
type NurseryConfig struct {
// ChainIO is used by the utxo nursery to determine the current block
// height, which drives the incubation of the nursery's outputs.
ChainIO lnwallet.BlockChainIO
// ConfDepth is the number of blocks the nursery store waits before
// determining outputs in the chain as confirmed.
ConfDepth uint32
// DB provides access to a user's channels, such that they can be marked
// fully closed after incubation has concluded.
DB *channeldb.DB
// Estimator is used when crafting sweep transactions to estimate the
// necessary fee relative to the expected size of the sweep transaction.
Estimator lnwallet.FeeEstimator
// GenSweepScript generates a P2WKH script belonging to the wallet where
// funds can be swept.
GenSweepScript func() ([]byte, error)
// Notifier provides the utxo nursery the ability to subscribe to
// transaction confirmation events, which advance outputs through their
// persistence state transitions.
Notifier chainntnfs.ChainNotifier
// PublishTransaction facilitates the process of broadcasting a signed
// transaction to the appropriate network.
PublishTransaction func(*wire.MsgTx) error
// Signer is used by the utxo nursery to generate valid witnesses at the
// time the incubated outputs need to be spent.
Signer lnwallet.Signer
// Store provides access to and modification of the persistent state
// maintained about the utxo nursery's incubating outputs.
Store NurseryStore
}
// utxoNursery is a system dedicated to incubating time-locked outputs created
// by the broadcast of a commitment transaction either by us, or the remote
// peer. The nursery accepts outputs and "incubates" them until they've reached
@ -84,32 +219,24 @@ var (
// the source wallet, returning the outputs so they can be used within future
// channels, or regular Bitcoin transactions.
type utxoNursery struct {
sync.RWMutex
notifier chainntnfs.ChainNotifier
wallet *lnwallet.LightningWallet
db *channeldb.DB
requests chan *incubationRequest
started uint32
stopped uint32
quit chan struct{}
wg sync.WaitGroup
cfg *NurseryConfig
mu sync.Mutex
bestHeight uint32
quit chan struct{}
wg sync.WaitGroup
}
// newUtxoNursery creates a new instance of the utxoNursery from a
// ChainNotifier and LightningWallet instance.
func newUtxoNursery(db *channeldb.DB, notifier chainntnfs.ChainNotifier,
wallet *lnwallet.LightningWallet) *utxoNursery {
func newUtxoNursery(cfg *NurseryConfig) *utxoNursery {
return &utxoNursery{
notifier: notifier,
wallet: wallet,
requests: make(chan *incubationRequest),
db: db,
quit: make(chan struct{}),
cfg: cfg,
quit: make(chan struct{}),
}
}
@ -122,115 +249,72 @@ func (u *utxoNursery) Start() error {
utxnLog.Tracef("Starting UTXO nursery")
// Query the database for the most recently processed block. We'll use
// this to strict the search space when asking for confirmation
// notifications, and also to scan the chain to graduate now mature
// outputs.
var lastGraduatedHeight uint32
err := u.db.View(func(tx *bolt.Tx) error {
kgtnBucket := tx.Bucket(kindergartenBucket)
if kgtnBucket == nil {
return nil
}
heightBytes := kgtnBucket.Get(lastGraduatedHeightKey)
if heightBytes == nil {
return nil
}
lastGraduatedHeight = byteOrder.Uint32(heightBytes)
return nil
})
if err != nil {
return err
}
if err := u.reloadPreschool(lastGraduatedHeight); err != nil {
return err
}
// 1. Start watching for new blocks, as this will drive the nursery
// store's state machine.
// Register with the notifier to receive notifications for each newly
// connected block. We register during startup to ensure that no blocks
// are missed while we are handling blocks that were missed during the
// time the UTXO nursery was unavailable.
newBlockChan, err := u.notifier.RegisterBlockEpochNtfn()
if err != nil {
return err
}
if err := u.catchUpKindergarten(lastGraduatedHeight); err != nil {
return err
}
u.wg.Add(1)
go u.incubator(newBlockChan, lastGraduatedHeight)
return nil
}
// reloadPreschool re-initializes the chain notifier with all of the outputs
// that had been saved to the "preschool" database bucket prior to shutdown.
func (u *utxoNursery) reloadPreschool(heightHint uint32) error {
return u.db.View(func(tx *bolt.Tx) error {
psclBucket := tx.Bucket(preschoolBucket)
if psclBucket == nil {
return nil
}
return psclBucket.ForEach(func(outputBytes, kidBytes []byte) error {
var psclOutput kidOutput
err := psclOutput.Decode(bytes.NewBuffer(kidBytes))
if err != nil {
return err
}
sourceTxid := psclOutput.OutPoint().Hash
confChan, err := u.notifier.RegisterConfirmationsNtfn(
&sourceTxid, 1, heightHint,
)
if err != nil {
return err
}
utxnLog.Infof("Preschool outpoint %v re-registered for confirmation "+
"notification.", psclOutput.OutPoint())
go psclOutput.waitForPromotion(u.db, confChan)
return nil
})
})
}
// catchUpKindergarten handles the graduation of kindergarten outputs from
// blocks that were missed while the UTXO Nursery was down or offline.
// graduateMissedBlocks is called during the startup of the UTXO Nursery.
func (u *utxoNursery) catchUpKindergarten(lastGraduatedHeight uint32) error {
// Get the most recently mined block
_, bestHeight, err := u.wallet.Cfg.ChainIO.GetBestBlock()
// connected block. We register immediately on startup to ensure that no
// blocks are missed while we are handling blocks that were missed
// during the time the UTXO nursery was unavailable.
newBlockChan, err := u.cfg.Notifier.RegisterBlockEpochNtfn()
if err != nil {
return err
}
// If we haven't yet seen any registered force closes, or we're already
// caught up with the current best chain, then we can exit early.
if lastGraduatedHeight == 0 || uint32(bestHeight) == lastGraduatedHeight {
return nil
// 2. Flush all fully-graduated channels from the pipeline.
// Load any pending close channels, which represents the super set of
// all channels that may still be incubating.
pendingCloseChans, err := u.cfg.DB.FetchClosedChannels(true)
if err != nil {
newBlockChan.Cancel()
return err
}
utxnLog.Infof("Processing outputs from missed blocks. Starting with "+
"blockHeight: %v, to current blockHeight: %v", lastGraduatedHeight,
bestHeight)
// Loop through and check for graduating outputs at each of the missed
// block heights.
for graduationHeight := lastGraduatedHeight + 1; graduationHeight <= uint32(bestHeight); graduationHeight++ {
utxnLog.Debugf("Attempting to graduate outputs at height=%v",
graduationHeight)
if err := u.graduateKindergarten(graduationHeight); err != nil {
// Ensure that all mature channels have been marked as fully closed in
// the channeldb.
for _, pendingClose := range pendingCloseChans {
err := u.closeAndRemoveIfMature(&pendingClose.ChanPoint)
if err != nil {
newBlockChan.Cancel()
return err
}
}
utxnLog.Infof("UTXO Nursery is now fully synced")
// TODO(conner): check if any fully closed channels can be removed from
// utxn.
// Query the nursery store for the lowest block height we could be
// incubating, which is taken to be the last height for which the
// database was purged.
lastGraduatedHeight, err := u.cfg.Store.LastGraduatedHeight()
if err != nil {
newBlockChan.Cancel()
return err
}
// 2. Restart spend ntfns for any preschool outputs, which are waiting
// for the force closed commitment txn to confirm.
//
// NOTE: The next two steps *may* spawn go routines, thus from this
// point forward, we must close the nursery's quit channel if we detect
// any failures during startup to ensure they terminate.
if err := u.reloadPreschool(lastGraduatedHeight); err != nil {
newBlockChan.Cancel()
close(u.quit)
return err
}
// 3. Replay all crib and kindergarten outputs from last pruned to
// current best height.
if err := u.reloadClasses(lastGraduatedHeight); err != nil {
newBlockChan.Cancel()
close(u.quit)
return err
}
u.wg.Add(1)
go u.incubator(newBlockChan)
return nil
}
@ -250,21 +334,23 @@ func (u *utxoNursery) Stop() error {
return nil
}
// incubationRequest is a request to the utxoNursery to incubate a set of
// outputs until their mature, finally sweeping them into the wallet once
// available.
type incubationRequest struct {
outputs []*kidOutput
}
// incubateOutputs sends a request to utxoNursery to incubate the outputs
// IncubateOutputs sends a request to utxoNursery to incubate the outputs
// defined within the summary of a closed channel. Individually, as all outputs
// reach maturity they'll be swept back into the wallet.
func (u *utxoNursery) IncubateOutputs(closeSummary *lnwallet.ForceCloseSummary) {
var incReq incubationRequest
// reach maturity, they'll be swept back into the wallet.
func (u *utxoNursery) IncubateOutputs(
closeSummary *lnwallet.ForceCloseSummary) error {
// It could be that our to-self output was below the dust limit. In
// that case the SignDescriptor would be nil and we would not have that
nHtlcs := len(closeSummary.HtlcResolutions)
var (
commOutput *kidOutput
htlcOutputs = make([]babyOutput, 0, nHtlcs)
)
// 1. Build all the spendable outputs that we will try to incubate.
// It could be that our to-self output was below the dust limit. In that
// case the SignDescriptor would be nil and we would not have that
// output to incubate.
if closeSummary.SelfOutputSignDesc != nil {
selfOutput := makeKidOutput(
@ -275,75 +361,319 @@ func (u *utxoNursery) IncubateOutputs(closeSummary *lnwallet.ForceCloseSummary)
closeSummary.SelfOutputSignDesc,
)
incReq.outputs = append(incReq.outputs, &selfOutput)
// We'll skip any zero value'd outputs as this indicates we
// don't have a settled balance within the commitment
// transaction.
if selfOutput.Amount() > 0 {
commOutput = &selfOutput
}
}
// If there are no outputs to incubate, there is nothing to send to the
// request channel.
if len(incReq.outputs) != 0 {
u.requests <- &incReq
for i := range closeSummary.HtlcResolutions {
htlcRes := closeSummary.HtlcResolutions[i]
htlcOutpoint := &wire.OutPoint{
Hash: htlcRes.SignedTimeoutTx.TxHash(),
Index: 0,
}
htlcOutput := makeBabyOutput(
htlcOutpoint,
&closeSummary.ChanPoint,
closeSummary.SelfOutputMaturity,
lnwallet.HtlcOfferedTimeout,
&htlcRes,
)
if htlcOutput.Amount() > 0 {
htlcOutputs = append(htlcOutputs, htlcOutput)
}
}
// If there are no outputs to incubate for this channel, we simply mark
// the channel as fully closed.
if commOutput == nil && len(htlcOutputs) == 0 {
utxnLog.Infof("Channel(%s) has no outputs to incubate, "+
"marking fully closed.", &closeSummary.ChanPoint)
return u.cfg.DB.MarkChanFullyClosed(&closeSummary.ChanPoint)
}
utxnLog.Infof("Incubating Channel(%s) has-commit=%v, num-htlcs=%d",
&closeSummary.ChanPoint, commOutput != nil, len(htlcOutputs))
u.mu.Lock()
defer u.mu.Unlock()
// 2. Persist the outputs we intended to sweep in the nursery store
if err := u.cfg.Store.Incubate(commOutput, htlcOutputs); err != nil {
utxnLog.Errorf("unable to begin incubation of Channel(%s): %v",
&closeSummary.ChanPoint, err)
return err
}
// 3. If we are incubating a preschool output, register for a
// confirmation notification that will transition it to the kindergarten
// bucket.
if commOutput != nil {
return u.registerCommitConf(commOutput, u.bestHeight)
}
return nil
}
// incubator is tasked with watching over all outputs from channel closes as
// they transition from being broadcast (at which point they move into the
// "preschool state"), then confirmed and waiting for the necessary number of
// blocks to be confirmed (as specified as kidOutput.blocksToMaturity and
// enforced by CheckSequenceVerify). When the necessary block height has been
// reached, the output has "matured" and the waitForGraduation function will
// generate a sweep transaction to move funds from the commitment transaction
// into the user's wallet.
func (u *utxoNursery) incubator(newBlockChan *chainntnfs.BlockEpochEvent,
startingHeight uint32) {
// NurseryReport attempts to return a nursery report stored for the target
// outpoint. A nursery report details the maturity/sweeping progress for a
// contract that was previously force closed. If a report entry for the target
// chanPoint is unable to be constructed, then an error will be returned.
func (u *utxoNursery) NurseryReport(
chanPoint *wire.OutPoint) (*contractMaturityReport, error) {
u.mu.Lock()
defer u.mu.Unlock()
utxnLog.Infof("NurseryReport: building nursery report for channel %v",
chanPoint)
report := &contractMaturityReport{
chanPoint: *chanPoint,
}
if err := u.cfg.Store.ForChanOutputs(chanPoint, func(k, v []byte) error {
switch {
case bytes.HasPrefix(k, cribPrefix):
// Cribs outputs are the only kind currently stored as
// baby outputs.
var baby babyOutput
err := baby.Decode(bytes.NewReader(v))
if err != nil {
return err
}
// Each crib output represents a stage one htlc, and
// will contribute towards the limbo balance.
report.AddLimboStage1Htlc(&baby)
case bytes.HasPrefix(k, psclPrefix),
bytes.HasPrefix(k, kndrPrefix),
bytes.HasPrefix(k, gradPrefix):
// All others states can be deserialized as kid outputs.
var kid kidOutput
err := kid.Decode(bytes.NewReader(v))
if err != nil {
return err
}
// Now, use the state prefixes to determine how the this
// output should be represented in the nursery report.
// An output's funds are always in limbo until reaching
// the graduate state.
switch {
case bytes.HasPrefix(k, psclPrefix):
// Preschool outputs are awaiting the
// confirmation of the commitment transaction.
report.AddLimboCommitment(&kid)
case bytes.HasPrefix(k, kndrPrefix):
// Kindergarten outputs may originate from
// either the commitment transaction or an htlc.
// We can distinguish them via their witness
// types.
switch kid.WitnessType() {
case lnwallet.CommitmentTimeLock:
// The commitment transaction has been
// confirmed, and we are waiting the CSV
// delay to expire.
report.AddLimboCommitment(&kid)
case lnwallet.HtlcOfferedTimeout:
// The htlc timeout transaction has
// confirmed, and the CSV delay has
// begun ticking.
report.AddLimboStage2Htlc(&kid)
}
case bytes.HasPrefix(k, gradPrefix):
// Graduate outputs are those whose funds have
// been swept back into the wallet. Each output
// will contribute towards the recovered
// balance.
switch kid.WitnessType() {
case lnwallet.CommitmentTimeLock:
// The commitment output was
// successfully swept back into a
// regular p2wkh output.
report.AddRecoveredCommitment(&kid)
case lnwallet.HtlcOfferedTimeout:
// This htlc output successfully resides
// in a p2wkh output belonging to the
// user.
report.AddRecoveredHtlc(&kid)
}
}
default:
}
return nil
}); err != nil {
return nil, err
}
return report, nil
}
// reloadPreschool re-initializes the chain notifier with all of the outputs
// that had been saved to the "preschool" database bucket prior to shutdown.
func (u *utxoNursery) reloadPreschool(heightHint uint32) error {
psclOutputs, err := u.cfg.Store.FetchPreschools()
if err != nil {
return err
}
for i := range psclOutputs {
err := u.registerCommitConf(&psclOutputs[i], heightHint)
if err != nil {
return err
}
}
return nil
}
// reloadClasses reinitializes any height-dependent state transitions for which
// the utxonursery has not recevied confirmation, and replays the graduation of
// all kindergarten and crib outputs for heights that have not been finalized.
// This allows the nursery to reinitialize all state to continue sweeping
// outputs, even in the event that we missed blocks while offline. reloadClasses
// is called during the startup of the UTXO Nursery.
func (u *utxoNursery) reloadClasses(lastGradHeight uint32) error {
// Begin by loading all of the still-active heights up to and including
// the last height we successfully graduated.
activeHeights, err := u.cfg.Store.HeightsBelowOrEqual(lastGradHeight)
if err != nil {
return err
}
if len(activeHeights) > 0 {
utxnLog.Infof("Re-registering confirmations for %d already "+
"graduated heights below height=%d", len(activeHeights),
lastGradHeight)
}
// Attempt to re-register notifications for any outputs still at these
// heights.
for _, classHeight := range activeHeights {
utxnLog.Debugf("Attempting to regraduate outputs at height=%v",
classHeight)
if err = u.regraduateClass(classHeight); err != nil {
utxnLog.Errorf("Failed to regraduate outputs at "+
"height=%v: %v", classHeight, err)
return err
}
}
// Get the most recently mined block.
_, bestHeight, err := u.cfg.ChainIO.GetBestBlock()
if err != nil {
return err
}
// If we haven't yet seen any registered force closes, or we're already
// caught up with the current best chain, then we can exit early.
if lastGradHeight == 0 || uint32(bestHeight) == lastGradHeight {
return nil
}
utxnLog.Infof("Processing outputs from missed blocks. Starting with "+
"blockHeight=%v, to current blockHeight=%v", lastGradHeight,
bestHeight)
// Loop through and check for graduating outputs at each of the missed
// block heights.
for curHeight := lastGradHeight + 1; curHeight <= uint32(bestHeight); curHeight++ {
utxnLog.Debugf("Attempting to graduate outputs at height=%v",
curHeight)
if err := u.graduateClass(curHeight); err != nil {
utxnLog.Errorf("Failed to graduate outputs at "+
"height=%v: %v", curHeight, err)
return err
}
}
utxnLog.Infof("UTXO Nursery is now fully synced")
return nil
}
// regraduateClass handles the steps involved in re-registering for
// confirmations for all still-active outputs at a particular height. This is
// used during restarts to ensure that any still-pending state transitions are
// properly registered, so they can be driven by the chain notifier. No
// transactions or signing are done as a result of this step.
func (u *utxoNursery) regraduateClass(classHeight uint32) error {
// Fetch all information about the crib and kindergarten outputs at this
// height. In addition to the outputs, we also retrieve the finalized
// kindergarten sweep txn, which will be nil if we have not attempted
// this height before, or if no kindergarten outputs exist at this
// height.
finalTx, kgtnOutputs, cribOutputs, err := u.cfg.Store.FetchClass(
classHeight)
if err != nil {
return err
}
if finalTx != nil {
utxnLog.Infof("Re-registering confirmation for kindergarten "+
"sweep transaction at height=%d ", classHeight)
err = u.registerSweepConf(finalTx, kgtnOutputs, classHeight)
if err != nil {
utxnLog.Errorf("Failed to re-register for kindergarten "+
"sweep transaction at height=%d: %v",
classHeight, err)
return err
}
}
if len(cribOutputs) == 0 {
return nil
}
utxnLog.Infof("Re-registering confirmation for first-stage HTLC "+
"outputs at height=%d ", classHeight)
// Now, we broadcast all pre-signed htlc txns from the crib outputs at
// this height. There is no need to finalize these txns, since the txid
// is predetermined when signed in the wallet.
for i := range cribOutputs {
err = u.registerTimeoutConf(&cribOutputs[i], classHeight)
if err != nil {
utxnLog.Errorf("Failed to re-register first-stage "+
"HTLC output %v", cribOutputs[i].OutPoint())
return err
}
}
return nil
}
// incubator is tasked with driving all state transitions that are dependent on
// the current height of the blockchain. As new blocks arrive, the incubator
// will attempt spend outputs at the latest height. The asynchronous
// confirmation of these spends will either 1) move a crib output into the
// kindergarten bucket or 2) move a kindergarten output into the graduated
// bucket.
func (u *utxoNursery) incubator(newBlockChan *chainntnfs.BlockEpochEvent) {
defer u.wg.Done()
defer newBlockChan.Cancel()
currentHeight := startingHeight
out:
for {
select {
case preschoolRequest := <-u.requests:
utxnLog.Infof("Incubating %v new outputs",
len(preschoolRequest.outputs))
for _, output := range preschoolRequest.outputs {
// We'll skip any zero value'd outputs as this
// indicates we don't have a settled balance
// within the commitment transaction.
if output.Amount() == 0 {
continue
}
sourceTxid := output.OutPoint().Hash
if err := output.enterPreschool(u.db); err != nil {
utxnLog.Errorf("unable to add kidOutput to preschool: %v, %v ",
output, err)
continue
}
// Register for a notification that will
// trigger graduation from preschool to
// kindergarten when the channel close
// transaction has been confirmed.
confChan, err := u.notifier.RegisterConfirmationsNtfn(
&sourceTxid, 1, currentHeight,
)
if err != nil {
utxnLog.Errorf("unable to register output for confirmation: %v",
sourceTxid)
continue
}
// Launch a dedicated goroutine that will move
// the output from the preschool bucket to the
// kindergarten bucket once the channel close
// transaction has been confirmed.
go output.waitForPromotion(u.db, confChan)
}
case epoch, ok := <-newBlockChan.Epochs:
// If the epoch channel has been closed, then the
// ChainNotifier is exiting which means the daemon is
@ -358,22 +688,516 @@ out:
// will give stale data
// A new block has just been connected to the main
// chain which means we might be able to graduate some
// outputs out of the kindergarten bucket. Graduation
// entails successfully sweeping a time-locked output.
// chain, which means we might be able to graduate crib
// or kindergarten outputs at this height. This involves
// broadcasting any presigned htlc timeout txns, as well
// as signing and broadcasting a sweep txn that spends
// from all kindergarten outputs at this height.
height := uint32(epoch.Height)
currentHeight = height
if err := u.graduateKindergarten(height); err != nil {
if err := u.graduateClass(height); err != nil {
utxnLog.Errorf("error while graduating "+
"kindergarten outputs: %v", err)
"class at height=%d: %v", height, err)
// TODO(conner): signal fatal error to daemon
}
case <-u.quit:
break out
return
}
}
}
// graduateClass handles the steps involved in spending outputs whose CSV or
// CLTV delay expires at the nursery's current height. This method is called
// each time a new block arrives, or during startup to catch up on heights we
// may have missed while the nursery was offline.
func (u *utxoNursery) graduateClass(classHeight uint32) error {
// Record this height as the nursery's current best height.
u.mu.Lock()
defer u.mu.Unlock()
u.bestHeight = classHeight
// Fetch all information about the crib and kindergarten outputs at this
// height. In addition to the outputs, we also retrieve the finalized
// kindergarten sweep txn, which will be nil if we have not attempted
// this height before, or if no kindergarten outputs exist at this
// height.
finalTx, kgtnOutputs, cribOutputs, err := u.cfg.Store.FetchClass(
classHeight)
if err != nil {
return err
}
// Load the last finalized height, so we can determine if the
// kindergarten sweep txn should be crafted.
lastFinalizedHeight, err := u.cfg.Store.LastFinalizedHeight()
if err != nil {
return err
}
// If we haven't processed this height before, we finalize the
// graduating kindergarten outputs, by signing a sweep transaction that
// spends from them. This txn is persisted such that we never broadcast
// a different txn for the same height. This allows us to recover from
// failures, and watch for the correct txid.
if classHeight > lastFinalizedHeight {
// If this height has never been finalized, we have never
// generated a sweep txn for this height. Generate one if there
// are kindergarten outputs to be spent.
if len(kgtnOutputs) > 0 {
finalTx, err = u.createSweepTx(kgtnOutputs)
if err != nil {
utxnLog.Errorf("Failed to create sweep txn at "+
"height=%d", classHeight)
return err
}
}
// Persist the kindergarten sweep txn to the nursery store. It
// is safe to store a nil finalTx, which happens if there are no
// graduating kindergarten outputs.
err = u.cfg.Store.FinalizeKinder(classHeight, finalTx)
if err != nil {
utxnLog.Errorf("Failed to finalize kindergarten at "+
"height=%d", classHeight)
return err
}
// Log if the finalized transaction is non-trivial.
if finalTx != nil {
utxnLog.Infof("Finalized kindergarten at height=%d ",
classHeight)
}
}
// Now that the kindergarten sweep txn has either been finalized or
// restored, broadcast the txn, and set up notifications that will
// transition the swept kindergarten outputs into graduated outputs.
if finalTx != nil {
err := u.sweepGraduatingKinders(classHeight, finalTx,
kgtnOutputs)
if err != nil {
utxnLog.Errorf("Failed to sweep %d kindergarten outputs "+
"at height=%d: %v", len(kgtnOutputs), classHeight,
err)
return err
}
}
// Now, we broadcast all pre-signed htlc txns from the crib outputs at
// this height. There is no need to finalize these txns, since the txid
// is predetermined when signed in the wallet.
for i := range cribOutputs {
err := u.sweepCribOutput(classHeight, &cribOutputs[i])
if err != nil {
utxnLog.Errorf("Failed to sweep first-stage HTLC "+
"(CLTV-delayed) output %v",
cribOutputs[i].OutPoint())
return err
}
}
return u.cfg.Store.GraduateHeight(classHeight)
}
// craftSweepTx accepts accepts a list of kindergarten outputs, and signs and
// generates a signed txn that spends from them. This method also makes an
// accurate fee estimate before generating the required witnesses.
func (u *utxoNursery) createSweepTx(kgtnOutputs []kidOutput) (*wire.MsgTx, error) {
// Create a transaction which sweeps all the newly mature outputs into
// a output controlled by the wallet.
// TODO(roasbeef): can be more intelligent about buffering outputs to
// be more efficient on-chain.
// Assemble the kindergarten class into a slice csv spendable outputs,
// while also computing an estimate for the total transaction weight.
var (
csvSpendableOutputs []CsvSpendableOutput
weightEstimate lnwallet.TxWeightEstimator
)
// Allocate enough room for each of the kindergarten outputs.
csvSpendableOutputs = make([]CsvSpendableOutput, 0, len(kgtnOutputs))
// Our sweep transaction will pay to a single segwit p2wkh address,
// ensure it contributes to our weight estimate.
weightEstimate.AddP2WKHOutput()
// For each kindergarten output, use its witness type to determine the
// estimate weight of its witness.
for i := range kgtnOutputs {
input := &kgtnOutputs[i]
var witnessWeight int
switch input.WitnessType() {
case lnwallet.CommitmentTimeLock:
witnessWeight = lnwallet.ToLocalTimeoutWitnessSize
case lnwallet.HtlcOfferedTimeout:
witnessWeight = lnwallet.OfferedHtlcTimeoutWitnessSize
default:
utxnLog.Warnf("kindergarten output in nursery store "+
"contains unexpected witness type: %v",
input.WitnessType())
continue
}
// Add the kindergarten output's input and witness to our
// running estimate.
weightEstimate.AddWitnessInput(witnessWeight)
// Include this input in the transaction.
csvSpendableOutputs = append(csvSpendableOutputs, input)
}
txWeight := uint64(weightEstimate.Weight())
return u.sweepCsvSpendableOutputsTxn(txWeight, csvSpendableOutputs)
}
// sweepCsvSpendableOutputsTxn creates a final sweeping transaction with all
// witnesses in place for all inputs using the provided txn fee. The created
// transaction has a single output sending all the funds back to the source
// wallet, after accounting for the fee estimate.
func (u *utxoNursery) sweepCsvSpendableOutputsTxn(txWeight uint64,
inputs []CsvSpendableOutput) (*wire.MsgTx, error) {
// Generate the receiving script to which the funds will be swept.
pkScript, err := u.cfg.GenSweepScript()
if err != nil {
return nil, err
}
// Sum up the total value contained in the inputs.
var totalSum btcutil.Amount
for _, o := range inputs {
totalSum += o.Amount()
}
// Using the txn weight estimate, compute the required txn fee.
feePerWeight := u.cfg.Estimator.EstimateFeePerWeight(1)
txFee := btcutil.Amount(txWeight * feePerWeight)
// Sweep as much possible, after subtracting txn fees.
sweepAmt := int64(totalSum - txFee)
// Create the sweep transaction that we will be building. We use version
// 2 as it is required for CSV. The txn will sweep the amount after fees
// to the pkscript generated above.
sweepTx := wire.NewMsgTx(2)
sweepTx.AddTxOut(&wire.TxOut{
PkScript: pkScript,
Value: sweepAmt,
})
// Add all of our inputs, including the respective CSV delays.
for _, input := range inputs {
sweepTx.AddTxIn(&wire.TxIn{
PreviousOutPoint: *input.OutPoint(),
// TODO(roasbeef): assumes pure block delays
Sequence: input.BlocksToMaturity(),
})
}
// Before signing the transaction, check to ensure that it meets some
// basic validity requirements.
// TODO(conner): add more control to sanity checks, allowing us to delay
// spending "problem" outputs, e.g. possibly batching with other classes
// if fees are too low.
btx := btcutil.NewTx(sweepTx)
if err := blockchain.CheckTransactionSanity(btx); err != nil {
return nil, err
}
hashCache := txscript.NewTxSigHashes(sweepTx)
// With all the inputs in place, use each output's unique witness
// function to generate the final witness required for spending.
addWitness := func(idx int, tso CsvSpendableOutput) error {
witness, err := tso.BuildWitness(u.cfg.Signer, sweepTx, hashCache, idx)
if err != nil {
return err
}
sweepTx.TxIn[idx].Witness = witness
return nil
}
for i, input := range inputs {
if err := addWitness(i, input); err != nil {
return nil, err
}
}
return sweepTx, nil
}
// sweepGraduatingKinders generates and broadcasts the transaction that
// transfers control of funds from a channel commitment transaction to the
// user's wallet.
func (u *utxoNursery) sweepGraduatingKinders(classHeight uint32,
finalTx *wire.MsgTx, kgtnOutputs []kidOutput) error {
utxnLog.Infof("Sweeping %v CSV-delayed outputs with sweep tx "+
"(txid=%v): %v", len(kgtnOutputs), finalTx.TxHash(),
newLogClosure(func() string {
return spew.Sdump(finalTx)
}),
)
// With the sweep transaction fully signed, broadcast the transaction
// to the network. Additionally, we can stop tracking these outputs as
// they've just been swept.
// TODO(conner): handle concrete error types returned from publication
if err := u.cfg.PublishTransaction(finalTx); err != nil &&
!strings.Contains(err.Error(), "TX rejected:") {
utxnLog.Errorf("unable to broadcast sweep tx: %v, %v",
err, spew.Sdump(finalTx))
return err
}
return u.registerSweepConf(finalTx, kgtnOutputs, classHeight)
}
// registerSweepConf is responsible for registering a finalized kindergarten
// sweep transaction for confirmation notifications. If the confirmation was
// successfully registered, a goroutine will be spawned that waits for the
// confirmation, and graduates the provided kindergarten class within the
// nursery store.
func (u *utxoNursery) registerSweepConf(finalTx *wire.MsgTx,
kgtnOutputs []kidOutput, heightHint uint32) error {
finalTxID := finalTx.TxHash()
confChan, err := u.cfg.Notifier.RegisterConfirmationsNtfn(
&finalTxID, u.cfg.ConfDepth, heightHint)
if err != nil {
utxnLog.Errorf("unable to register notification for "+
"sweep confirmation: %v", finalTxID)
return err
}
utxnLog.Infof("Registering sweep tx %v for confs at height=%d",
finalTxID, heightHint)
u.wg.Add(1)
go u.waitForSweepConf(heightHint, kgtnOutputs, confChan)
return nil
}
// waitForSweepConf watches for the confirmation of a sweep transaction
// containing a batch of kindergarten outputs. Once confirmation has been
// received, the nursery will mark those outputs as fully graduated, and proceed
// to mark any mature channels as fully closed in channeldb.
// NOTE(conner): this method MUST be called as a go routine.
func (u *utxoNursery) waitForSweepConf(classHeight uint32,
kgtnOutputs []kidOutput, confChan *chainntnfs.ConfirmationEvent) {
defer u.wg.Done()
select {
case _, ok := <-confChan.Confirmed:
if !ok {
utxnLog.Errorf("Notification chan closed, can't"+
" advance %v graduating outputs",
len(kgtnOutputs))
return
}
case <-u.quit:
return
}
u.mu.Lock()
defer u.mu.Unlock()
// TODO(conner): add retry logic?
// Mark the confirmed kindergarten outputs as graduated.
if err := u.cfg.Store.GraduateKinder(classHeight); err != nil {
utxnLog.Errorf("Unable to graduate %v kingdergarten outputs: "+
"%v", len(kgtnOutputs), err)
return
}
utxnLog.Infof("Graduated %d kindergarten outputs from height=%d",
len(kgtnOutputs), classHeight)
// Iterate over the kid outputs and construct a set of all channel
// points to which they belong.
var possibleCloses = make(map[wire.OutPoint]struct{})
for _, kid := range kgtnOutputs {
possibleCloses[*kid.OriginChanPoint()] = struct{}{}
}
// Attempt to close each channel, only doing so if all of the channel's
// outputs have been graduated.
for chanPoint := range possibleCloses {
if err := u.closeAndRemoveIfMature(&chanPoint); err != nil {
utxnLog.Errorf("Failed to close and remove channel %v",
chanPoint)
return
}
}
}
// sweepCribOutput broadcasts the crib output's htlc timeout txn, and sets up a
// notification that will advance it to the kindergarten bucket upon
// confirmation.
func (u *utxoNursery) sweepCribOutput(classHeight uint32, baby *babyOutput) error {
utxnLog.Infof("Publishing CTLV-delayed HTLC output using timeout tx "+
"(txid=%v): %v", baby.timeoutTx.TxHash(),
newLogClosure(func() string {
return spew.Sdump(baby.timeoutTx)
}),
)
// Broadcast HTLC transaction
// TODO(conner): handle concrete error types returned from publication
err := u.cfg.PublishTransaction(baby.timeoutTx)
if err != nil &&
!strings.Contains(err.Error(), "TX rejected:") {
utxnLog.Errorf("Unable to broadcast baby tx: "+
"%v, %v", err,
spew.Sdump(baby.timeoutTx))
return err
}
return u.registerTimeoutConf(baby, classHeight)
}
// registerTimeoutConf is responsible for subscribing to confirmation
// notification for an htlc timeout transaction. If successful, a goroutine will
// be spawned that will transition the provided baby output into the
// kindergarten state within the nursery store.
func (u *utxoNursery) registerTimeoutConf(baby *babyOutput, heightHint uint32) error {
birthTxID := baby.timeoutTx.TxHash()
// Register for the confirmation of presigned htlc txn.
confChan, err := u.cfg.Notifier.RegisterConfirmationsNtfn(
&birthTxID, u.cfg.ConfDepth, heightHint)
if err != nil {
return err
}
utxnLog.Infof("Htlc output %v registered for promotion "+
"notification.", baby.OutPoint())
u.wg.Add(1)
go u.waitForTimeoutConf(baby, confChan)
return nil
}
// waitForTimeoutConf watches for the confirmation of an htlc timeout
// transaction, and attempts to move the htlc output from the crib bucket to the
// kindergarten bucket upon success.
func (u *utxoNursery) waitForTimeoutConf(baby *babyOutput,
confChan *chainntnfs.ConfirmationEvent) {
defer u.wg.Done()
select {
case txConfirmation, ok := <-confChan.Confirmed:
if !ok {
utxnLog.Errorf("Notification chan "+
"closed, can't advance baby output %v",
baby.OutPoint())
return
}
baby.SetConfHeight(txConfirmation.BlockHeight)
case <-u.quit:
return
}
u.mu.Lock()
defer u.mu.Unlock()
// TODO(conner): add retry logic?
err := u.cfg.Store.CribToKinder(baby)
if err != nil {
utxnLog.Errorf("Unable to move htlc output from "+
"crib to kindergarten bucket: %v", err)
return
}
utxnLog.Infof("Htlc output %v promoted to "+
"kindergarten", baby.OutPoint())
}
// registerCommitConf is responsible for subscribing to the confirmation of a
// commitment transaction. If successful, the provided preschool output will be
// moved persistently into the kindergarten state within the nursery store.
func (u *utxoNursery) registerCommitConf(kid *kidOutput, heightHint uint32) error {
txID := kid.OutPoint().Hash
confChan, err := u.cfg.Notifier.RegisterConfirmationsNtfn(&txID,
u.cfg.ConfDepth, heightHint)
if err != nil {
return err
}
utxnLog.Infof("Commitment outpoint %v registered for "+
"confirmation notification.", kid.OutPoint())
u.wg.Add(1)
go u.waitForCommitConf(kid, confChan)
return nil
}
// waitForCommitConf is intended to be run as a goroutine that will wait until a
// channel force close commitment transaction has been included in a confirmed
// block. Once the transaction has been confirmed (as reported by the Chain
// Notifier), waitForCommitConf will delete the output from the "preschool"
// database bucket and atomically add it to the "kindergarten" database bucket.
// This is the second step in the output incubation process.
func (u *utxoNursery) waitForCommitConf(kid *kidOutput,
confChan *chainntnfs.ConfirmationEvent) {
defer u.wg.Done()
select {
case txConfirmation, ok := <-confChan.Confirmed:
if !ok {
utxnLog.Errorf("Notification chan "+
"closed, can't advance output %v",
kid.OutPoint())
return
}
kid.SetConfHeight(txConfirmation.BlockHeight)
case <-u.quit:
return
}
u.mu.Lock()
defer u.mu.Unlock()
// TODO(conner): add retry logic?
err := u.cfg.Store.PreschoolToKinder(kid)
if err != nil {
utxnLog.Errorf("Unable to move commitment output "+
"from preschool to kindergarten bucket: %v",
err)
return
}
utxnLog.Infof("Commitment output %v promoted to "+
"kindergarten", kid.OutPoint())
}
// contractMaturityReport is a report that details the maturity progress of a
// particular force closed contract.
type contractMaturityReport struct {
@ -385,9 +1209,15 @@ type contractMaturityReport struct {
// contract.
limboBalance btcutil.Amount
// confirmationHeight is the block height that this output originally
// confirmed at.
confirmationHeight uint32
// recoveredBalance is the total value that has been successfully swept
// back to the user's wallet.
recoveredBalance btcutil.Amount
// localAmount is the local value of the commitment output.
localAmount btcutil.Amount
// confHeight is the block height that this output originally confirmed.
confHeight uint32
// maturityRequirement is the input age required for this output to
// reach maturity.
@ -396,535 +1226,167 @@ type contractMaturityReport struct {
// maturityHeight is the absolute block height that this output will
// mature at.
maturityHeight uint32
// htlcs records a maturity report for each htlc output in this channel.
htlcs []htlcMaturityReport
}
// NurseryReport attempts to return a nursery report stored for the target
// outpoint. A nursery report details the maturity/sweeping progress for a
// contract that was previously force closed. If a report entry for the target
// chanPoint is unable to be constructed, then an error will be returned.
func (u *utxoNursery) NurseryReport(chanPoint *wire.OutPoint) (*contractMaturityReport, error) {
var report *contractMaturityReport
if err := u.db.View(func(tx *bolt.Tx) error {
// First we'll examine the preschool bucket as the target
// contract may not yet have been confirmed.
psclBucket := tx.Bucket(preschoolBucket)
if psclBucket == nil {
return nil
}
psclIndex := tx.Bucket(preschoolIndex)
if psclIndex == nil {
return nil
}
// htlcMaturityReport provides a summary of a single htlc output, and is
// embedded as party of the overarching contractMaturityReport
type htlcMaturityReport struct {
// outpoint is the final output that will be swept back to the wallet.
outpoint wire.OutPoint
var b bytes.Buffer
if err := writeOutpoint(&b, chanPoint); err != nil {
return err
}
chanPointBytes := b.Bytes()
// amount is the final value that will be swept in back to the wallet.
amount btcutil.Amount
var outputReader *bytes.Reader
// confHeight is the block height that this output originally confirmed.
confHeight uint32
// If the target contract hasn't been confirmed yet, then we
// can just construct the report from this information.
if outPoint := psclIndex.Get(chanPointBytes); outPoint != nil {
// The channel entry hasn't yet been fully confirmed
// yet, so we'll dig into the preschool bucket to fetch
// the channel information.
outputBytes := psclBucket.Get(outPoint)
if outputBytes == nil {
return nil
}
// maturityRequirement is the input age required for this output to
// reach maturity.
maturityRequirement uint32
outputReader = bytes.NewReader(outputBytes)
} else {
// Otherwise, we'll have to consult out contract index,
// so fetch that bucket as well as the kindergarten
// bucket.
indexBucket := tx.Bucket(contractIndex)
if indexBucket == nil {
return fmt.Errorf("contract not found, " +
"contract index not populated")
}
kgtnBucket := tx.Bucket(kindergartenBucket)
if kgtnBucket == nil {
return fmt.Errorf("contract not found, " +
"kindergarten bucket not populated")
}
// maturityHeight is the absolute block height that this output will
// mature at.
maturityHeight uint32
// Attempt to query the index to see if we have an
// entry for this particular contract.
indexInfo := indexBucket.Get(chanPointBytes)
if indexInfo == nil {
return ErrContractNotFound
}
// stage indicates whether the htlc is in the CLTV-timeout stage (1) or
// the CSV-delay stage (2). A stage 1 htlc's maturity height will be set
// to it's expiry height, while a stage 2 htlc's maturity height will be
// set to it's confirmation height plus the maturity requirement.
stage uint32
}
// If an entry is found, then using the height store in
// the first 4 bytes, we'll fetch the height that this
// entry matures at.
height := indexInfo[:4]
heightRow := kgtnBucket.Get(height)
if heightRow == nil {
return ErrContractNotFound
}
// AddLimboCommitment adds an incubating commitment output to maturity
// report's htlcs, and contributes its amount to the limbo balance.
func (c *contractMaturityReport) AddLimboCommitment(kid *kidOutput) {
c.limboBalance += kid.Amount()
// Once we have the entry itself, we'll slice of the
// last for bytes so we can seek into this row to fetch
// the contract's information.
offset := byteOrder.Uint32(indexInfo[4:])
outputReader = bytes.NewReader(heightRow[offset:])
}
c.localAmount += kid.Amount()
c.confHeight = kid.ConfHeight()
c.maturityRequirement = kid.BlocksToMaturity()
// With the proper set of bytes received, we'll deserialize the
// information for this immature output.
var immatureOutput kidOutput
if err := immatureOutput.Decode(outputReader); err != nil {
return err
}
// TODO(roasbeef): should actually be list of outputs
report = &contractMaturityReport{
chanPoint: *chanPoint,
limboBalance: immatureOutput.Amount(),
maturityRequirement: immatureOutput.BlocksToMaturity(),
}
// If the confirmation height is set, then this means the
// contract has been confirmed, and we know the final maturity
// height.
if immatureOutput.ConfHeight() != 0 {
report.confirmationHeight = immatureOutput.ConfHeight()
report.maturityHeight = (immatureOutput.BlocksToMaturity() +
immatureOutput.ConfHeight())
}
return nil
}); err != nil {
return nil, err
// If the confirmation height is set, then this means the contract has
// been confirmed, and we know the final maturity height.
if kid.ConfHeight() != 0 {
c.maturityHeight = kid.BlocksToMaturity() + kid.ConfHeight()
}
return report, nil
}
// enterPreschool is the first stage in the process of transferring funds from
// a force closed channel into the user's wallet. When an output is in the
// "preschool" stage, the daemon is waiting for the initial confirmation of the
// commitment transaction.
func (k *kidOutput) enterPreschool(db *channeldb.DB) error {
return db.Update(func(tx *bolt.Tx) error {
psclBucket, err := tx.CreateBucketIfNotExists(preschoolBucket)
if err != nil {
return err
}
psclIndex, err := tx.CreateBucketIfNotExists(preschoolIndex)
if err != nil {
return err
}
// AddRecoveredCommitment adds a graduated commitment output to maturity
// report's htlcs, and contributes its amount to the recovered balance.
func (c *contractMaturityReport) AddRecoveredCommitment(kid *kidOutput) {
c.recoveredBalance += kid.Amount()
// Once we have the buckets we can insert the raw bytes of the
// immature outpoint into the preschool bucket.
var outpointBytes bytes.Buffer
if err := writeOutpoint(&outpointBytes, k.OutPoint()); err != nil {
return err
}
var kidBytes bytes.Buffer
if err := k.Encode(&kidBytes); err != nil {
return err
}
err = psclBucket.Put(outpointBytes.Bytes(), kidBytes.Bytes())
if err != nil {
return err
}
c.localAmount += kid.Amount()
c.confHeight = kid.ConfHeight()
c.maturityRequirement = kid.BlocksToMaturity()
c.maturityHeight = kid.BlocksToMaturity() + kid.ConfHeight()
}
// Additionally, we'll populate the preschool index so we can
// track all the immature outpoints for a particular channel's
// chanPoint.
var b bytes.Buffer
err = writeOutpoint(&b, k.OriginChanPoint())
if err != nil {
return err
}
err = psclIndex.Put(b.Bytes(), outpointBytes.Bytes())
if err != nil {
return err
}
// AddLimboStage1Htlc adds an htlc crib output to the maturity report's
// htlcs, and contributes its amount to the limbo balance.
func (c *contractMaturityReport) AddLimboStage1Htlc(baby *babyOutput) {
c.limboBalance += baby.Amount()
utxnLog.Infof("Outpoint %v now in preschool, waiting for "+
"initial confirmation", k.OutPoint())
return nil
c.htlcs = append(c.htlcs, htlcMaturityReport{
outpoint: *baby.OutPoint(),
amount: baby.Amount(),
confHeight: baby.ConfHeight(),
maturityHeight: baby.expiry,
stage: 1,
})
}
// waitForPromotion is intended to be run as a goroutine that will wait until a
// channel force close commitment transaction has been included in a confirmed
// block. Once the transaction has been confirmed (as reported by the Chain
// Notifier), waitForPromotion will delete the output from the "preschool"
// database bucket and atomically add it to the "kindergarten" database bucket.
// This is the second step in the output incubation process.
func (k *kidOutput) waitForPromotion(db *channeldb.DB, confChan *chainntnfs.ConfirmationEvent) {
txConfirmation, ok := <-confChan.Confirmed
if !ok {
utxnLog.Errorf("notification chan "+
"closed, can't advance output %v", k.OutPoint())
return
// AddLimboStage2Htlc adds an htlc kindergarten output to the maturity report's
// htlcs, and contributes its amount to the limbo balance.
func (c *contractMaturityReport) AddLimboStage2Htlc(kid *kidOutput) {
c.limboBalance += kid.Amount()
htlcReport := htlcMaturityReport{
outpoint: *kid.OutPoint(),
amount: kid.Amount(),
confHeight: kid.ConfHeight(),
maturityRequirement: kid.BlocksToMaturity(),
stage: 2,
}
utxnLog.Infof("Outpoint %v confirmed in block %v moving to kindergarten",
k.OutPoint(), txConfirmation.BlockHeight)
// If the confirmation height is set, then this means the first stage
// has been confirmed, and we know the final maturity height of the CSV
// delay.
if kid.ConfHeight() != 0 {
htlcReport.maturityHeight = kid.ConfHeight() + kid.BlocksToMaturity()
}
k.SetConfHeight(txConfirmation.BlockHeight)
c.htlcs = append(c.htlcs, htlcReport)
}
// The following block deletes a kidOutput from the preschool database
// bucket and adds it to the kindergarten database bucket which is
// keyed by block height. Keys and values are serialized into byte
// array form prior to database insertion.
err := db.Update(func(tx *bolt.Tx) error {
var originPoint bytes.Buffer
if err := writeOutpoint(&originPoint, k.OriginChanPoint()); err != nil {
return err
}
// AddRecoveredHtlc adds an graduate output to the maturity report's htlcs, and
// contributes its amount to the recovered balance.
func (c *contractMaturityReport) AddRecoveredHtlc(kid *kidOutput) {
c.recoveredBalance += kid.Amount()
psclBucket := tx.Bucket(preschoolBucket)
if psclBucket == nil {
return errors.New("unable to open preschool bucket")
}
psclIndex := tx.Bucket(preschoolIndex)
if psclIndex == nil {
return errors.New("unable to open preschool index")
}
// Now that the entry has been confirmed, in order to move it
// along in the maturity pipeline we first delete the entry
// from the preschool bucket, as well as the secondary index.
var outpointBytes bytes.Buffer
if err := writeOutpoint(&outpointBytes, k.OutPoint()); err != nil {
return err
}
if err := psclBucket.Delete(outpointBytes.Bytes()); err != nil {
utxnLog.Errorf("unable to delete kindergarten output from "+
"preschool bucket: %v", k.OutPoint())
return err
}
if err := psclIndex.Delete(originPoint.Bytes()); err != nil {
utxnLog.Errorf("unable to delete kindergarten output from "+
"preschool index: %v", k.OutPoint())
return err
}
// Next, fetch the kindergarten bucket. This output will remain
// in this bucket until it's fully mature.
kgtnBucket, err := tx.CreateBucketIfNotExists(kindergartenBucket)
if err != nil {
return err
}
maturityHeight := k.ConfHeight() + k.BlocksToMaturity()
heightBytes := make([]byte, 4)
byteOrder.PutUint32(heightBytes, maturityHeight)
// If there're any existing outputs for this particular block
// height target, then we'll append this new output to the
// serialized list of outputs.
var existingOutputs []byte
if results := kgtnBucket.Get(heightBytes); results != nil {
existingOutputs = results
}
// We'll grab the output's offset in the value for its maturity
// height so we can add this to the contract index.
outputOffset := len(existingOutputs)
b := bytes.NewBuffer(existingOutputs)
if err := k.Encode(b); err != nil {
return err
}
if err := kgtnBucket.Put(heightBytes, b.Bytes()); err != nil {
return err
}
// Finally, we'll insert a new entry into the contract index.
// The entry itself consists of 4 bytes for the height, and 4
// bytes for the offset within the value for the height.
var indexEntry [4 + 4]byte
copy(indexEntry[:4], heightBytes)
byteOrder.PutUint32(indexEntry[4:], uint32(outputOffset))
indexBucket, err := tx.CreateBucketIfNotExists(contractIndex)
if err != nil {
return err
}
err = indexBucket.Put(originPoint.Bytes(), indexEntry[:])
if err != nil {
return err
}
utxnLog.Infof("Outpoint %v now in kindergarten, will mature "+
"at height %v (delay of %v)", k.OutPoint(),
maturityHeight, k.BlocksToMaturity())
return nil
c.htlcs = append(c.htlcs, htlcMaturityReport{
outpoint: *kid.OutPoint(),
amount: kid.Amount(),
confHeight: kid.ConfHeight(),
maturityRequirement: kid.BlocksToMaturity(),
maturityHeight: kid.ConfHeight() + kid.BlocksToMaturity(),
})
if err != nil {
utxnLog.Errorf("unable to move kid output from preschool bucket "+
"to kindergarten bucket: %v", err)
}
}
// graduateKindergarten handles the steps invoked with moving funds from a
// force close commitment transaction into a user's wallet after the output
// from the commitment transaction has become spendable. graduateKindergarten
// is called both when a new block notification has been received and also at
// startup in order to process graduations from blocks missed while the UTXO
// nursery was offline.
// TODO(roasbeef): single db transaction for the below
func (u *utxoNursery) graduateKindergarten(blockHeight uint32) error {
// First fetch the set of outputs that we can "graduate" at this
// particular block height. We can graduate an output once we've
// reached its height maturity.
kgtnOutputs, err := fetchGraduatingOutputs(u.db, u.wallet, blockHeight)
if err != nil {
return err
}
// If we're able to graduate any outputs, then create a single
// transaction which sweeps them all into the wallet.
if len(kgtnOutputs) > 0 {
err := sweepGraduatingOutputs(u.wallet, kgtnOutputs)
if err != nil {
return err
}
// Now that the sweeping transaction has been broadcast, for
// each of the immature outputs, we'll mark them as being fully
// closed within the database.
for _, closedChan := range kgtnOutputs {
err := u.db.MarkChanFullyClosed(closedChan.OriginChanPoint())
if err != nil {
return err
}
}
}
// Using a re-org safety margin of 6-blocks, delete any outputs which
// have graduated 6 blocks ago.
deleteHeight := blockHeight - 6
if err := deleteGraduatedOutputs(u.db, deleteHeight); err != nil {
return err
}
// Finally, record the last height at which we graduated outputs so we
// can reconcile our state with that of the main-chain during restarts.
return putLastHeightGraduated(u.db, blockHeight)
}
// fetchGraduatingOutputs checks the "kindergarten" database bucket whenever a
// new block is received in order to determine if commitment transaction
// outputs have become newly spendable. If fetchGraduatingOutputs finds outputs
// that are ready for "graduation," it passes them on to be swept. This is the
// third step in the output incubation process.
func fetchGraduatingOutputs(db *channeldb.DB, wallet *lnwallet.LightningWallet,
blockHeight uint32) ([]*kidOutput, error) {
var results []byte
if err := db.View(func(tx *bolt.Tx) error {
// A new block has just been connected, check to see if we have
// any new outputs that can be swept into the wallet.
kgtnBucket := tx.Bucket(kindergartenBucket)
if kgtnBucket == nil {
return nil
}
heightBytes := make([]byte, 4)
byteOrder.PutUint32(heightBytes, blockHeight)
results = kgtnBucket.Get(heightBytes)
// closeAndRemoveIfMature removes a particular channel from the channel index
// if and only if all of its outputs have been marked graduated. If the channel
// still has ungraduated outputs, the method will succeed without altering the
// database state.
func (u *utxoNursery) closeAndRemoveIfMature(chanPoint *wire.OutPoint) error {
isMature, err := u.cfg.Store.IsMatureChannel(chanPoint)
if err == ErrContractNotFound {
return nil
}); err != nil {
return nil, err
}
// If no time-locked outputs can be swept at this point, then we can
// exit early.
if len(results) == 0 {
return nil, nil
}
// Otherwise, we deserialize the list of kid outputs into their full
// forms.
kgtnOutputs, err := deserializeKidList(bytes.NewReader(results))
if err != nil {
utxnLog.Errorf("error while deserializing list of kidOutputs: %v", err)
}
// For each of the outputs, we also generate its proper witness
// function based on its witness type. This varies if the output is on
// our commitment transaction or theirs, and also if it's an HTLC
// output or not.
for _, kgtnOutput := range kgtnOutputs {
kgtnOutput.witnessFunc = kgtnOutput.witnessType.GenWitnessFunc(
wallet.Cfg.Signer, kgtnOutput.SignDesc())
}
utxnLog.Infof("New block: height=%v, sweeping %v mature outputs",
blockHeight, len(kgtnOutputs))
return kgtnOutputs, nil
}
// sweepGraduatingOutputs generates and broadcasts the transaction that
// transfers control of funds from a channel commitment transaction to the
// user's wallet.
func sweepGraduatingOutputs(wallet *lnwallet.LightningWallet, kgtnOutputs []*kidOutput) error {
// Create a transaction which sweeps all the newly mature outputs into
// a output controlled by the wallet.
// TODO(roasbeef): can be more intelligent about buffering outputs to
// be more efficient on-chain.
sweepTx, err := createSweepTx(wallet, kgtnOutputs)
if err != nil {
// TODO(roasbeef): retry logic?
utxnLog.Errorf("unable to create sweep tx: %v", err)
} else if err != nil {
utxnLog.Errorf("Unable to determine maturity of "+
"channel=%s", chanPoint)
return err
}
utxnLog.Infof("Sweeping %v time-locked outputs "+
"with sweep tx (txid=%v): %v", len(kgtnOutputs),
sweepTx.TxHash(),
newLogClosure(func() string {
return spew.Sdump(sweepTx)
}))
// Nothing to do if we are still incubating.
if !isMature {
return nil
}
// With the sweep transaction fully signed, broadcast the transaction
// to the network. Additionally, we can stop tracking these outputs as
// they've just been swept.
if err := wallet.PublishTransaction(sweepTx); err != nil {
utxnLog.Errorf("unable to broadcast sweep tx: %v, %v",
err, spew.Sdump(sweepTx))
// Now that the sweeping transaction has been broadcast, for
// each of the immature outputs, we'll mark them as being fully
// closed within the database.
err = u.cfg.DB.MarkChanFullyClosed(chanPoint)
if err != nil {
utxnLog.Errorf("Unable to mark channel=%v as fully "+
"closed: %v", chanPoint, err)
return err
}
utxnLog.Infof("Marked Channel(%s) as fully closed", chanPoint)
// Now that the channel is fully closed, we remove the channel from the
// nursery store here. This preserves the invariant that we never remove
// a channel unless it is mature, as this is the only place the utxo
// nursery removes a channel.
if err := u.cfg.Store.RemoveChannel(chanPoint); err != nil {
utxnLog.Errorf("Unable to remove channel=%s from "+
"nursery store: %v", chanPoint, err)
return err
}
utxnLog.Infof("Removed channel %v from nursery store", chanPoint)
return nil
}
// createSweepTx creates a final sweeping transaction with all witnesses in
// place for all inputs. The created transaction has a single output sending
// all the funds back to the source wallet.
func createSweepTx(wallet *lnwallet.LightningWallet,
matureOutputs []*kidOutput) (*wire.MsgTx, error) {
pkScript, err := newSweepPkScript(wallet)
if err != nil {
return nil, err
}
var totalSum btcutil.Amount
for _, o := range matureOutputs {
totalSum += o.Amount()
}
sweepTx := wire.NewMsgTx(2)
sweepTx.AddTxOut(&wire.TxOut{
PkScript: pkScript,
Value: int64(totalSum - 5000),
})
for _, utxo := range matureOutputs {
sweepTx.AddTxIn(&wire.TxIn{
PreviousOutPoint: *utxo.OutPoint(),
// TODO(roasbeef): assumes pure block delays
Sequence: utxo.BlocksToMaturity(),
})
}
// TODO(roasbeef): insert fee calculation
// * remove hardcoded fee above
// With all the inputs in place, use each output's unique witness
// function to generate the final witness required for spending.
hashCache := txscript.NewTxSigHashes(sweepTx)
for i, txIn := range sweepTx.TxIn {
witness, err := matureOutputs[i].witnessFunc(sweepTx, hashCache, i)
if err != nil {
return nil, err
}
txIn.Witness = witness
}
return sweepTx, nil
}
// deleteGraduatedOutputs removes outputs from the kindergarten database bucket
// when six blockchain confirmations have passed since the outputs were swept.
// We wait for six confirmations to ensure that the outputs will be swept if a
// chain reorganization occurs. This is the final step in the output incubation
// process.
func deleteGraduatedOutputs(db *channeldb.DB, deleteHeight uint32) error {
return db.Update(func(tx *bolt.Tx) error {
kgtnBucket := tx.Bucket(kindergartenBucket)
if kgtnBucket == nil {
return nil
}
heightBytes := make([]byte, 4)
byteOrder.PutUint32(heightBytes, deleteHeight)
results := kgtnBucket.Get(heightBytes)
if results == nil {
return nil
}
// Delete the row for this height within the kindergarten bucket.k
if err := kgtnBucket.Delete(heightBytes); err != nil {
return err
}
sweptOutputs, err := deserializeKidList(bytes.NewBuffer(results))
if err != nil {
return err
}
utxnLog.Infof("Deleting %v swept outputs from kindergarten bucket "+
"at block height: %v", len(sweptOutputs), deleteHeight)
// Additionally, for each output that has now been fully swept,
// we'll also remove the index entry for that output.
indexBucket := tx.Bucket(contractIndex)
if indexBucket == nil {
return nil
}
for _, sweptOutput := range sweptOutputs {
var chanPoint bytes.Buffer
err := writeOutpoint(&chanPoint, sweptOutput.OriginChanPoint())
if err != nil {
return err
}
if err := indexBucket.Delete(chanPoint.Bytes()); err != nil {
return err
}
}
return nil
})
}
// putLastHeightGraduated persists the most recently processed blockheight
// to the database. This blockheight is used during restarts to determine if
// blocks were missed while the UTXO Nursery was offline.
func putLastHeightGraduated(db *channeldb.DB, blockheight uint32) error {
return db.Update(func(tx *bolt.Tx) error {
kgtnBucket, err := tx.CreateBucketIfNotExists(kindergartenBucket)
if err != nil {
return nil
}
heightBytes := make([]byte, 4)
byteOrder.PutUint32(heightBytes, blockheight)
return kgtnBucket.Put(lastGraduatedHeightKey, heightBytes)
})
}
// newSweepPkScript creates a new public key script which should be used to
// sweep any time-locked, or contested channel funds into the wallet.
// Specifically, the script generated is a version 0,
// pay-to-witness-pubkey-hash (p2wkh) output.
// Specifically, the script generated is a version 0, pay-to-witness-pubkey-hash
// (p2wkh) output.
func newSweepPkScript(wallet lnwallet.WalletController) ([]byte, error) {
sweepAddr, err := wallet.NewAddress(lnwallet.WitnessPubKey, false)
if err != nil {
@ -934,26 +1396,6 @@ func newSweepPkScript(wallet lnwallet.WalletController) ([]byte, error) {
return txscript.PayToAddrScript(sweepAddr)
}
// deserializedKidList takes a sequence of serialized kid outputs and returns a
// slice of kidOutput structs.
func deserializeKidList(r io.Reader) ([]*kidOutput, error) {
var kidOutputs []*kidOutput
for {
var kid = &kidOutput{}
if err := kid.Decode(r); err != nil {
if err == io.EOF {
break
} else {
return nil, err
}
}
kidOutputs = append(kidOutputs, kid)
}
return kidOutputs, nil
}
// CsvSpendableOutput is a SpendableOutput that contains all of the information
// necessary to construct, sign, and sweep an output locked with a CSV delay.
type CsvSpendableOutput interface {
@ -977,12 +1419,13 @@ type CsvSpendableOutput interface {
OriginChanPoint() *wire.OutPoint
}
// babyOutput is an HTLC output that is in the earliest stage of upbringing.
// Each babyOutput carries a presigned timeout transction, which should be
// broadcast at the appropriate CLTV expiry, and its future kidOutput self. If
// all goes well, and the timeout transaction is successfully confirmed, the
// the now-mature kidOutput will be unwrapped and continue its journey through
// the nursery.
// babyOutput represents a two-stage CSV locked output, and is used to track
// htlc outputs through incubation. The first stage requires broadcasting a
// presigned timeout txn that spends from the CLTV locked output on the
// commitment txn. A babyOutput is treated as a subset of CsvSpendableOutputs,
// with the additional constraint that a transaction must be broadcast before it
// can be spent. Each baby transaction embeds the kidOutput that can later be
// used to spend the CSV output contained in the timeout txn.
type babyOutput struct {
// expiry is the absolute block height at which the timeoutTx should be
// broadcast to the network.
@ -992,8 +1435,8 @@ type babyOutput struct {
// transitions the htlc into the delay+claim stage.
timeoutTx *wire.MsgTx
// kidOutput represents the CSV output to be swept after the timeoutTx has
// been broadcast and confirmed.
// kidOutput represents the CSV output to be swept from the timeoutTx
// after it has been broadcast and confirmed.
kidOutput
}

@ -40,11 +40,38 @@ var (
Hash: [chainhash.HashSize]byte{
0x81, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda,
0x63, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17,
0xd, 0xe7, 0x95, 0xe4, 0xb7, 0x25, 0xb8, 0x4d,
0x0d, 0xe7, 0x95, 0xe4, 0xb7, 0x25, 0xb8, 0x4d,
0x1e, 0xb, 0x4c, 0xfd, 0x9e, 0xc5, 0x8c, 0xe9,
},
Index: 23,
},
{
Hash: [chainhash.HashSize]byte{
0x1e, 0xb, 0x4c, 0xfd, 0x9e, 0xc5, 0x8c, 0xe9,
0x81, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda,
0x0d, 0xe7, 0x95, 0xe4, 0xb7, 0x25, 0xb8, 0x4d,
0x63, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17,
},
Index: 30,
},
{
Hash: [chainhash.HashSize]byte{
0x0d, 0xe7, 0x95, 0xe4, 0xfc, 0xd2, 0xc6, 0xda,
0xb7, 0x25, 0xb8, 0x4d, 0x63, 0x59, 0xe6, 0x96,
0x31, 0x13, 0xa1, 0x17, 0x81, 0xb6, 0x37, 0xd8,
0x1e, 0x0b, 0x4c, 0xfd, 0x9e, 0xc5, 0x8c, 0xe9,
},
Index: 2,
},
{
Hash: [chainhash.HashSize]byte{
0x48, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17,
0x51, 0xb6, 0x37, 0xd8, 0x1f, 0x0b, 0x4c, 0xf9,
0x9e, 0xc5, 0x8c, 0xe9, 0xfc, 0xd2, 0xc6, 0xda,
0x2d, 0xe7, 0x93, 0xe4, 0xb7, 0x25, 0xb8, 0x4d,
},
Index: 9,
},
}
keys = [][]byte{
@ -170,50 +197,61 @@ var (
{
breachedOutput: breachedOutput{
amt: btcutil.Amount(13e7),
outpoint: outPoints[0],
outpoint: outPoints[1],
witnessType: lnwallet.CommitmentTimeLock,
},
originChanPoint: outPoints[1],
blocksToMaturity: uint32(100),
confHeight: uint32(1770001),
originChanPoint: outPoints[0],
blocksToMaturity: uint32(42),
confHeight: uint32(1000),
},
{
breachedOutput: breachedOutput{
amt: btcutil.Amount(24e7),
outpoint: outPoints[1],
outpoint: outPoints[2],
witnessType: lnwallet.CommitmentTimeLock,
},
originChanPoint: outPoints[0],
blocksToMaturity: uint32(50),
confHeight: uint32(22342321),
blocksToMaturity: uint32(42),
confHeight: uint32(1000),
},
{
breachedOutput: breachedOutput{
amt: btcutil.Amount(2e5),
outpoint: outPoints[2],
outpoint: outPoints[3],
witnessType: lnwallet.CommitmentTimeLock,
},
originChanPoint: outPoints[2],
blocksToMaturity: uint32(12),
confHeight: uint32(34241),
originChanPoint: outPoints[0],
blocksToMaturity: uint32(28),
confHeight: uint32(500),
},
{
breachedOutput: breachedOutput{
amt: btcutil.Amount(10e6),
outpoint: outPoints[4],
witnessType: lnwallet.CommitmentTimeLock,
},
originChanPoint: outPoints[0],
blocksToMaturity: uint32(28),
confHeight: uint32(500),
},
}
babyOutputs = []babyOutput{
{
kidOutput: kidOutputs[0],
kidOutput: kidOutputs[1],
expiry: 3829,
timeoutTx: timeoutTx,
},
{
kidOutput: kidOutputs[1],
expiry: 85903,
kidOutput: kidOutputs[2],
expiry: 4,
timeoutTx: timeoutTx,
},
{
kidOutput: kidOutputs[2],
kidOutput: kidOutputs[3],
expiry: 4,
timeoutTx: timeoutTx,
},
@ -283,32 +321,18 @@ func init() {
}
signDescriptors[i].PubKey = pk
kidOutputs[i].signDesc = signDescriptors[i]
babyOutputs[i].kidOutput.signDesc = signDescriptors[i]
}
}
func TestDeserializeKidsList(t *testing.T) {
var b bytes.Buffer
for _, kid := range kidOutputs {
if err := kid.Encode(&b); err != nil {
t.Fatalf("unable to serialize and add kid output to "+
"list: %v", err)
}
}
kidList, err := deserializeKidList(&b)
if err != nil {
t.Fatalf("unable to deserialize kid output list: %v", err)
}
for i := range kidOutputs {
if !reflect.DeepEqual(&kidOutputs[i], kidList[i]) {
t.Fatalf("kidOutputs don't match \n%+v\n%+v",
&kidOutputs[i], kidList[i])
}
isd := i % len(signDescriptors)
kidOutputs[i].signDesc = signDescriptors[isd]
}
for i := range babyOutputs {
isd := i % len(signDescriptors)
babyOutputs[i].kidOutput.signDesc = signDescriptors[isd]
}
initIncubateTests()
}
func TestKidOutputSerialization(t *testing.T) {