contractcourt: record force and breach close initiator
This commit is contained in:
parent
c9915e027e
commit
4eb3036f67
@ -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())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user