From 4eb3036f6773a5b0881ea007e3022e8fd05a59b4 Mon Sep 17 00:00:00 2001 From: carla Date: Fri, 21 Feb 2020 13:49:12 +0200 Subject: [PATCH] contractcourt: record force and breach close initiator --- contractcourt/chain_arbitrator.go | 7 +- contractcourt/chain_watcher.go | 4 +- contractcourt/channel_arbitrator.go | 11 +- contractcourt/channel_arbitrator_test.go | 194 ++++++++++++++++++++++- 4 files changed, 202 insertions(+), 14 deletions(-) diff --git a/contractcourt/chain_arbitrator.go b/contractcourt/chain_arbitrator.go index 552549f4..7a9cb309 100644 --- a/contractcourt/chain_arbitrator.go +++ b/contractcourt/chain_arbitrator.go @@ -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) diff --git a/contractcourt/chain_watcher.go b/contractcourt/chain_watcher.go index c08916d0..3012376b 100644 --- a/contractcourt/chain_watcher.go +++ b/contractcourt/chain_watcher.go @@ -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 } diff --git a/contractcourt/channel_arbitrator.go b/contractcourt/channel_arbitrator.go index 6db666c9..2a58ea52 100644 --- a/contractcourt/channel_arbitrator.go +++ b/contractcourt/channel_arbitrator.go @@ -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) diff --git a/contractcourt/channel_arbitrator_test.go b/contractcourt/channel_arbitrator_test.go index 2e7d4063..0a73cd5e 100644 --- a/contractcourt/channel_arbitrator_test.go +++ b/contractcourt/channel_arbitrator_test.go @@ -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()) + } + }) + } +}