contractcourt: record force and breach close initiator

This commit is contained in:
carla 2020-02-21 13:49:12 +02:00
parent c9915e027e
commit 4eb3036f67
No known key found for this signature in database
GPG Key ID: 4CA7FE54A6213C91
4 changed files with 202 additions and 14 deletions

@ -284,8 +284,11 @@ func newActiveChannelArbitrator(channel *channeldb.OpenChannel,
return chanMachine.ForceClose()
},
MarkCommitmentBroadcasted: channel.MarkCommitmentBroadcasted,
MarkChannelClosed: func(summary *channeldb.ChannelCloseSummary) error {
if err := channel.CloseChannel(summary); err != nil {
MarkChannelClosed: func(summary *channeldb.ChannelCloseSummary,
statuses ...channeldb.ChannelStatus) error {
err := channel.CloseChannel(summary, statuses...)
if err != nil {
return err
}
c.cfg.NotifyClosedChannel(summary.ChanPoint)

@ -985,7 +985,9 @@ func (c *chainWatcher) dispatchContractBreach(spendEvent *chainntnfs.SpendDetail
closeSummary.LastChanSyncMsg = chanSync
}
if err := c.cfg.chanState.CloseChannel(&closeSummary); err != nil {
if err := c.cfg.chanState.CloseChannel(
&closeSummary, channeldb.ChanStatusRemoteCloseInitiator,
); err != nil {
return err
}

@ -104,8 +104,10 @@ type ChannelArbitratorConfig struct {
// passed close summary. After this method successfully returns we can
// no longer expect to receive chain events for this channel, and must
// be able to recover from a failure without getting the close event
// again.
MarkChannelClosed func(*channeldb.ChannelCloseSummary) error
// again. It takes an optional channel status which will update the
// channel status in the record that we keep of historical channels.
MarkChannelClosed func(*channeldb.ChannelCloseSummary,
...channeldb.ChannelStatus) error
// IsPendingClose is a boolean indicating whether the channel is marked
// as pending close in the database.
@ -2178,7 +2180,10 @@ func (c *ChannelArbitrator) channelAttendant(bestHeight int32) {
// transition into StateContractClosed based on the
// close status of the channel.
closeSummary := &uniClosure.ChannelCloseSummary
err = c.cfg.MarkChannelClosed(closeSummary)
err = c.cfg.MarkChannelClosed(
closeSummary,
channeldb.ChanStatusRemoteCloseInitiator,
)
if err != nil {
log.Errorf("Unable to mark channel closed: %v",
err)

@ -12,6 +12,7 @@ import (
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/coreos/bbolt"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
@ -274,7 +275,26 @@ func (c *chanArbTestCtx) Restart(restartClosure func(*chanArbTestCtx)) (*chanArb
return newCtx, nil
}
func createTestChannelArbitrator(t *testing.T, log ArbitratorLog) (*chanArbTestCtx, error) {
// testChanArbOption applies custom settings to a channel arbitrator config for
// testing purposes.
type testChanArbOption func(cfg *ChannelArbitratorConfig)
// remoteInitiatorOption sets the MarkChannelClosed function in the
// Channel Arbitrator's config.
func withMarkClosed(markClosed func(*channeldb.ChannelCloseSummary,
...channeldb.ChannelStatus) error) testChanArbOption {
return func(cfg *ChannelArbitratorConfig) {
cfg.MarkChannelClosed = markClosed
}
}
// createTestChannelArbitrator returns a channel arbitrator test context which
// contains a channel arbitrator with default values. These values can be
// changed by providing options which overwrite the default config.
func createTestChannelArbitrator(t *testing.T, log ArbitratorLog,
opts ...testChanArbOption) (*chanArbTestCtx, error) {
blockEpochs := make(chan *chainntnfs.BlockEpoch)
blockEpoch := &chainntnfs.BlockEpochEvent{
Epochs: blockEpochs,
@ -332,7 +352,7 @@ func createTestChannelArbitrator(t *testing.T, log ArbitratorLog) (*chanArbTestC
// Next we'll create the matching configuration struct that contains
// all interfaces and methods the arbitrator needs to do its job.
arbCfg := ChannelArbitratorConfig{
arbCfg := &ChannelArbitratorConfig{
ChanPoint: chanPoint,
ShortChanID: shortChanID,
BlockEpochs: blockEpoch,
@ -350,7 +370,8 @@ func createTestChannelArbitrator(t *testing.T, log ArbitratorLog) (*chanArbTestC
MarkCommitmentBroadcasted: func(_ *wire.MsgTx, _ bool) error {
return nil
},
MarkChannelClosed: func(*channeldb.ChannelCloseSummary) error {
MarkChannelClosed: func(*channeldb.ChannelCloseSummary,
...channeldb.ChannelStatus) error {
return nil
},
IsPendingClose: false,
@ -358,6 +379,11 @@ func createTestChannelArbitrator(t *testing.T, log ArbitratorLog) (*chanArbTestC
ChainEvents: chanEvents,
}
// Apply all custom options to the config struct.
for _, option := range opts {
option(arbCfg)
}
var cleanUp func()
if log == nil {
dbDir, err := ioutil.TempDir("", "chanArb")
@ -371,7 +397,7 @@ func createTestChannelArbitrator(t *testing.T, log ArbitratorLog) (*chanArbTestC
}
backingLog, err := newBoltArbitratorLog(
db, arbCfg, chainhash.Hash{}, chanPoint,
db, *arbCfg, chainhash.Hash{}, chanPoint,
)
if err != nil {
return nil, err
@ -389,7 +415,7 @@ func createTestChannelArbitrator(t *testing.T, log ArbitratorLog) (*chanArbTestC
htlcSets := make(map[HtlcSetKey]htlcSet)
chanArb := NewChannelArbitrator(arbCfg, htlcSets, log)
chanArb := NewChannelArbitrator(*arbCfg, htlcSets, log)
return &chanArbTestCtx{
t: t,
@ -432,7 +458,9 @@ func TestChannelArbitratorCooperativeClose(t *testing.T) {
// We set up a channel to detect when MarkChannelClosed is called.
closeInfos := make(chan *channeldb.ChannelCloseSummary)
chanArbCtx.chanArb.cfg.MarkChannelClosed = func(
closeInfo *channeldb.ChannelCloseSummary) error {
closeInfo *channeldb.ChannelCloseSummary,
statuses ...channeldb.ChannelStatus) error {
closeInfos <- closeInfo
return nil
}
@ -1213,7 +1241,9 @@ func TestChannelArbitratorPersistence(t *testing.T) {
// Now we make the log succeed writing the resolutions, but fail when
// attempting to close the channel.
log.failLog = false
chanArb.cfg.MarkChannelClosed = func(*channeldb.ChannelCloseSummary) error {
chanArb.cfg.MarkChannelClosed = func(*channeldb.ChannelCloseSummary,
...channeldb.ChannelStatus) error {
return fmt.Errorf("intentional close error")
}
@ -1465,7 +1495,8 @@ func TestChannelArbitratorCommitFailure(t *testing.T) {
closed := make(chan struct{})
chanArb.cfg.MarkChannelClosed = func(
*channeldb.ChannelCloseSummary) error {
*channeldb.ChannelCloseSummary,
...channeldb.ChannelStatus) error {
close(closed)
return nil
}
@ -1910,3 +1941,150 @@ func TestChannelArbitratorPendingExpiredHTLC(t *testing.T) {
StateCommitmentBroadcasted,
)
}
// TestRemoteCloseInitiator tests the setting of close initiator statuses
// for remote force closes and breaches.
func TestRemoteCloseInitiator(t *testing.T) {
// getCloseSummary returns a unilateral close summary for the channel
// provided.
getCloseSummary := func(channel *channeldb.OpenChannel) *RemoteUnilateralCloseInfo {
return &RemoteUnilateralCloseInfo{
UnilateralCloseSummary: &lnwallet.UnilateralCloseSummary{
SpendDetail: &chainntnfs.SpendDetail{
SpenderTxHash: &chainhash.Hash{},
SpendingTx: &wire.MsgTx{
TxIn: []*wire.TxIn{},
TxOut: []*wire.TxOut{},
},
},
ChannelCloseSummary: channeldb.ChannelCloseSummary{
ChanPoint: channel.FundingOutpoint,
RemotePub: channel.IdentityPub,
SettledBalance: btcutil.Amount(500),
TimeLockedBalance: btcutil.Amount(10000),
IsPending: false,
},
HtlcResolutions: &lnwallet.HtlcResolutions{},
},
}
}
tests := []struct {
name string
// notifyClose sends the appropriate chain event to indicate
// that the channel has closed. The event subscription channel
// is expected to be buffered, as is the default for test
// channel arbitrators.
notifyClose func(sub *ChainEventSubscription,
channel *channeldb.OpenChannel)
// expectedStates is the set of states we expect the arbitrator
// to progress through.
expectedStates []ArbitratorState
}{
{
name: "force close",
notifyClose: func(sub *ChainEventSubscription,
channel *channeldb.OpenChannel) {
s := getCloseSummary(channel)
sub.RemoteUnilateralClosure <- s
},
expectedStates: []ArbitratorState{
StateContractClosed, StateFullyResolved,
},
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
// First, create alice's channel.
alice, _, cleanUp, err := lnwallet.CreateTestChannels(
true,
)
if err != nil {
t.Fatalf("unable to create test channels: %v",
err)
}
defer cleanUp()
// Create a mock log which will not block the test's
// expected number of transitions transitions, and has
// no commit resolutions so that the channel will
// resolve immediately.
log := &mockArbitratorLog{
state: StateDefault,
newStates: make(chan ArbitratorState,
len(test.expectedStates)),
resolutions: &ContractResolutions{
CommitHash: chainhash.Hash{},
CommitResolution: nil,
},
}
// Mock marking the channel as closed, we only care
// about setting of channel status.
mockMarkClosed := func(_ *channeldb.ChannelCloseSummary,
statuses ...channeldb.ChannelStatus) error {
for _, status := range statuses {
err := alice.State().ApplyChanStatus(status)
if err != nil {
return err
}
}
return nil
}
chanArbCtx, err := createTestChannelArbitrator(
t, log, withMarkClosed(mockMarkClosed),
)
if err != nil {
t.Fatalf("unable to create "+
"ChannelArbitrator: %v", err)
}
chanArb := chanArbCtx.chanArb
if err := chanArb.Start(); err != nil {
t.Fatalf("unable to start "+
"ChannelArbitrator: %v", err)
}
defer func() {
if err := chanArb.Stop(); err != nil {
t.Fatal(err)
}
}()
// It should start out in the default state.
chanArbCtx.AssertState(StateDefault)
// Notify the close event.
test.notifyClose(chanArb.cfg.ChainEvents, alice.State())
// Check that the channel transitions as expected.
chanArbCtx.AssertStateTransitions(
test.expectedStates...,
)
// It should also mark the channel as resolved.
select {
case <-chanArbCtx.resolvedChan:
// Expected.
case <-time.After(defaultTimeout):
t.Fatalf("contract was not resolved")
}
// Check that alice has the status we expect.
if !alice.State().HasChanStatus(
channeldb.ChanStatusRemoteCloseInitiator,
) {
t.Fatalf("expected remote close initiator, "+
"got: %v", alice.State().ChanStatus())
}
})
}
}