channeldb: added isPending flag and queries

In order to facilitate persistence during the funding process, added
the isPending flag to channels so that when the daemon restarts, we can
properly re-initialize the chain notifier and update the state of
channels that were going through the funding process.
This commit is contained in:
bryanvu authored and Olaoluwa Osuntokun committed 2017-02-24 11:37:33 -08:00
1 parent 9034f85b88
commit 3e02ea11ef
7 files changed
+239 -23

No files matched your search

+71 -7
View File
@@ -67,6 +67,7 @@ var (
satSentPrefix = []byte("ssp")
satReceivedPrefix = []byte("srp")
netFeesPrefix = []byte("ntp")
isPendingPrefix = []byte("pdg")
// chanIDKey stores the node, and channelID for an active channel.
chanIDKey = []byte("cik")
@@ -194,6 +195,10 @@ type OpenChannel struct {
// negotiate fees, or close the channel.
IsInitiator bool
// IsPending indicates whether a channel's funding transaction has been
// confirmed.
IsPending bool
// FundingOutpoint is the outpoint of the final funding transaction.
FundingOutpoint *wire.OutPoint
@@ -318,16 +323,18 @@ func (c *OpenChannel) fullSync(tx *bolt.Tx) error {
return putOpenChannel(chanBucket, nodeChanBucket, c)
}
// FullSyncWithAddr is identical to the FullSync function in that it writes the
// full channel state to disk. Additionally, this function also creates a
// LinkNode relationship between this newly created channel and an existing of
// new LinkNode instance. Syncing with this method rather than FullSync is
// required in order to allow listing all channels in the database globally, or
// according to the LinkNode they were created with.
// SyncPending writes the contents of the channel to the database while it's in
// the pending (waiting for funding confirmation) state. The IsPending flag
// will be set to true. When the channel's funding transaction is confirmed,
// the channel should be marked as "open" and the IsPending flag set to false.
// Note that this function also creates a LinkNode relationship between this
// newly created channel and a new LinkNode instance. This allows listing all
// channels in the database globally, or according to the LinkNode they were
// created with.
//
// TODO(roasbeef): addr param should eventually be a lnwire.NetAddress type
// that includes service bits.
func (c *OpenChannel) FullSyncWithAddr(addr *net.TCPAddr) error {
func (c *OpenChannel) SyncPending(addr *net.TCPAddr) error {
c.Lock()
defer c.Unlock()
@@ -732,6 +739,9 @@ func putOpenChannel(openChanBucket *bolt.Bucket, nodeChanBucket *bolt.Bucket,
if err := putChanAmountsTransferred(openChanBucket, channel); err != nil {
return err
}
if err := putChanIsPending(openChanBucket, channel); err != nil {
return err
}
// Next, write out the fields of the channel update less frequently.
if err := putChannelIDs(nodeChanBucket, channel); err != nil {
@@ -816,6 +826,9 @@ func fetchOpenChannel(openChanBucket *bolt.Bucket, nodeChanBucket *bolt.Bucket,
if err = fetchChanAmountsTransferred(openChanBucket, channel); err != nil {
return nil, fmt.Errorf("unable to read sat transferred: %v", err)
}
if err = fetchChanIsPending(openChanBucket, channel); err != nil {
return nil, err
}
return channel, nil
}
@@ -843,6 +856,9 @@ func deleteOpenChannel(openChanBucket *bolt.Bucket, nodeChanBucket *bolt.Bucket,
if err := deleteChanOurDustLimit(openChanBucket, channelID); err != nil {
return err
}
if err := deleteChanIsPending(openChanBucket, channelID); err != nil {
return err
}
// Finally, delete all the fields directly within the node's channel
// bucket.
@@ -1159,6 +1175,54 @@ func fetchChanAmountsTransferred(openChanBucket *bolt.Bucket, channel *OpenChann
return nil
}
func putChanIsPending(openChanBucket *bolt.Bucket, channel *OpenChannel) error {
scratch := make([]byte, 2)
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], isPendingPrefix)
if channel.IsPending {
byteOrder.PutUint16(scratch, uint16(1))
return openChanBucket.Put(keyPrefix, scratch)
}
byteOrder.PutUint16(scratch, uint16(0))
return openChanBucket.Put(keyPrefix, scratch)
}
func deleteChanIsPending(openChanBucket *bolt.Bucket, chanID []byte) error {
keyPrefix := make([]byte, 3+len(chanID))
copy(keyPrefix[3:], chanID)
copy(keyPrefix[:3], isPendingPrefix)
return openChanBucket.Delete(keyPrefix)
}
func fetchChanIsPending(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], isPendingPrefix)
isPending := byteOrder.Uint16(openChanBucket.Get(keyPrefix))
if isPending == 1 {
channel.IsPending = true
} else {
channel.IsPending = false
}
return nil
}
func putChannelIDs(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error {
// TODO(roasbeef): just pass in chanID everywhere for puts
var b bytes.Buffer
+49
View File
@@ -3,6 +3,7 @@ package channeldb
import (
"bytes"
"io/ioutil"
"net"
"os"
"reflect"
"testing"
@@ -136,6 +137,7 @@ func createTestChannelState(cdb *DB) (*OpenChannel, error) {
return &OpenChannel{
IsInitiator: true,
IsPending: true,
ChanType: SingleFunder,
IdentityPub: pubKey,
ChanID: id,
@@ -583,3 +585,50 @@ func TestChannelStateTransition(t *testing.T) {
t.Fatal("revocation log search should've failed")
}
}
func TestFetchPendingChannels(t *testing.T) {
cdb, cleanUp, err := makeTestDB()
if err != nil {
t.Fatalf("uanble to make test database: %v", err)
}
defer cleanUp()
// Create first test channel state
state, err := createTestChannelState(cdb)
if err != nil {
t.Fatalf("unable to create channel state: %v", err)
}
addr := &net.TCPAddr{
IP: net.ParseIP("127.0.0.1"),
Port: 18555,
}
if err := state.SyncPending(addr); err != nil {
t.Fatalf("unable to save and serialize channel state: %v", err)
}
pendingChannels, err := cdb.FetchPendingChannels()
if err != nil {
t.Fatalf("unable to list pending channels: %v", err)
}
if len(pendingChannels) != 1 {
t.Fatalf("incorrect number of pending channels: expecting %v,"+
"got %v", 1, len(pendingChannels))
}
if err := cdb.MarkChannelAsOpen(pendingChannels[0].ChanID); err != nil {
t.Fatalf("unable to mark channel as open: %v", err)
}
pendingChannels, err = cdb.FetchPendingChannels()
if err != nil {
t.Fatalf("unable to list pending channels: %v", err)
}
if len(pendingChannels) != 0 {
t.Fatalf("incorrect number of pending channels: expecting %v,"+
"got %v", 0, len(pendingChannels))
}
}
+57 -4
View File
@@ -295,9 +295,23 @@ func (d *DB) fetchNodeChannels(openChanBucket,
}
// FetchAllChannels attempts to retrieve all open channels currently stored
// within the database. If no active channels exist within the network, then
// ErrNoActiveChannels is returned.
// within the database.
func (d *DB) FetchAllChannels() ([]*OpenChannel, error) {
return fetchChannels(d, false)
}
// FetchPendingChannels will return channels that have completed the process
// of generating and broadcasting funding transactions, but whose funding
// transactions have yet to be confirmed on the blockchain.
func (d *DB) FetchPendingChannels() ([]*OpenChannel, error) {
return fetchChannels(d, true)
}
// fetchChannels attempts to retrieve channels currently stored in the
// database. The pendingOnly parameter determines whether only pending
// channels will be returned. If no active channels exist within the network,
// then ErrNoActiveChannels is returned.
func fetchChannels(d *DB, pendingOnly bool) ([]*OpenChannel, error) {
var channels []*OpenChannel
err := d.View(func(tx *bolt.Tx) error {
@@ -330,8 +344,15 @@ func (d *DB) FetchAllChannels() ([]*OpenChannel, error) {
return fmt.Errorf("unable to read channel for "+
"node_key=%x: %v", k, err)
}
channels = append(channels, nodeChannels...)
if pendingOnly {
for _, channel := range nodeChannels {
if channel.IsPending == true {
channels = append(channels, channel)
}
}
} else {
channels = append(channels, nodeChannels...)
}
return nil
})
})
@@ -339,6 +360,38 @@ func (d *DB) FetchAllChannels() ([]*OpenChannel, error) {
return channels, err
}
// MarkChannelAsOpen records the finalization of the funding process and marks
// a channel as available for use.
func (d *DB) MarkChannelAsOpen(outpoint *wire.OutPoint) error {
err := 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.
var b bytes.Buffer
if err := writeOutpoint(&b, outpoint); err != nil {
return err
}
keyPrefix := make([]byte, 3+b.Len())
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)
})
if err != nil {
return err
}
return nil
}
// syncVersions function is used for safe db version synchronization. It applies
// migration functions to the current database and recovers the previous
// state of db if at least one error/panic appeared during migration.