From a5113c2439279677f6caf2391e4301e372dd9aac Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 10 May 2017 16:44:40 -0700 Subject: [PATCH] channeldb+funding: track opening height using MarkChannelAsOpen This commit modifies the OpenChannel structure on-disk to also track that opening height of a channel. This change is being made in order to make and more light client friendly. A follow up commit will modify several areas of the codebase to use this new functionality. --- channeldb/channel.go | 63 +++++++++++++++++++++++++++++++++++---- channeldb/channel_test.go | 18 +++++++++-- channeldb/db.go | 29 ++++++++++++------ fundingmanager.go | 3 +- 4 files changed, 96 insertions(+), 17 deletions(-) diff --git a/channeldb/channel.go b/channeldb/channel.go index 17994b07..1a01928f 100644 --- a/channeldb/channel.go +++ b/channeldb/channel.go @@ -70,6 +70,7 @@ var ( satReceivedPrefix = []byte("srp") netFeesPrefix = []byte("ntp") isPendingPrefix = []byte("pdg") + openHeightPrefix = []byte("open-height-prefix") // chanIDKey stores the node, and channelID for an active channel. chanIDKey = []byte("cik") @@ -129,6 +130,10 @@ const ( // to an on-disk log, which can then subsequently be queried in order to // "time-travel" to a prior state. type OpenChannel struct { + // OpeningHeight is the height in which this channel was officially + // marked open. + OpeningHeight uint32 + // IdentityPub is the identity public key of the remote node this // channel has been established with. IdentityPub *btcec.PublicKey @@ -356,17 +361,18 @@ func (c *OpenChannel) SyncPending(addr *net.TCPAddr) error { return err } - // If a LinkNode for this identity public key already exsits, then - // we can exit early. + // If a LinkNode for this identity public key already exists, + // then we can exit early. nodePub := c.IdentityPub.SerializeCompressed() if nodeInfoBucket.Get(nodePub) != nil { return nil } // Next, we need to establish a (possibly) new LinkNode - // relationship for this channel. The LinkNode metadata contains - // reachability, up-time, and service bits related information. - // TODO(roasbeef): net info shuld be in lnwire.NetAddress + // relationship for this channel. The LinkNode metadata + // contains reachability, up-time, and service bits related + // information. + // TODO(roasbeef): net info should be in lnwire.NetAddress linkNode := c.Db.NewLinkNode(wire.MainNet, c.IdentityPub, addr) return putLinkNode(nodeInfoBucket, linkNode) @@ -966,6 +972,9 @@ func putOpenChannel(openChanBucket *bolt.Bucket, nodeChanBucket *bolt.Bucket, if err := putChanIsPending(openChanBucket, channel); err != nil { return err } + if err := putChanOpenHeight(openChanBucket, channel); err != nil { + return err + } // Next, write out the fields of the channel update less frequently. if err := putChannelIDs(nodeChanBucket, channel); err != nil { @@ -1053,6 +1062,9 @@ func fetchOpenChannel(openChanBucket *bolt.Bucket, nodeChanBucket *bolt.Bucket, if err = fetchChanIsPending(openChanBucket, channel); err != nil { return nil, err } + if err := fetchChanOpenHeight(openChanBucket, channel); err != nil { + return nil, err + } return channel, nil } @@ -1083,6 +1095,9 @@ func deleteOpenChannel(openChanBucket *bolt.Bucket, nodeChanBucket *bolt.Bucket, if err := deleteChanIsPending(openChanBucket, channelID); err != nil { return err } + if err := deleteChanOpenHeight(openChanBucket, channelID); err != nil { + return err + } // Finally, delete all the fields directly within the node's channel // bucket. @@ -1447,6 +1462,44 @@ func fetchChanIsPending(openChanBucket *bolt.Bucket, channel *OpenChannel) error return nil } +func putChanOpenHeight(openChanBucket *bolt.Bucket, channel *OpenChannel) error { + var b bytes.Buffer + if err := writeOutpoint(&b, channel.ChanID); err != nil { + return err + } + + keyPrefix := make([]byte, 3+b.Len()) + copy(keyPrefix[3:], b.Bytes()) + copy(keyPrefix[:3], openHeightPrefix) + + var scratch [4]byte + byteOrder.PutUint32(scratch[:], channel.OpeningHeight) + return openChanBucket.Put(keyPrefix, scratch[:]) +} + +func fetchChanOpenHeight(openChanBucket *bolt.Bucket, channel *OpenChannel) error { + var b bytes.Buffer + if err := writeOutpoint(&b, channel.ChanID); err != nil { + return err + } + + keyPrefix := make([]byte, 3+b.Len()) + copy(keyPrefix[3:], b.Bytes()) + copy(keyPrefix[:3], openHeightPrefix) + + openHeightBytes := openChanBucket.Get(keyPrefix) + channel.OpeningHeight = byteOrder.Uint32(openHeightBytes) + + return nil +} + +func deleteChanOpenHeight(openChanBucket *bolt.Bucket, chanID []byte) error { + keyPrefix := make([]byte, 3+len(chanID)) + copy(keyPrefix[3:], chanID) + copy(keyPrefix[:3], openHeightPrefix) + return openChanBucket.Delete(keyPrefix) +} + func putChannelIDs(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error { // TODO(roasbeef): just pass in chanID everywhere for puts var b bytes.Buffer diff --git a/channeldb/channel_test.go b/channeldb/channel_test.go index c9f4b9b5..99413b4f 100644 --- a/channeldb/channel_test.go +++ b/channeldb/channel_test.go @@ -666,10 +666,23 @@ func TestFetchPendingChannels(t *testing.T) { "got %v", 1, len(pendingChannels)) } - if err := cdb.MarkChannelAsOpen(pendingChannels[0].ChanID); err != nil { + const openHeight = 100 + err = cdb.MarkChannelAsOpen(pendingChannels[0].ChanID, openHeight) + if err != nil { t.Fatalf("unable to mark channel as open: %v", err) } + // Next, we'll re-fetch the channel to ensure that the open height was + // properly set. + openChans, err := cdb.FetchAllChannels() + if err != nil { + t.Fatalf("unable to fetch channels: %v", err) + } + if openChans[0].OpeningHeight != openHeight { + t.Fatalf("channel opening heights don't match: expected %v, "+ + "got %v", openChans[0].OpeningHeight, openHeight) + } + pendingChannels, err = cdb.FetchPendingChannels() if err != nil { t.Fatalf("unable to list pending channels: %v", err) @@ -707,7 +720,8 @@ func TestFetchClosedChannels(t *testing.T) { // Next, simulate the confirmation of the channel by marking it as // pending within the database. - if err := cdb.MarkChannelAsOpen(state.ChanID); err != nil { + const openHeight = 100 + if err := cdb.MarkChannelAsOpen(state.ChanID, openHeight); err != nil { t.Fatalf("unable to mark channel as open: %v", err) } diff --git a/channeldb/db.go b/channeldb/db.go index 24b32e6d..5b5d9f89 100644 --- a/channeldb/db.go +++ b/channeldb/db.go @@ -344,6 +344,7 @@ func fetchChannels(d *DB, pendingOnly bool) ([]*OpenChannel, error) { return fmt.Errorf("unable to read channel for "+ "node_key=%x: %v", k, err) } + // TODO(roasbeef): simplify if pendingOnly { for _, channel := range nodeChannels { if channel.IsPending { @@ -361,16 +362,17 @@ func fetchChannels(d *DB, pendingOnly bool) ([]*OpenChannel, error) { } // MarkChannelAsOpen records the finalization of the funding process and marks -// a channel as available for use. -func (d *DB) MarkChannelAsOpen(outpoint *wire.OutPoint) error { +// a channel as available for use. Additionally the height in which this +// channel as opened will also be recorded within the database. +func (d *DB) MarkChannelAsOpen(outpoint *wire.OutPoint, openHeight uint32) error { return d.Update(func(tx *bolt.Tx) error { openChanBucket := tx.Bucket(openChannelBucket) if openChanBucket == nil { return ErrNoActiveChannels } - // Generate the database key, which will consist of the IsPending - // prefix followed by the channel's outpoint. + // Generate the database key, which will consist of the + // IsPending prefix followed by the channel's outpoint. var b bytes.Buffer if err := writeOutpoint(&b, outpoint); err != nil { return err @@ -379,11 +381,20 @@ func (d *DB) MarkChannelAsOpen(outpoint *wire.OutPoint) error { copy(keyPrefix[3:], b.Bytes()) copy(keyPrefix[:3], isPendingPrefix) - // For the database value, store a zero, since the channel is no - // longer pending. - scratch := make([]byte, 2) - byteOrder.PutUint16(scratch, uint16(0)) - return openChanBucket.Put(keyPrefix, scratch) + // For the database value, store a zero, since the channel is + // no longer pending. + scratch := make([]byte, 4) + byteOrder.PutUint16(scratch[:2], uint16(0)) + if err := openChanBucket.Put(keyPrefix, scratch[:2]); err != nil { + return err + } + + // Finally, we'll also store the opening height for this + // channel as well. + byteOrder.PutUint32(scratch, openHeight) + copy(keyPrefix[:3], openHeightPrefix) + + return openChanBucket.Put(keyPrefix, scratch[:]) }) } diff --git a/fundingmanager.go b/fundingmanager.go index 4c89c2df..565288e8 100644 --- a/fundingmanager.go +++ b/fundingmanager.go @@ -922,7 +922,8 @@ func (f *fundingManager) waitForFundingConfirmation(completeChan *channeldb.Open // Now that the channel has been fully confirmed, we'll mark it as open // within the database. completeChan.IsPending = false - err = f.cfg.Wallet.ChannelDB.MarkChannelAsOpen(&fundingPoint) + err = f.cfg.Wallet.ChannelDB.MarkChannelAsOpen(&fundingPoint, + confDetails.BlockHeight) if err != nil { fndgLog.Errorf("error setting channel pending flag to false: "+ "%v", err)