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()
|
return chanMachine.ForceClose()
|
||||||
},
|
},
|
||||||
MarkCommitmentBroadcasted: channel.MarkCommitmentBroadcasted,
|
MarkCommitmentBroadcasted: channel.MarkCommitmentBroadcasted,
|
||||||
MarkChannelClosed: func(summary *channeldb.ChannelCloseSummary) error {
|
MarkChannelClosed: func(summary *channeldb.ChannelCloseSummary,
|
||||||
if err := channel.CloseChannel(summary); err != nil {
|
statuses ...channeldb.ChannelStatus) error {
|
||||||
|
|
||||||
|
err := channel.CloseChannel(summary, statuses...)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
c.cfg.NotifyClosedChannel(summary.ChanPoint)
|
c.cfg.NotifyClosedChannel(summary.ChanPoint)
|
||||||
|
@ -985,7 +985,9 @@ func (c *chainWatcher) dispatchContractBreach(spendEvent *chainntnfs.SpendDetail
|
|||||||
closeSummary.LastChanSyncMsg = chanSync
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,8 +104,10 @@ type ChannelArbitratorConfig struct {
|
|||||||
// passed close summary. After this method successfully returns we can
|
// passed close summary. After this method successfully returns we can
|
||||||
// no longer expect to receive chain events for this channel, and must
|
// no longer expect to receive chain events for this channel, and must
|
||||||
// be able to recover from a failure without getting the close event
|
// be able to recover from a failure without getting the close event
|
||||||
// again.
|
// again. It takes an optional channel status which will update the
|
||||||
MarkChannelClosed func(*channeldb.ChannelCloseSummary) error
|
// 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
|
// IsPendingClose is a boolean indicating whether the channel is marked
|
||||||
// as pending close in the database.
|
// as pending close in the database.
|
||||||
@ -2178,7 +2180,10 @@ func (c *ChannelArbitrator) channelAttendant(bestHeight int32) {
|
|||||||
// transition into StateContractClosed based on the
|
// transition into StateContractClosed based on the
|
||||||
// close status of the channel.
|
// close status of the channel.
|
||||||
closeSummary := &uniClosure.ChannelCloseSummary
|
closeSummary := &uniClosure.ChannelCloseSummary
|
||||||
err = c.cfg.MarkChannelClosed(closeSummary)
|
err = c.cfg.MarkChannelClosed(
|
||||||
|
closeSummary,
|
||||||
|
channeldb.ChanStatusRemoteCloseInitiator,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Unable to mark channel closed: %v",
|
log.Errorf("Unable to mark channel closed: %v",
|
||||||
err)
|
err)
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/btcsuite/btcutil"
|
||||||
"github.com/coreos/bbolt"
|
"github.com/coreos/bbolt"
|
||||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
@ -274,7 +275,26 @@ func (c *chanArbTestCtx) Restart(restartClosure func(*chanArbTestCtx)) (*chanArb
|
|||||||
return newCtx, nil
|
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)
|
blockEpochs := make(chan *chainntnfs.BlockEpoch)
|
||||||
blockEpoch := &chainntnfs.BlockEpochEvent{
|
blockEpoch := &chainntnfs.BlockEpochEvent{
|
||||||
Epochs: blockEpochs,
|
Epochs: blockEpochs,
|
||||||
@ -332,7 +352,7 @@ func createTestChannelArbitrator(t *testing.T, log ArbitratorLog) (*chanArbTestC
|
|||||||
|
|
||||||
// Next we'll create the matching configuration struct that contains
|
// Next we'll create the matching configuration struct that contains
|
||||||
// all interfaces and methods the arbitrator needs to do its job.
|
// all interfaces and methods the arbitrator needs to do its job.
|
||||||
arbCfg := ChannelArbitratorConfig{
|
arbCfg := &ChannelArbitratorConfig{
|
||||||
ChanPoint: chanPoint,
|
ChanPoint: chanPoint,
|
||||||
ShortChanID: shortChanID,
|
ShortChanID: shortChanID,
|
||||||
BlockEpochs: blockEpoch,
|
BlockEpochs: blockEpoch,
|
||||||
@ -350,7 +370,8 @@ func createTestChannelArbitrator(t *testing.T, log ArbitratorLog) (*chanArbTestC
|
|||||||
MarkCommitmentBroadcasted: func(_ *wire.MsgTx, _ bool) error {
|
MarkCommitmentBroadcasted: func(_ *wire.MsgTx, _ bool) error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
MarkChannelClosed: func(*channeldb.ChannelCloseSummary) error {
|
MarkChannelClosed: func(*channeldb.ChannelCloseSummary,
|
||||||
|
...channeldb.ChannelStatus) error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
IsPendingClose: false,
|
IsPendingClose: false,
|
||||||
@ -358,6 +379,11 @@ func createTestChannelArbitrator(t *testing.T, log ArbitratorLog) (*chanArbTestC
|
|||||||
ChainEvents: chanEvents,
|
ChainEvents: chanEvents,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply all custom options to the config struct.
|
||||||
|
for _, option := range opts {
|
||||||
|
option(arbCfg)
|
||||||
|
}
|
||||||
|
|
||||||
var cleanUp func()
|
var cleanUp func()
|
||||||
if log == nil {
|
if log == nil {
|
||||||
dbDir, err := ioutil.TempDir("", "chanArb")
|
dbDir, err := ioutil.TempDir("", "chanArb")
|
||||||
@ -371,7 +397,7 @@ func createTestChannelArbitrator(t *testing.T, log ArbitratorLog) (*chanArbTestC
|
|||||||
}
|
}
|
||||||
|
|
||||||
backingLog, err := newBoltArbitratorLog(
|
backingLog, err := newBoltArbitratorLog(
|
||||||
db, arbCfg, chainhash.Hash{}, chanPoint,
|
db, *arbCfg, chainhash.Hash{}, chanPoint,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -389,7 +415,7 @@ func createTestChannelArbitrator(t *testing.T, log ArbitratorLog) (*chanArbTestC
|
|||||||
|
|
||||||
htlcSets := make(map[HtlcSetKey]htlcSet)
|
htlcSets := make(map[HtlcSetKey]htlcSet)
|
||||||
|
|
||||||
chanArb := NewChannelArbitrator(arbCfg, htlcSets, log)
|
chanArb := NewChannelArbitrator(*arbCfg, htlcSets, log)
|
||||||
|
|
||||||
return &chanArbTestCtx{
|
return &chanArbTestCtx{
|
||||||
t: t,
|
t: t,
|
||||||
@ -432,7 +458,9 @@ func TestChannelArbitratorCooperativeClose(t *testing.T) {
|
|||||||
// We set up a channel to detect when MarkChannelClosed is called.
|
// We set up a channel to detect when MarkChannelClosed is called.
|
||||||
closeInfos := make(chan *channeldb.ChannelCloseSummary)
|
closeInfos := make(chan *channeldb.ChannelCloseSummary)
|
||||||
chanArbCtx.chanArb.cfg.MarkChannelClosed = func(
|
chanArbCtx.chanArb.cfg.MarkChannelClosed = func(
|
||||||
closeInfo *channeldb.ChannelCloseSummary) error {
|
closeInfo *channeldb.ChannelCloseSummary,
|
||||||
|
statuses ...channeldb.ChannelStatus) error {
|
||||||
|
|
||||||
closeInfos <- closeInfo
|
closeInfos <- closeInfo
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -1213,7 +1241,9 @@ func TestChannelArbitratorPersistence(t *testing.T) {
|
|||||||
// Now we make the log succeed writing the resolutions, but fail when
|
// Now we make the log succeed writing the resolutions, but fail when
|
||||||
// attempting to close the channel.
|
// attempting to close the channel.
|
||||||
log.failLog = false
|
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")
|
return fmt.Errorf("intentional close error")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1465,7 +1495,8 @@ func TestChannelArbitratorCommitFailure(t *testing.T) {
|
|||||||
|
|
||||||
closed := make(chan struct{})
|
closed := make(chan struct{})
|
||||||
chanArb.cfg.MarkChannelClosed = func(
|
chanArb.cfg.MarkChannelClosed = func(
|
||||||
*channeldb.ChannelCloseSummary) error {
|
*channeldb.ChannelCloseSummary,
|
||||||
|
...channeldb.ChannelStatus) error {
|
||||||
close(closed)
|
close(closed)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -1910,3 +1941,150 @@ func TestChannelArbitratorPendingExpiredHTLC(t *testing.T) {
|
|||||||
StateCommitmentBroadcasted,
|
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