channeldb: add new frozen channel type bit and thaw height

In this commit, we add a new channel type bit: a frozen channel. A
frozen channel is one that can only be cooperatively closed by the
responder, but not the initiator. This channel type is useful for
certain classes of channel factory like protocols. We then add a new key
on the channel bucket level to store the height after which this
restriction no longer applies.
This commit is contained in:
Olaoluwa Osuntokun 2020-03-13 16:49:12 -07:00
parent 8fd5d13c75
commit 421d73b72e
No known key found for this signature in database
GPG Key ID: BC13F65E2DC84465
2 changed files with 82 additions and 1 deletions

@ -102,6 +102,11 @@ var (
// channel closure. This key should be accessed from within the // channel closure. This key should be accessed from within the
// sub-bucket of a target channel, identified by its channel point. // sub-bucket of a target channel, identified by its channel point.
revocationLogBucket = []byte("revocation-log-key") revocationLogBucket = []byte("revocation-log-key")
// frozenChanKey is the key where we store the information for any
// active "frozen" channels. This key is present only in the leaf
// bucket for a given channel.
frozenChanKey = []byte("frozen-chans")
) )
var ( var (
@ -191,6 +196,11 @@ const (
// channel type also uses a delayed to_remote output script. If bit is // channel type also uses a delayed to_remote output script. If bit is
// set, we'll find the size of the anchor outputs in the database. // set, we'll find the size of the anchor outputs in the database.
AnchorOutputsBit ChannelType = 1 << 3 AnchorOutputsBit ChannelType = 1 << 3
// FrozenBit indicates that the channel is a frozen channel, meaning
// that only the responder can decide to cooperatively close the
// channel.
FrozenBit ChannelType = 1 << 4
) )
// IsSingleFunder returns true if the channel type if one of the known single // IsSingleFunder returns true if the channel type if one of the known single
@ -222,6 +232,13 @@ func (c ChannelType) HasAnchors() bool {
return c&AnchorOutputsBit == AnchorOutputsBit return c&AnchorOutputsBit == AnchorOutputsBit
} }
// IsFrozen returns true if the channel is considered to be "frozen". A frozen
// channel means that only the responder can initiate a cooperative channel
// closure.
func (c ChannelType) IsFrozen() bool {
return c&FrozenBit == FrozenBit
}
// ChannelConstraints represents a set of constraints meant to allow a node to // ChannelConstraints represents a set of constraints meant to allow a node to
// limit their exposure, enact flow control and ensure that all HTLCs are // limit their exposure, enact flow control and ensure that all HTLCs are
// economically relevant. This struct will be mirrored for both sides of the // economically relevant. This struct will be mirrored for both sides of the
@ -635,6 +652,11 @@ type OpenChannel struct {
// was not set, the field is empty. // was not set, the field is empty.
RemoteShutdownScript lnwire.DeliveryAddress RemoteShutdownScript lnwire.DeliveryAddress
// ThawHeight is the height when a frozen channel once again becomes a
// normal channel. If this is zero, then there're no restrictions on
// this channel.
ThawHeight uint32
// TODO(roasbeef): eww // TODO(roasbeef): eww
Db *DB Db *DB
@ -1229,6 +1251,17 @@ func putOpenChannel(chanBucket kvdb.RwBucket, channel *OpenChannel) error {
return fmt.Errorf("unable to store chan commitments: %v", err) return fmt.Errorf("unable to store chan commitments: %v", err)
} }
// Next, if this is a frozen channel, we'll add in the axillary
// information we need to store.
if channel.ChanType.IsFrozen() {
err := storeThawHeight(
chanBucket, channel.ThawHeight,
)
if err != nil {
return fmt.Errorf("unable to store thaw height: %v", err)
}
}
// Finally, we'll write out the revocation state for both parties // Finally, we'll write out the revocation state for both parties
// within a distinct key space. // within a distinct key space.
if err := putChanRevocationState(chanBucket, channel); err != nil { if err := putChanRevocationState(chanBucket, channel); err != nil {
@ -1259,6 +1292,18 @@ func fetchOpenChannel(chanBucket kvdb.ReadBucket,
return nil, fmt.Errorf("unable to fetch chan commitments: %v", err) return nil, fmt.Errorf("unable to fetch chan commitments: %v", err)
} }
// Next, if this is a frozen channel, we'll add in the axillary
// information we need to store.
if channel.ChanType.IsFrozen() {
thawHeight, err := fetchThawHeight(chanBucket)
if err != nil {
return nil, fmt.Errorf("unable to store thaw "+
"height: %v", err)
}
channel.ThawHeight = thawHeight
}
// Finally, we'll retrieve the current revocation state so we can // Finally, we'll retrieve the current revocation state so we can
// properly // properly
if err := fetchChanRevocationState(chanBucket, channel); err != nil { if err := fetchChanRevocationState(chanBucket, channel); err != nil {
@ -2517,6 +2562,15 @@ func (c *OpenChannel) CloseChannel(summary *ChannelCloseSummary,
return err return err
} }
// We'll also remove the channel from the frozen channel bucket
// if we need to.
if c.ChanType.IsFrozen() {
err := deleteThawHeight(chanBucket)
if err != nil {
return err
}
}
// With the base channel data deleted, attempt to delete the // With the base channel data deleted, attempt to delete the
// information stored within the revocation log. // information stored within the revocation log.
logBucket := chanBucket.NestedReadWriteBucket(revocationLogBucket) logBucket := chanBucket.NestedReadWriteBucket(revocationLogBucket)
@ -3213,3 +3267,29 @@ func fetchChannelLogEntry(log kvdb.ReadBucket,
commitReader := bytes.NewReader(commitBytes) commitReader := bytes.NewReader(commitBytes)
return deserializeChanCommit(commitReader) return deserializeChanCommit(commitReader)
} }
func fetchThawHeight(chanBucket kvdb.ReadBucket) (uint32, error) {
var height uint32
heightBytes := chanBucket.Get(frozenChanKey)
heightReader := bytes.NewReader(heightBytes)
if err := ReadElements(heightReader, &height); err != nil {
return 0, err
}
return height, nil
}
func storeThawHeight(chanBucket kvdb.RwBucket, height uint32) error {
var heightBuf bytes.Buffer
if err := WriteElements(&heightBuf, height); err != nil {
return err
}
return chanBucket.Put(frozenChanKey, heightBuf.Bytes())
}
func deleteThawHeight(chanBucket kvdb.RwBucket) error {
return chanBucket.Delete(frozenChanKey)
}

@ -349,7 +349,7 @@ func createTestChannelState(t *testing.T, cdb *DB) *OpenChannel {
chanID := lnwire.NewShortChanIDFromInt(uint64(rand.Int63())) chanID := lnwire.NewShortChanIDFromInt(uint64(rand.Int63()))
return &OpenChannel{ return &OpenChannel{
ChanType: SingleFunderBit, ChanType: SingleFunderBit | FrozenBit,
ChainHash: key, ChainHash: key,
FundingOutpoint: wire.OutPoint{Hash: key, Index: rand.Uint32()}, FundingOutpoint: wire.OutPoint{Hash: key, Index: rand.Uint32()},
ShortChannelID: chanID, ShortChannelID: chanID,
@ -387,6 +387,7 @@ func createTestChannelState(t *testing.T, cdb *DB) *OpenChannel {
Db: cdb, Db: cdb,
Packager: NewChannelPackager(chanID), Packager: NewChannelPackager(chanID),
FundingTxn: testTx, FundingTxn: testTx,
ThawHeight: uint32(defaultPendingHeight),
} }
} }