Merge pull request #384 from cfromknecht/utxn-incubate-2nd-layer-htlcs
Utxo Nursery, adds outgoing 2nd-layer HTLC persistence
This commit is contained in:
commit
caec23a236
748
lnd_test.go
748
lnd_test.go
@ -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
|
||||
|
795
lnrpc/rpc.pb.go
795
lnrpc/rpc.pb.go
@ -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)
|
||||
}
|
||||
|
163
nursery_store.go
163
nursery_store.go
@ -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
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)
|
||||
}
|
||||
}
|
32
rpcserver.go
32
rpcserver.go
@ -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)
|
||||
|
38
server.go
38
server.go
@ -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
|
||||
|
1971
utxonursery.go
1971
utxonursery.go
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user