From 1c0dc98a7cd59747a1fca50a239d75f436186cfa Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Wed, 4 Dec 2019 13:29:30 -0800 Subject: [PATCH] channeldb: differentiate force vs coop close --- channeldb/channel.go | 60 ++++++++++++++++++++++++++++++++++----- channeldb/channel_test.go | 36 ++++++++++++++++++----- 2 files changed, 82 insertions(+), 14 deletions(-) diff --git a/channeldb/channel.go b/channeldb/channel.go index 1fdd97e9..8a67b8a4 100644 --- a/channeldb/channel.go +++ b/channeldb/channel.go @@ -59,9 +59,13 @@ var ( // remote peer during a channel sync in case we have lost channel state. dataLossCommitPointKey = []byte("data-loss-commit-point-key") - // closingTxKey points to a the closing tx that we broadcasted when - // moving the channel to state CommitBroadcasted. - closingTxKey = []byte("closing-tx-key") + // forceCloseTxKey points to a the unilateral closing tx that we + // broadcasted when moving the channel to state CommitBroadcasted. + forceCloseTxKey = []byte("closing-tx-key") + + // coopCloseTxKey points to a the cooperative closing tx that we + // broadcasted when moving the channel to state CoopBroadcasted. + coopCloseTxKey = []byte("coop-closing-tx-key") // commitDiffKey stores the current pending commitment state we've // extended to the remote party (if any). Each time we propose a new @@ -361,6 +365,10 @@ var ( // has been restored, and doesn't have all the fields a typical channel // will have. ChanStatusRestored ChannelStatus = 1 << 3 + + // ChanStatusCoopBroadcasted indicates that a cooperative close for this + // channel has been broadcasted. + ChanStatusCoopBroadcasted ChannelStatus = 1 << 4 ) // chanStatusStrings maps a ChannelStatus to a human friendly string that @@ -371,6 +379,7 @@ var chanStatusStrings = map[ChannelStatus]string{ ChanStatusCommitBroadcasted: "ChanStatusCommitBroadcasted", ChanStatusLocalDataLoss: "ChanStatusLocalDataLoss", ChanStatusRestored: "ChanStatusRestored", + ChanStatusCoopBroadcasted: "ChanStatusCoopBroadcasted", } // orderedChanStatusFlags is an in-order list of all that channel status flags. @@ -380,6 +389,7 @@ var orderedChanStatusFlags = []ChannelStatus{ ChanStatusCommitBroadcasted, ChanStatusLocalDataLoss, ChanStatusRestored, + ChanStatusCoopBroadcasted, } // String returns a human-readable representation of the ChannelStatus. @@ -919,6 +929,30 @@ func (c *OpenChannel) isBorked(chanBucket *bbolt.Bucket) (bool, error) { // republish this tx at startup to ensure propagation, and we should still // handle the case where a different tx actually hits the chain. func (c *OpenChannel) MarkCommitmentBroadcasted(closeTx *wire.MsgTx) error { + return c.markBroadcasted( + ChanStatusCommitBroadcasted, forceCloseTxKey, closeTx, + ) +} + +// MarkCoopBroadcasted marks the channel to indicate that a cooperative close +// transaction has been broadcast, either our own or the remote, and that we +// should wach the chain for it to confirm before taking further action. It +// takes as argument a cooperative close tx that could appear on chain, and +// should be rebroadcast upon startup. This is only used to republish and ensure +// propagation, and we should still handle the case where a different tx +// actually hits the chain. +func (c *OpenChannel) MarkCoopBroadcasted(closeTx *wire.MsgTx) error { + return c.markBroadcasted( + ChanStatusCoopBroadcasted, coopCloseTxKey, closeTx, + ) +} + +// markBroadcasted is a helper function which modifies the channel status of the +// receiving channel and inserts a close transaction under the requested key, +// which should specify either a coop or force close. +func (c *OpenChannel) markBroadcasted(status ChannelStatus, key []byte, + closeTx *wire.MsgTx) error { + c.Lock() defer c.Unlock() @@ -928,15 +962,27 @@ func (c *OpenChannel) MarkCommitmentBroadcasted(closeTx *wire.MsgTx) error { } putClosingTx := func(chanBucket *bbolt.Bucket) error { - return chanBucket.Put(closingTxKey, b.Bytes()) + return chanBucket.Put(key, b.Bytes()) } - return c.putChanStatus(ChanStatusCommitBroadcasted, putClosingTx) + return c.putChanStatus(status, putClosingTx) } -// BroadcastedCommitment retrieves the stored closing tx set during +// BroadcastedCommitment retrieves the stored unilateral closing tx set during // MarkCommitmentBroadcasted. If not found ErrNoCloseTx is returned. func (c *OpenChannel) BroadcastedCommitment() (*wire.MsgTx, error) { + return c.getClosingTx(forceCloseTxKey) +} + +// BroadcastedCooperative retrieves the stored cooperative closing tx set during +// MarkCoopBroadcasted. If not found ErrNoCloseTx is returned. +func (c *OpenChannel) BroadcastedCooperative() (*wire.MsgTx, error) { + return c.getClosingTx(coopCloseTxKey) +} + +// getClosingTx is a helper method which returns the stored closing transaction +// for key. The caller should use either the force or coop closing keys. +func (c *OpenChannel) getClosingTx(key []byte) (*wire.MsgTx, error) { var closeTx *wire.MsgTx err := c.Db.View(func(tx *bbolt.Tx) error { @@ -951,7 +997,7 @@ func (c *OpenChannel) BroadcastedCommitment() (*wire.MsgTx, error) { return err } - bs := chanBucket.Get(closingTxKey) + bs := chanBucket.Get(key) if bs == nil { return ErrNoCloseTx } diff --git a/channeldb/channel_test.go b/channeldb/channel_test.go index 6d34c2f9..2d606356 100644 --- a/channeldb/channel_test.go +++ b/channeldb/channel_test.go @@ -900,6 +900,14 @@ func TestFetchWaitingCloseChannels(t *testing.T) { if err := channel.MarkCommitmentBroadcasted(closeTx); err != nil { t.Fatalf("unable to mark commitment broadcast: %v", err) } + + // Modify the close tx deterministically and also mark it as + // coop closed. Later we will test that distinct transactions + // are returned for both coop and force closes. + closeTx.TxIn[0].PreviousOutPoint.Index ^= 1 + if err := channel.MarkCoopBroadcasted(closeTx); err != nil { + t.Fatalf("unable to mark coop broadcast: %v", err) + } } // Now, we'll fetch all the channels waiting to be closed from the @@ -909,7 +917,7 @@ func TestFetchWaitingCloseChannels(t *testing.T) { if err != nil { t.Fatalf("unable to fetch all waiting close channels: %v", err) } - if len(waitingCloseChannels) != 2 { + if len(waitingCloseChannels) != numChannels { t.Fatalf("expected %d channels waiting to be closed, got %d", 2, len(waitingCloseChannels)) } @@ -923,17 +931,31 @@ func TestFetchWaitingCloseChannels(t *testing.T) { channel.FundingOutpoint) } - // Finally, make sure we can retrieve the closing tx for the - // channel. - closeTx, err := channel.BroadcastedCommitment() + chanPoint := channel.FundingOutpoint + + // Assert that the force close transaction is retrievable. + forceCloseTx, err := channel.BroadcastedCommitment() if err != nil { t.Fatalf("Unable to retrieve commitment: %v", err) } - if closeTx.TxIn[0].PreviousOutPoint != channel.FundingOutpoint { + if forceCloseTx.TxIn[0].PreviousOutPoint != chanPoint { t.Fatalf("expected outpoint %v, got %v", - channel.FundingOutpoint, - closeTx.TxIn[0].PreviousOutPoint) + chanPoint, + forceCloseTx.TxIn[0].PreviousOutPoint) + } + + // Assert that the coop close transaction is retrievable. + coopCloseTx, err := channel.BroadcastedCooperative() + if err != nil { + t.Fatalf("unable to retrieve coop close: %v", err) + } + + chanPoint.Index ^= 1 + if coopCloseTx.TxIn[0].PreviousOutPoint != chanPoint { + t.Fatalf("expected outpoint %v, got %v", + chanPoint, + coopCloseTx.TxIn[0].PreviousOutPoint) } } }