From bafea35a32897086a6919db3e9eb5be4c3d8399f Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Thu, 4 May 2017 15:19:21 -0700 Subject: [PATCH] channeldb: expand channel close summaries to include additional data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit expands the close summaries within the database to include additional information such as: how the channel was closed, if the channel if fully closed, and other balance information. This new information will be used in an upcoming expansion of the pendingChannels RPC to display more comprehensive data w.r.t where the node’s funds are and how soon they’ll be fully available. The data stored in the closeChannel bucket has been expanded to include the above. Additionally the CloseChannel method on the OpenChannel struct now takes in the additional information that details how and who it was closed by. This commit borrows heavily from the initial work in #179 by @halseth. --- channeldb/channel.go | 202 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 192 insertions(+), 10 deletions(-) diff --git a/channeldb/channel.go b/channeldb/channel.go index 2c72f62e..bd75014a 100644 --- a/channeldb/channel.go +++ b/channeldb/channel.go @@ -2,6 +2,7 @@ package channeldb import ( "bytes" + "encoding/binary" "fmt" "io" "net" @@ -11,6 +12,7 @@ import ( "github.com/boltdb/bolt" "github.com/lightningnetwork/lnd/shachain" "github.com/roasbeef/btcd/btcec" + "github.com/roasbeef/btcd/chaincfg/chainhash" "github.com/roasbeef/btcd/wire" "github.com/roasbeef/btcutil" ) @@ -650,11 +652,71 @@ func (c *OpenChannel) FindPreviousState(updateNum uint64) (*ChannelDelta, error) return delta, nil } +// ClosureType is an enum like structure that details exactly _how_ a channel +// was closed. Three closure types are currently possible: cooperative, force, +// and breach. +type ClosureType uint8 + +const ( + // Cooperative indicates that a channel has been closed cooperatively. + // This means that both channel peers were online and signed a new + // transaction paying out the settled balance of the contract. + CooperativeClose ClosureType = iota + + // Force indicates that one peer unilaterally broadcast their current + // commitment state on-chain. + ForceClose + + // Beach indicates that one peer attempted to broadcast a prior + // _revoked_ channel state. + BreachClose +) + +// ChannelCloseSummary contains the final state of a channel at the point it +// was close. Once a channel is closed, all the information pertaining to that +// channel within the openChannelBucket is deleted, and a compact summary is +// but in place instead. +type ChannelCloseSummary struct { + // ChanPoint is the outpoint for this channel's funding transaction, + // and is used as a unique identifier for the channel. + ChanPoint wire.OutPoint + + // ClosingTXID is the txid of the transaction which ultimately closed + // this channel. + ClosingTXID chainhash.Hash + + // RemotePub is the public key of the remote peer that we formerly had + // a channel with. + RemotePub *btcec.PublicKey + + // Capacity was the total capacity of the channel. + Capacity btcutil.Amount + + // OurBalance is our total balance settled balance at the time of + // channel closure. + OurBalance btcutil.Amount + + // CloseType details exactly _how_ the channel was closed. Three + // closure types are possible: cooperative, force, and breach. + CloseType ClosureType + + // IsPending indicates whether this channel is in the 'pending close' + // state, which means the channel closing transaction has been + // broadcast, but not confirmed yet or has not yet been fully resolved. + // In the case of a channel that has been cooperatively closed, it will + // no longer be considered pending as soon as the closing transaction + // has been confirmed. However, for channel that have been force + // closed, they'll stay marked as "pending" until _all_ the pending + // funds have been swept. + IsPending bool +} + // CloseChannel closes a previously active lightning channel. Closing a channel // entails deleting all saved state within the database concerning this -// channel, as well as created a small channel summary for record keeping -// purposes. -func (c *OpenChannel) CloseChannel() error { +// channel. This method also takes a struct that summarizes the state of the +// channel at closing, this compact representation will be the only component +// of a channel left over after a full closing. +func (c *OpenChannel) CloseChannel(summary *ChannelCloseSummary) error { return c.Db.Update(func(tx *bolt.Tx) error { // First fetch the top level bucket which stores all data // related to current, active channels. @@ -716,7 +778,7 @@ func (c *OpenChannel) CloseChannel() error { // Finally, create a summary of this channel in the closed // channel bucket for this node. - return putClosedChannelSummary(tx, outPointBytes) + return putChannelCloseSummary(tx, outPointBytes, summary) }) } @@ -768,17 +830,109 @@ func (c *OpenChannel) Snapshot() *ChannelSnapshot { return snapshot } -func putClosedChannelSummary(tx *bolt.Tx, chanID []byte) error { - // For now, a summary of a closed channel simply involves recording the - // outpoint of the funding transaction. +func putChannelCloseSummary(tx *bolt.Tx, chanID []byte, + summary *ChannelCloseSummary) error { + closedChanBucket, err := tx.CreateBucketIfNotExists(closedChannelBucket) if err != nil { return err } - // TODO(roasbeef): add other info - // * should likely have each in own bucket per node - return closedChanBucket.Put(chanID, nil) + var b bytes.Buffer + if err := serializeChannelCloseSummary(&b, summary); err != nil { + return err + } + + return closedChanBucket.Put(chanID, b.Bytes()) +} + +func serializeChannelCloseSummary(w io.Writer, cs *ChannelCloseSummary) error { + if err := writeBool(w, cs.IsPending); err != nil { + return err + } + + if err := writeOutpoint(w, &cs.ChanPoint); err != nil { + return err + } + if _, err := w.Write(cs.ClosingTXID[:]); err != nil { + return err + } + + if err := binary.Write(w, byteOrder, cs.OurBalance); err != nil { + return err + } + if err := binary.Write(w, byteOrder, cs.Capacity); err != nil { + return err + } + + if _, err := w.Write([]byte{byte(cs.CloseType)}); err != nil { + return err + } + + pub := cs.RemotePub.SerializeCompressed() + if _, err := w.Write(pub); err != nil { + return err + } + + return nil +} + +func fetchChannelCloseSummary(tx *bolt.Tx, + chanID []byte) (*ChannelCloseSummary, error) { + + closedChanBucket, err := tx.CreateBucketIfNotExists(closedChannelBucket) + if err != nil { + return nil, err + } + + summaryBytes := closedChanBucket.Get(chanID) + if summaryBytes == nil { + return nil, fmt.Errorf("closed channel summary not found") + } + + summaryReader := bytes.NewReader(summaryBytes) + return deserializeCloseChannelSummary(summaryReader) +} + +func deserializeCloseChannelSummary(r io.Reader) (*ChannelCloseSummary, error) { + c := &ChannelCloseSummary{} + + var err error + c.IsPending, err = readBool(r) + if err != nil { + return nil, err + } + + if err := readOutpoint(r, &c.ChanPoint); err != nil { + return nil, err + } + if _, err := io.ReadFull(r, c.ClosingTXID[:]); err != nil { + return nil, err + } + + if err := binary.Read(r, byteOrder, &c.OurBalance); err != nil { + return nil, err + } + if err := binary.Read(r, byteOrder, &c.Capacity); err != nil { + return nil, err + } + + var closeType [1]byte + if _, err := io.ReadFull(r, closeType[:]); err != nil { + return nil, err + } + c.CloseType = ClosureType(closeType[0]) + + var pub [33]byte + if _, err := io.ReadFull(r, pub[:]); err != nil { + return nil, err + } + c.RemotePub, err = btcec.ParsePubKey(pub[:], btcec.S256()) + if err != nil { + return nil, err + } + + return c, nil } // putChannel serializes, and stores the current state of the channel in its @@ -2045,3 +2199,31 @@ func readOutpoint(r io.Reader, o *wire.OutPoint) error { return nil } + +func writeBool(w io.Writer, b bool) error { + boolByte := byte(0x01) + if !b { + boolByte = byte(0x00) + } + + if _, err := w.Write([]byte{boolByte}); err != nil { + return err + } + + return nil +} + +// TODO(roasbeef): make sweep to use this and above everywhere +// * using this because binary.Write can only handle bools post go1.8 +func readBool(r io.Reader) (bool, error) { + var boolByte [1]byte + if _, err := io.ReadFull(r, boolByte[:]); err != nil { + return false, err + } + + if boolByte[0] == 0x00 { + return false, nil + } + + return true, nil +}