From 1f34f117ff015f272fafd19829be24f97babc2de Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Wed, 8 Nov 2017 19:27:45 -0800 Subject: [PATCH 01/13] lnrpc: adds PendingHTLCs to ForceClosedChannels --- lnrpc/rpc.pb.go | 795 +++++++++++++++++++++++------------------ lnrpc/rpc.pb.gw.go | 20 +- lnrpc/rpc.proto | 40 ++- lnrpc/rpc.swagger.json | 47 ++- 4 files changed, 543 insertions(+), 359 deletions(-) diff --git a/lnrpc/rpc.pb.go b/lnrpc/rpc.pb.go index 4ddafa6a..315ac975 100644 --- a/lnrpc/rpc.pb.go +++ b/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, } diff --git a/lnrpc/rpc.pb.gw.go b/lnrpc/rpc.pb.gw.go index a652f0e2..ebf026f6 100644 --- a/lnrpc/rpc.pb.gw.go +++ b/lnrpc/rpc.pb.gw.go @@ -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) diff --git a/lnrpc/rpc.proto b/lnrpc/rpc.proto index d24988e1..3ecab04c 100644 --- a/lnrpc/rpc.proto +++ b/lnrpc/rpc.proto @@ -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 diff --git a/lnrpc/rpc.swagger.json b/lnrpc/rpc.swagger.json index e3298fa8..2cccd97b 100644 --- a/lnrpc/rpc.swagger.json +++ b/lnrpc/rpc.swagger.json @@ -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": { From 23343c0700c894ab07f31f34b6fa2fec13226aa9 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Wed, 8 Nov 2017 23:08:13 -0800 Subject: [PATCH 02/13] rpcserver: adds signed blocks til maturity in pending channels --- rpcserver.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rpcserver.go b/rpcserver.go index 65bd1cd6..cb642b30 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -1246,8 +1246,9 @@ func (r *rpcServer) PendingChannels(ctx context.Context, // 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 } resp.TotalLimboBalance += int64(nurseryInfo.limboBalance) From 7888c6e04077ba7256d0be4b5adf880b6966a7f0 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Sun, 1 Oct 2017 20:54:23 -0700 Subject: [PATCH 03/13] lnwallet/witnessgen: add HtlcOfferedTimeLock witness type --- lnwallet/witnessgen.go | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/lnwallet/witnessgen.go b/lnwallet/witnessgen.go index 434bff4e..c48b7f90 100644 --- a/lnwallet/witnessgen.go +++ b/lnwallet/witnessgen.go @@ -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) } From 305acb86d00603bf0bd255b8b2afad45ec57c0ea Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Tue, 10 Oct 2017 01:36:06 -0700 Subject: [PATCH 04/13] lnwallet/size: adds htlc success and timeout witness sizes --- lnwallet/size.go | 49 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/lnwallet/size.go b/lnwallet/size.go index 75b9e4cd..c570cebe 100644 --- a/lnwallet/size.go +++ b/lnwallet/size.go @@ -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 From 35e3201097f204ba49f76dc210572a0356258ab1 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Sun, 1 Oct 2017 20:55:39 -0700 Subject: [PATCH 05/13] lnwallet/script_utils: adds public HtlcSpendSuccess witness func --- lnwallet/script_utils.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/lnwallet/script_utils.go b/lnwallet/script_utils.go index 18efdd01..ef0fc829 100644 --- a/lnwallet/script_utils.go +++ b/lnwallet/script_utils.go @@ -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. From 997a37ca23f931e11f9d8c15e0073249b5b80424 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 6 Oct 2017 16:44:05 -0700 Subject: [PATCH 06/13] utxonursery: integrate nursery store --- utxonursery.go | 1971 +++++++++++++++++++++++++++++------------------- 1 file changed, 1207 insertions(+), 764 deletions(-) diff --git a/utxonursery.go b/utxonursery.go index 2bad107f..0051756f 100644 --- a/utxonursery.go +++ b/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 } From 2859956cd20308d4ec3718aeb4cd37dacb174590 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Sun, 8 Oct 2017 18:15:34 -0700 Subject: [PATCH 07/13] utxonursery_test: extends test vectors for nursery store --- utxonursery_test.go | 102 +++++++++++++++++++++++++++----------------- 1 file changed, 63 insertions(+), 39 deletions(-) diff --git a/utxonursery_test.go b/utxonursery_test.go index 2d7b9560..3727e0ce 100644 --- a/utxonursery_test.go +++ b/utxonursery_test.go @@ -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) { From 2ef821ed9ada6eef0ae43c92319797609577be17 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 6 Oct 2017 16:58:13 -0700 Subject: [PATCH 08/13] lnd_test: extends force closure test to test for outgoing htlc incubation --- lnd_test.go | 748 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 644 insertions(+), 104 deletions(-) diff --git a/lnd_test.go b/lnd_test.go index 4eff33f6..56b95f2f 100644 --- a/lnd_test.go +++ b/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 From ed0c3dbc58f33732ab185476f273be624e3ecb26 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Tue, 7 Nov 2017 18:06:55 -0800 Subject: [PATCH 09/13] nursery_store: tracks last graduated and adds HeightsBelowOrEqual This commit removes the use of a purge height from the nursery store, instead tracking the last graduated height. This serves to indicate the last height that the nursery store successfully graduated. It also adds a method for retrieving the set of all active heights below a particular threshold, e.g. the last graduated height, allowing the utxo nursery to replay these heights on startup for the specific purpose of re-registering for confirmations of outputs in the height index. --- nursery_store.go | 163 ++++++++++++++++++++++++++--------------------- 1 file changed, 90 insertions(+), 73 deletions(-) diff --git a/nursery_store.go b/nursery_store.go index 5d694aba..90fd359d 100644 --- a/nursery_store.go +++ b/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-finalized-height-key: +// ├── last-graduated-height-key: // | // | 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 From 82f4e7c038631e13c293e77cc35581acaa8cf807 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Sun, 22 Oct 2017 00:23:49 -0700 Subject: [PATCH 10/13] nursery_store_test: adds basic tests for state changes --- nursery_store_test.go | 754 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 754 insertions(+) create mode 100644 nursery_store_test.go diff --git a/nursery_store_test.go b/nursery_store_test.go new file mode 100644 index 00000000..1fce278b --- /dev/null +++ b/nursery_store_test.go @@ -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) + } +} From 2b74a6549f419d73689be8e82b20ba43a7aac81e Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Sun, 1 Oct 2017 20:39:40 -0700 Subject: [PATCH 11/13] server: instantiate utxon with NurseryConfig --- server.go | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/server.go b/server.go index 403a3e70..4df5f2bb 100644 --- a/server.go +++ b/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 From eee4c225bf2c02d34f595731a5150e28f77ab64f Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 12 Oct 2017 22:35:39 -0700 Subject: [PATCH 12/13] rpcserver: check for errors returned from incubation --- rpcserver.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rpcserver.go b/rpcserver.go index cb642b30..42505167 100644 --- a/rpcserver.go +++ b/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 } From 5a2769664aa902d0c8a2df9fe666799fb9ba1f5b Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Wed, 8 Nov 2017 19:27:13 -0800 Subject: [PATCH 13/13] rpcserver: populate pending channel resp with htlcs --- rpcserver.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/rpcserver.go b/rpcserver.go index 42505167..ef4666d6 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -1243,6 +1243,7 @@ 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 @@ -1253,6 +1254,28 @@ func (r *rpcServer) PendingChannels(ctx context.Context, 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) }