mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-20 02:27:21 +01:00
Merge pull request #6158 from Crypt-iQ/breach_resolver_cancel
server+contractcourt: create breachResolver to ensure htlc's are failed back
This commit is contained in:
commit
a57c650f96
121
contractcourt/breach_resolver.go
Normal file
121
contractcourt/breach_resolver.go
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
package contractcourt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
|
)
|
||||||
|
|
||||||
|
// breachResolver is a resolver that will handle breached closes. In the
|
||||||
|
// future, this will likely take over the duties the current breacharbiter has.
|
||||||
|
type breachResolver struct {
|
||||||
|
// resolved reflects if the contract has been fully resolved or not.
|
||||||
|
resolved bool
|
||||||
|
|
||||||
|
// subscribed denotes whether or not the breach resolver has subscribed
|
||||||
|
// to the breacharbiter for breach resolution.
|
||||||
|
subscribed bool
|
||||||
|
|
||||||
|
// replyChan is closed when the breach arbiter has completed serving
|
||||||
|
// justice.
|
||||||
|
replyChan chan struct{}
|
||||||
|
|
||||||
|
contractResolverKit
|
||||||
|
}
|
||||||
|
|
||||||
|
// newBreachResolver instantiates a new breach resolver.
|
||||||
|
func newBreachResolver(resCfg ResolverConfig) *breachResolver {
|
||||||
|
r := &breachResolver{
|
||||||
|
contractResolverKit: *newContractResolverKit(resCfg),
|
||||||
|
replyChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
r.initLogger(r)
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolverKey returns the unique identifier for this resolver.
|
||||||
|
func (b *breachResolver) ResolverKey() []byte {
|
||||||
|
key := newResolverID(b.ChanPoint)
|
||||||
|
return key[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve queries the breacharbiter to see if the justice transaction has been
|
||||||
|
// broadcast.
|
||||||
|
func (b *breachResolver) Resolve() (ContractResolver, error) {
|
||||||
|
if !b.subscribed {
|
||||||
|
complete, err := b.SubscribeBreachComplete(
|
||||||
|
&b.ChanPoint, b.replyChan,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the breach resolution process is already complete, then
|
||||||
|
// we can cleanup and checkpoint the resolved state.
|
||||||
|
if complete {
|
||||||
|
b.resolved = true
|
||||||
|
return nil, b.Checkpoint(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent duplicate subscriptions.
|
||||||
|
b.subscribed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-b.replyChan:
|
||||||
|
// The replyChan has been closed, signalling that the breach
|
||||||
|
// has been fully resolved. Checkpoint the resolved state and
|
||||||
|
// exit.
|
||||||
|
b.resolved = true
|
||||||
|
return nil, b.Checkpoint(b)
|
||||||
|
case <-b.quit:
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errResolverShuttingDown
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop signals the breachResolver to stop.
|
||||||
|
func (b *breachResolver) Stop() {
|
||||||
|
close(b.quit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsResolved returns true if the breachResolver is fully resolved and cleanup
|
||||||
|
// can occur.
|
||||||
|
func (b *breachResolver) IsResolved() bool {
|
||||||
|
return b.resolved
|
||||||
|
}
|
||||||
|
|
||||||
|
// SupplementState adds additional state to the breachResolver.
|
||||||
|
func (b *breachResolver) SupplementState(_ *channeldb.OpenChannel) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode encodes the breachResolver to the passed writer.
|
||||||
|
func (b *breachResolver) Encode(w io.Writer) error {
|
||||||
|
return binary.Write(w, endian, b.resolved)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newBreachResolverFromReader attempts to decode an encoded breachResolver
|
||||||
|
// from the passed Reader instance, returning an active breachResolver.
|
||||||
|
func newBreachResolverFromReader(r io.Reader, resCfg ResolverConfig) (
|
||||||
|
*breachResolver, error) {
|
||||||
|
|
||||||
|
b := &breachResolver{
|
||||||
|
contractResolverKit: *newContractResolverKit(resCfg),
|
||||||
|
replyChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := binary.Read(r, endian, &b.resolved); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
b.initLogger(b)
|
||||||
|
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A compile time assertion to ensure breachResolver meets the ContractResolver
|
||||||
|
// interface.
|
||||||
|
var _ ContractResolver = (*breachResolver)(nil)
|
@ -185,6 +185,8 @@ type BreachArbiter struct {
|
|||||||
|
|
||||||
cfg *BreachConfig
|
cfg *BreachConfig
|
||||||
|
|
||||||
|
subscriptions map[wire.OutPoint]chan struct{}
|
||||||
|
|
||||||
quit chan struct{}
|
quit chan struct{}
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
@ -194,8 +196,9 @@ type BreachArbiter struct {
|
|||||||
// its dependent objects.
|
// its dependent objects.
|
||||||
func NewBreachArbiter(cfg *BreachConfig) *BreachArbiter {
|
func NewBreachArbiter(cfg *BreachConfig) *BreachArbiter {
|
||||||
return &BreachArbiter{
|
return &BreachArbiter{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
quit: make(chan struct{}),
|
subscriptions: make(map[wire.OutPoint]chan struct{}),
|
||||||
|
quit: make(chan struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,6 +325,47 @@ func (b *BreachArbiter) IsBreached(chanPoint *wire.OutPoint) (bool, error) {
|
|||||||
return b.cfg.Store.IsBreached(chanPoint)
|
return b.cfg.Store.IsBreached(chanPoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SubscribeBreachComplete is used by outside subsystems to be notified of a
|
||||||
|
// successful breach resolution.
|
||||||
|
func (b *BreachArbiter) SubscribeBreachComplete(chanPoint *wire.OutPoint,
|
||||||
|
c chan struct{}) (bool, error) {
|
||||||
|
|
||||||
|
breached, err := b.cfg.Store.IsBreached(chanPoint)
|
||||||
|
if err != nil {
|
||||||
|
// If an error occurs, no subscription will be registered.
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !breached {
|
||||||
|
// If chanPoint no longer exists in the Store, then the breach
|
||||||
|
// was cleaned up successfully. Any subscription that occurs
|
||||||
|
// happens after the breach information was persisted to the
|
||||||
|
// underlying store.
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise since the channel point is not resolved, add a
|
||||||
|
// subscription. There can only be one subscription per channel point.
|
||||||
|
b.Lock()
|
||||||
|
defer b.Unlock()
|
||||||
|
b.subscriptions[*chanPoint] = c
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// notifyBreachComplete is used by the BreachArbiter to notify outside
|
||||||
|
// subsystems that the breach resolution process is complete.
|
||||||
|
func (b *BreachArbiter) notifyBreachComplete(chanPoint *wire.OutPoint) {
|
||||||
|
b.Lock()
|
||||||
|
defer b.Unlock()
|
||||||
|
if c, ok := b.subscriptions[*chanPoint]; ok {
|
||||||
|
close(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the subscription.
|
||||||
|
delete(b.subscriptions, *chanPoint)
|
||||||
|
}
|
||||||
|
|
||||||
// contractObserver is the primary goroutine for the BreachArbiter. This
|
// contractObserver is the primary goroutine for the BreachArbiter. This
|
||||||
// goroutine is responsible for handling breach events coming from the
|
// goroutine is responsible for handling breach events coming from the
|
||||||
// contractcourt on the ContractBreaches channel. If a channel breach is
|
// contractcourt on the ContractBreaches channel. If a channel breach is
|
||||||
@ -857,6 +901,14 @@ func (b *BreachArbiter) cleanupBreach(chanPoint *wire.OutPoint) error {
|
|||||||
err)
|
err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is after the Remove call so that the chan passed in via
|
||||||
|
// SubscribeBreachComplete is always notified, no matter when it is
|
||||||
|
// called. Otherwise, if notifyBreachComplete was before Remove, a
|
||||||
|
// very rare edge case could occur in which SubscribeBreachComplete
|
||||||
|
// is called after notifyBreachComplete and before Remove, meaning the
|
||||||
|
// caller would never be notified.
|
||||||
|
b.notifyBreachComplete(chanPoint)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,16 +36,21 @@ type ContractResolutions struct {
|
|||||||
// output. If the channel type doesn't include anchors, the value of
|
// output. If the channel type doesn't include anchors, the value of
|
||||||
// this field will be nil.
|
// this field will be nil.
|
||||||
AnchorResolution *lnwallet.AnchorResolution
|
AnchorResolution *lnwallet.AnchorResolution
|
||||||
|
|
||||||
|
// BreachResolution contains the data required to manage the lifecycle
|
||||||
|
// of a breach in the ChannelArbitrator.
|
||||||
|
BreachResolution *BreachResolution
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsEmpty returns true if the set of resolutions is "empty". A resolution is
|
// IsEmpty returns true if the set of resolutions is "empty". A resolution is
|
||||||
// empty if: our commitment output has been trimmed, and we don't have any
|
// empty if: our commitment output has been trimmed, we don't have any
|
||||||
// incoming or outgoing HTLC's active.
|
// incoming or outgoing HTLC's active, there is no anchor output to sweep, or
|
||||||
|
// there are no breached outputs to resolve.
|
||||||
func (c *ContractResolutions) IsEmpty() bool {
|
func (c *ContractResolutions) IsEmpty() bool {
|
||||||
return c.CommitResolution == nil &&
|
return c.CommitResolution == nil &&
|
||||||
len(c.HtlcResolutions.IncomingHTLCs) == 0 &&
|
len(c.HtlcResolutions.IncomingHTLCs) == 0 &&
|
||||||
len(c.HtlcResolutions.OutgoingHTLCs) == 0 &&
|
len(c.HtlcResolutions.OutgoingHTLCs) == 0 &&
|
||||||
c.AnchorResolution == nil
|
c.AnchorResolution == nil && c.BreachResolution == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ArbitratorLog is the primary source of persistent storage for the
|
// ArbitratorLog is the primary source of persistent storage for the
|
||||||
@ -142,7 +147,7 @@ const (
|
|||||||
// | | |
|
// | | |
|
||||||
// | | |-> StateCommitmentBroadcasted: chain/user trigger
|
// | | |-> StateCommitmentBroadcasted: chain/user trigger
|
||||||
// | | |
|
// | | |
|
||||||
// | | |-> StateContractClosed: local/remote close trigger
|
// | | |-> StateContractClosed: local/remote/breach close trigger
|
||||||
// | | | |
|
// | | | |
|
||||||
// | | | |-> StateWaitingFullResolution: contract resolutions not empty
|
// | | | |-> StateWaitingFullResolution: contract resolutions not empty
|
||||||
// | | | | |
|
// | | | | |
|
||||||
@ -152,9 +157,9 @@ const (
|
|||||||
// | | | |
|
// | | | |
|
||||||
// | | | |-> StateFullyResolved: contract resolutions empty
|
// | | | |-> StateFullyResolved: contract resolutions empty
|
||||||
// | | |
|
// | | |
|
||||||
// | | |-> StateFullyResolved: coop/breach close trigger
|
// | | |-> StateFullyResolved: coop/breach(legacy) close trigger
|
||||||
// | |
|
// | |
|
||||||
// | |-> StateContractClosed: local/remote close trigger
|
// | |-> StateContractClosed: local/remote/breach close trigger
|
||||||
// | | |
|
// | | |
|
||||||
// | | |-> StateWaitingFullResolution: contract resolutions not empty
|
// | | |-> StateWaitingFullResolution: contract resolutions not empty
|
||||||
// | | | |
|
// | | | |
|
||||||
@ -164,11 +169,11 @@ const (
|
|||||||
// | | |
|
// | | |
|
||||||
// | | |-> StateFullyResolved: contract resolutions empty
|
// | | |-> StateFullyResolved: contract resolutions empty
|
||||||
// | |
|
// | |
|
||||||
// | |-> StateFullyResolved: coop/breach close trigger
|
// | |-> StateFullyResolved: coop/breach(legacy) close trigger
|
||||||
// |
|
// |
|
||||||
// |-> StateContractClosed: local/remote close trigger
|
// |-> StateContractClosed: local/remote/breach close trigger
|
||||||
// | |
|
// | |
|
||||||
// | |-> StateWaitingFullResolution: contract resolutions empty
|
// | |-> StateWaitingFullResolution: contract resolutions not empty
|
||||||
// | | |
|
// | | |
|
||||||
// | | |-> StateWaitingFullResolution: contract resolutions not empty
|
// | | |-> StateWaitingFullResolution: contract resolutions not empty
|
||||||
// | | |
|
// | | |
|
||||||
@ -176,7 +181,7 @@ const (
|
|||||||
// | |
|
// | |
|
||||||
// | |-> StateFullyResolved: contract resolutions empty
|
// | |-> StateFullyResolved: contract resolutions empty
|
||||||
// |
|
// |
|
||||||
// |-> StateFullyResolved: coop/breach close trigger
|
// |-> StateFullyResolved: coop/breach(legacy) close trigger
|
||||||
|
|
||||||
// StateDefault is the default state. In this state, no major actions
|
// StateDefault is the default state. In this state, no major actions
|
||||||
// need to be executed.
|
// need to be executed.
|
||||||
@ -269,6 +274,10 @@ const (
|
|||||||
// sweeping out direct commitment output form the remote party's
|
// sweeping out direct commitment output form the remote party's
|
||||||
// commitment transaction.
|
// commitment transaction.
|
||||||
resolverUnilateralSweep resolverType = 4
|
resolverUnilateralSweep resolverType = 4
|
||||||
|
|
||||||
|
// resolverBreach is the type of resolver that manages a contract
|
||||||
|
// breach on-chain.
|
||||||
|
resolverBreach resolverType = 5
|
||||||
)
|
)
|
||||||
|
|
||||||
// resolverIDLen is the size of the resolver ID key. This is 36 bytes as we get
|
// resolverIDLen is the size of the resolver ID key. This is 36 bytes as we get
|
||||||
@ -341,6 +350,11 @@ var (
|
|||||||
// store the anchor resolution, if any.
|
// store the anchor resolution, if any.
|
||||||
anchorResolutionKey = []byte("anchor-resolution")
|
anchorResolutionKey = []byte("anchor-resolution")
|
||||||
|
|
||||||
|
// breachResolutionKey is the key under the logScope that we'll use to
|
||||||
|
// store the breach resolution, if any. This is used rather than the
|
||||||
|
// resolutionsKey.
|
||||||
|
breachResolutionKey = []byte("breach-resolution")
|
||||||
|
|
||||||
// actionsBucketKey is the key under the logScope that we'll use to
|
// actionsBucketKey is the key under the logScope that we'll use to
|
||||||
// store all chain actions once they're determined.
|
// store all chain actions once they're determined.
|
||||||
actionsBucketKey = []byte("chain-actions")
|
actionsBucketKey = []byte("chain-actions")
|
||||||
@ -464,6 +478,8 @@ func (b *boltArbitratorLog) writeResolver(contractBucket kvdb.RwBucket,
|
|||||||
rType = resolverIncomingContest
|
rType = resolverIncomingContest
|
||||||
case *commitSweepResolver:
|
case *commitSweepResolver:
|
||||||
rType = resolverUnilateralSweep
|
rType = resolverUnilateralSweep
|
||||||
|
case *breachResolver:
|
||||||
|
rType = resolverBreach
|
||||||
}
|
}
|
||||||
if _, err := buf.Write([]byte{byte(rType)}); err != nil {
|
if _, err := buf.Write([]byte{byte(rType)}); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -593,6 +609,11 @@ func (b *boltArbitratorLog) FetchUnresolvedContracts() ([]ContractResolver, erro
|
|||||||
resReader, resolverCfg,
|
resReader, resolverCfg,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
case resolverBreach:
|
||||||
|
res, err = newBreachResolverFromReader(
|
||||||
|
resReader, resolverCfg,
|
||||||
|
)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown resolver type: %v", resType)
|
return fmt.Errorf("unknown resolver type: %v", resType)
|
||||||
}
|
}
|
||||||
@ -785,6 +806,20 @@ func (b *boltArbitratorLog) LogContractResolutions(c *ContractResolutions) error
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write out the breach resolution if present.
|
||||||
|
if c.BreachResolution != nil {
|
||||||
|
var b bytes.Buffer
|
||||||
|
err := encodeBreachResolution(&b, c.BreachResolution)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = scopeBucket.Put(breachResolutionKey, b.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -904,6 +939,18 @@ func (b *boltArbitratorLog) FetchContractResolutions() (*ContractResolutions, er
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
breachResBytes := scopeBucket.Get(breachResolutionKey)
|
||||||
|
if breachResBytes != nil {
|
||||||
|
c.BreachResolution = &BreachResolution{}
|
||||||
|
resReader := bytes.NewReader(breachResBytes)
|
||||||
|
err := decodeBreachResolution(
|
||||||
|
resReader, c.BreachResolution,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}, func() {
|
}, func() {
|
||||||
c = &ContractResolutions{}
|
c = &ContractResolutions{}
|
||||||
@ -1372,6 +1419,21 @@ func decodeAnchorResolution(r io.Reader,
|
|||||||
return input.ReadSignDescriptor(r, &a.AnchorSignDescriptor)
|
return input.ReadSignDescriptor(r, &a.AnchorSignDescriptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func encodeBreachResolution(w io.Writer, b *BreachResolution) error {
|
||||||
|
if _, err := w.Write(b.FundingOutPoint.Hash[:]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return binary.Write(w, endian, b.FundingOutPoint.Index)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeBreachResolution(r io.Reader, b *BreachResolution) error {
|
||||||
|
_, err := io.ReadFull(r, b.FundingOutPoint.Hash[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return binary.Read(r, endian, &b.FundingOutPoint.Index)
|
||||||
|
}
|
||||||
|
|
||||||
func encodeHtlcSetKey(w io.Writer, h *HtlcSetKey) error {
|
func encodeHtlcSetKey(w io.Writer, h *HtlcSetKey) error {
|
||||||
err := binary.Write(w, endian, h.IsRemote)
|
err := binary.Write(w, endian, h.IsRemote)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -100,14 +100,12 @@ type ChainArbitratorConfig struct {
|
|||||||
MarkLinkInactive func(wire.OutPoint) error
|
MarkLinkInactive func(wire.OutPoint) error
|
||||||
|
|
||||||
// ContractBreach is a function closure that the ChainArbitrator will
|
// ContractBreach is a function closure that the ChainArbitrator will
|
||||||
// use to notify the breachArbiter about a contract breach. A callback
|
// use to notify the breachArbiter about a contract breach. It should
|
||||||
// should be passed that when called will mark the channel pending
|
// only return a non-nil error when the breachArbiter has preserved
|
||||||
// close in the database. It should only return a non-nil error when the
|
// the necessary breach info for this channel point. Once the breach
|
||||||
// breachArbiter has preserved the necessary breach info for this
|
// resolution is persisted in the channel arbitrator, it will be safe
|
||||||
// channel point, and the callback has succeeded, meaning it is safe to
|
// to mark the channel closed.
|
||||||
// stop watching the channel.
|
ContractBreach func(wire.OutPoint, *lnwallet.BreachRetribution) error
|
||||||
ContractBreach func(wire.OutPoint, *lnwallet.BreachRetribution,
|
|
||||||
func() error) error
|
|
||||||
|
|
||||||
// IsOurAddress is a function that returns true if the passed address
|
// IsOurAddress is a function that returns true if the passed address
|
||||||
// is known to the underlying wallet. Otherwise, false should be
|
// is known to the underlying wallet. Otherwise, false should be
|
||||||
@ -183,6 +181,12 @@ type ChainArbitratorConfig struct {
|
|||||||
// Clock is the clock implementation that ChannelArbitrator uses.
|
// Clock is the clock implementation that ChannelArbitrator uses.
|
||||||
// It is useful for testing.
|
// It is useful for testing.
|
||||||
Clock clock.Clock
|
Clock clock.Clock
|
||||||
|
|
||||||
|
// SubscribeBreachComplete is used by the breachResolver to register a
|
||||||
|
// subscription that notifies when the breach resolution process is
|
||||||
|
// complete.
|
||||||
|
SubscribeBreachComplete func(op *wire.OutPoint, c chan struct{}) (
|
||||||
|
bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChainArbitrator is a sub-system that oversees the on-chain resolution of all
|
// ChainArbitrator is a sub-system that oversees the on-chain resolution of all
|
||||||
@ -506,19 +510,17 @@ func (c *ChainArbitrator) Start() error {
|
|||||||
|
|
||||||
// First, we'll create an active chainWatcher for this channel
|
// First, we'll create an active chainWatcher for this channel
|
||||||
// to ensure that we detect any relevant on chain events.
|
// to ensure that we detect any relevant on chain events.
|
||||||
|
breachClosure := func(ret *lnwallet.BreachRetribution) error {
|
||||||
|
return c.cfg.ContractBreach(chanPoint, ret)
|
||||||
|
}
|
||||||
|
|
||||||
chainWatcher, err := newChainWatcher(
|
chainWatcher, err := newChainWatcher(
|
||||||
chainWatcherConfig{
|
chainWatcherConfig{
|
||||||
chanState: channel,
|
chanState: channel,
|
||||||
notifier: c.cfg.Notifier,
|
notifier: c.cfg.Notifier,
|
||||||
signer: c.cfg.Signer,
|
signer: c.cfg.Signer,
|
||||||
isOurAddr: c.cfg.IsOurAddress,
|
isOurAddr: c.cfg.IsOurAddress,
|
||||||
contractBreach: func(retInfo *lnwallet.BreachRetribution,
|
contractBreach: breachClosure,
|
||||||
markClosed func() error) error {
|
|
||||||
|
|
||||||
return c.cfg.ContractBreach(
|
|
||||||
chanPoint, retInfo, markClosed,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
extractStateNumHint: lnwallet.GetStateNumHint,
|
extractStateNumHint: lnwallet.GetStateNumHint,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -1116,11 +1118,11 @@ func (c *ChainArbitrator) WatchNewChannel(newChan *channeldb.OpenChannel) error
|
|||||||
notifier: c.cfg.Notifier,
|
notifier: c.cfg.Notifier,
|
||||||
signer: c.cfg.Signer,
|
signer: c.cfg.Signer,
|
||||||
isOurAddr: c.cfg.IsOurAddress,
|
isOurAddr: c.cfg.IsOurAddress,
|
||||||
contractBreach: func(retInfo *lnwallet.BreachRetribution,
|
contractBreach: func(
|
||||||
markClosed func() error) error {
|
retInfo *lnwallet.BreachRetribution) error {
|
||||||
|
|
||||||
return c.cfg.ContractBreach(
|
return c.cfg.ContractBreach(
|
||||||
chanPoint, retInfo, markClosed,
|
chanPoint, retInfo,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
extractStateNumHint: lnwallet.GetStateNumHint,
|
extractStateNumHint: lnwallet.GetStateNumHint,
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/btcsuite/btcd/btcec"
|
"github.com/btcsuite/btcd/btcec"
|
||||||
"github.com/btcsuite/btcd/chaincfg"
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/btcsuite/btcutil"
|
"github.com/btcsuite/btcutil"
|
||||||
@ -57,6 +58,29 @@ type RemoteUnilateralCloseInfo struct {
|
|||||||
CommitSet CommitSet
|
CommitSet CommitSet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BreachResolution wraps the outpoint of the breached channel.
|
||||||
|
type BreachResolution struct {
|
||||||
|
FundingOutPoint wire.OutPoint
|
||||||
|
}
|
||||||
|
|
||||||
|
// BreachCloseInfo wraps the BreachResolution with a CommitSet for the latest,
|
||||||
|
// non-breached state, with the AnchorResolution for the breached state.
|
||||||
|
type BreachCloseInfo struct {
|
||||||
|
*BreachResolution
|
||||||
|
*lnwallet.AnchorResolution
|
||||||
|
|
||||||
|
// CommitHash is the hash of the commitment transaction.
|
||||||
|
CommitHash chainhash.Hash
|
||||||
|
|
||||||
|
// CommitSet is the set of known valid commitments at the time the
|
||||||
|
// breach occurred on-chain.
|
||||||
|
CommitSet CommitSet
|
||||||
|
|
||||||
|
// CloseSummary gives the recipient of the BreachCloseInfo information
|
||||||
|
// to mark the channel closed in the database.
|
||||||
|
CloseSummary channeldb.ChannelCloseSummary
|
||||||
|
}
|
||||||
|
|
||||||
// CommitSet is a collection of the set of known valid commitments at a given
|
// CommitSet is a collection of the set of known valid commitments at a given
|
||||||
// instant. If ConfCommitKey is set, then the commitment identified by the
|
// instant. If ConfCommitKey is set, then the commitment identified by the
|
||||||
// HtlcSetKey has hit the chain. This struct will be used to examine all live
|
// HtlcSetKey has hit the chain. This struct will be used to examine all live
|
||||||
@ -124,7 +148,7 @@ type ChainEventSubscription struct {
|
|||||||
// ContractBreach is a channel that will be sent upon if we detect a
|
// ContractBreach is a channel that will be sent upon if we detect a
|
||||||
// contract breach. The struct sent across the channel contains all the
|
// contract breach. The struct sent across the channel contains all the
|
||||||
// material required to bring the cheating channel peer to justice.
|
// material required to bring the cheating channel peer to justice.
|
||||||
ContractBreach chan *lnwallet.BreachRetribution
|
ContractBreach chan *BreachCloseInfo
|
||||||
|
|
||||||
// Cancel cancels the subscription to the event stream for a particular
|
// Cancel cancels the subscription to the event stream for a particular
|
||||||
// channel. This method should be called once the caller no longer needs to
|
// channel. This method should be called once the caller no longer needs to
|
||||||
@ -150,13 +174,10 @@ type chainWatcherConfig struct {
|
|||||||
signer input.Signer
|
signer input.Signer
|
||||||
|
|
||||||
// contractBreach is a method that will be called by the watcher if it
|
// contractBreach is a method that will be called by the watcher if it
|
||||||
// detects that a contract breach transaction has been confirmed. A
|
// detects that a contract breach transaction has been confirmed. It
|
||||||
// callback should be passed that when called will mark the channel
|
// will only return a non-nil error when the breachArbiter has
|
||||||
// pending close in the database. It will only return a non-nil error
|
// preserved the necessary breach info for this channel point.
|
||||||
// when the breachArbiter has preserved the necessary breach info for
|
contractBreach func(*lnwallet.BreachRetribution) error
|
||||||
// this channel point, and the callback has succeeded, meaning it is
|
|
||||||
// safe to stop watching the channel.
|
|
||||||
contractBreach func(*lnwallet.BreachRetribution, func() error) error
|
|
||||||
|
|
||||||
// isOurAddr is a function that returns true if the passed address is
|
// isOurAddr is a function that returns true if the passed address is
|
||||||
// known to us.
|
// known to us.
|
||||||
@ -311,7 +332,7 @@ func (c *chainWatcher) SubscribeChannelEvents() *ChainEventSubscription {
|
|||||||
RemoteUnilateralClosure: make(chan *RemoteUnilateralCloseInfo, 1),
|
RemoteUnilateralClosure: make(chan *RemoteUnilateralCloseInfo, 1),
|
||||||
LocalUnilateralClosure: make(chan *LocalUnilateralCloseInfo, 1),
|
LocalUnilateralClosure: make(chan *LocalUnilateralCloseInfo, 1),
|
||||||
CooperativeClosure: make(chan *CooperativeCloseInfo, 1),
|
CooperativeClosure: make(chan *CooperativeCloseInfo, 1),
|
||||||
ContractBreach: make(chan *lnwallet.BreachRetribution, 1),
|
ContractBreach: make(chan *BreachCloseInfo, 1),
|
||||||
Cancel: func() {
|
Cancel: func() {
|
||||||
c.Lock()
|
c.Lock()
|
||||||
delete(c.clientSubscriptions, clientID)
|
delete(c.clientSubscriptions, clientID)
|
||||||
@ -785,12 +806,27 @@ func (c *chainWatcher) handleKnownRemoteState(
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create an AnchorResolution for the breached state.
|
||||||
|
anchorRes, err := lnwallet.NewAnchorResolution(
|
||||||
|
c.cfg.chanState, commitSpend.SpendingTx,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("unable to create anchor "+
|
||||||
|
"resolution: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We'll set the ConfCommitKey here as the remote htlc set. This is
|
||||||
|
// only used to ensure a nil-pointer-dereference doesn't occur and is
|
||||||
|
// not used otherwise. The HTLC's may not exist for the
|
||||||
|
// RemotePendingHtlcSet.
|
||||||
|
chainSet.commitSet.ConfCommitKey = &RemoteHtlcSet
|
||||||
|
|
||||||
// THEY'RE ATTEMPTING TO VIOLATE THE CONTRACT LAID OUT WITHIN THE
|
// THEY'RE ATTEMPTING TO VIOLATE THE CONTRACT LAID OUT WITHIN THE
|
||||||
// PAYMENT CHANNEL. Therefore we close the signal indicating a revoked
|
// PAYMENT CHANNEL. Therefore we close the signal indicating a revoked
|
||||||
// broadcast to allow subscribers to swiftly dispatch justice!!!
|
// broadcast to allow subscribers to swiftly dispatch justice!!!
|
||||||
err = c.dispatchContractBreach(
|
err = c.dispatchContractBreach(
|
||||||
commitSpend, &chainSet.remoteCommit,
|
commitSpend, chainSet, broadcastStateNum, retribution,
|
||||||
broadcastStateNum, retribution,
|
anchorRes,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("unable to handle channel "+
|
return false, fmt.Errorf("unable to handle channel "+
|
||||||
@ -1083,8 +1119,9 @@ func (c *chainWatcher) dispatchRemoteForceClose(
|
|||||||
// materials required to bring the cheater to justice, then notify all
|
// materials required to bring the cheater to justice, then notify all
|
||||||
// registered subscribers of this event.
|
// registered subscribers of this event.
|
||||||
func (c *chainWatcher) dispatchContractBreach(spendEvent *chainntnfs.SpendDetail,
|
func (c *chainWatcher) dispatchContractBreach(spendEvent *chainntnfs.SpendDetail,
|
||||||
remoteCommit *channeldb.ChannelCommitment, broadcastStateNum uint64,
|
chainSet *chainSet, broadcastStateNum uint64,
|
||||||
retribution *lnwallet.BreachRetribution) error {
|
retribution *lnwallet.BreachRetribution,
|
||||||
|
anchorRes *lnwallet.AnchorResolution) error {
|
||||||
|
|
||||||
log.Warnf("Remote peer has breached the channel contract for "+
|
log.Warnf("Remote peer has breached the channel contract for "+
|
||||||
"ChannelPoint(%v). Revoked state #%v was broadcast!!!",
|
"ChannelPoint(%v). Revoked state #%v was broadcast!!!",
|
||||||
@ -1125,7 +1162,7 @@ func (c *chainWatcher) dispatchContractBreach(spendEvent *chainntnfs.SpendDetail
|
|||||||
return spew.Sdump(retribution)
|
return spew.Sdump(retribution)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
settledBalance := remoteCommit.LocalBalance.ToSatoshis()
|
settledBalance := chainSet.remoteCommit.LocalBalance.ToSatoshis()
|
||||||
closeSummary := channeldb.ChannelCloseSummary{
|
closeSummary := channeldb.ChannelCloseSummary{
|
||||||
ChanPoint: c.cfg.chanState.FundingOutpoint,
|
ChanPoint: c.cfg.chanState.FundingOutpoint,
|
||||||
ChainHash: c.cfg.chanState.ChainHash,
|
ChainHash: c.cfg.chanState.ChainHash,
|
||||||
@ -1151,38 +1188,35 @@ func (c *chainWatcher) dispatchContractBreach(spendEvent *chainntnfs.SpendDetail
|
|||||||
closeSummary.LastChanSyncMsg = chanSync
|
closeSummary.LastChanSyncMsg = chanSync
|
||||||
}
|
}
|
||||||
|
|
||||||
// We create a function closure that will mark the channel as pending
|
// Hand the retribution info over to the breach arbiter. This function
|
||||||
// close in the database. We pass it to the contracBreach method such
|
// will wait for a response from the breach arbiter and then proceed to
|
||||||
// that it can ensure safe handoff of the breach before we close the
|
// send a BreachCloseInfo to the channel arbitrator. The channel arb
|
||||||
// channel.
|
// will then mark the channel as closed after resolutions and the
|
||||||
markClosed := func() error {
|
// commit set are logged in the arbitrator log.
|
||||||
// At this point, we've successfully received an ack for the
|
if err := c.cfg.contractBreach(retribution); err != nil {
|
||||||
// breach close, and we can mark the channel as pending force
|
|
||||||
// closed.
|
|
||||||
if err := c.cfg.chanState.CloseChannel(
|
|
||||||
&closeSummary, channeldb.ChanStatusRemoteCloseInitiator,
|
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("Breached channel=%v marked pending-closed",
|
|
||||||
c.cfg.chanState.FundingOutpoint)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hand the retribution info over to the breach arbiter.
|
|
||||||
if err := c.cfg.contractBreach(retribution, markClosed); err != nil {
|
|
||||||
log.Errorf("unable to hand breached contract off to "+
|
log.Errorf("unable to hand breached contract off to "+
|
||||||
"breachArbiter: %v", err)
|
"breachArbiter: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
breachRes := &BreachResolution{
|
||||||
|
FundingOutPoint: c.cfg.chanState.FundingOutpoint,
|
||||||
|
}
|
||||||
|
|
||||||
|
breachInfo := &BreachCloseInfo{
|
||||||
|
CommitHash: spendEvent.SpendingTx.TxHash(),
|
||||||
|
BreachResolution: breachRes,
|
||||||
|
AnchorResolution: anchorRes,
|
||||||
|
CommitSet: chainSet.commitSet,
|
||||||
|
CloseSummary: closeSummary,
|
||||||
|
}
|
||||||
|
|
||||||
// With the event processed and channel closed, we'll now notify all
|
// With the event processed and channel closed, we'll now notify all
|
||||||
// subscribers of the event.
|
// subscribers of the event.
|
||||||
c.Lock()
|
c.Lock()
|
||||||
for _, sub := range c.clientSubscriptions {
|
for _, sub := range c.clientSubscriptions {
|
||||||
select {
|
select {
|
||||||
case sub.ContractBreach <- retribution:
|
case sub.ContractBreach <- breachInfo:
|
||||||
case <-c.quit:
|
case <-c.quit:
|
||||||
c.Unlock()
|
c.Unlock()
|
||||||
return fmt.Errorf("quitting")
|
return fmt.Errorf("quitting")
|
||||||
|
@ -747,8 +747,8 @@ const (
|
|||||||
coopCloseTrigger
|
coopCloseTrigger
|
||||||
|
|
||||||
// breachCloseTrigger is a transition trigger driven by a remote breach
|
// breachCloseTrigger is a transition trigger driven by a remote breach
|
||||||
// being confirmed. In this case the channel arbitrator won't have to
|
// being confirmed. In this case the channel arbitrator will wait for
|
||||||
// do anything, so we'll just clean up and exit gracefully.
|
// the breacharbiter to finish and then clean up gracefully.
|
||||||
breachCloseTrigger
|
breachCloseTrigger
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -852,9 +852,8 @@ func (c *ChannelArbitrator) stateStep(
|
|||||||
|
|
||||||
// If the trigger is a cooperative close being confirmed, then
|
// If the trigger is a cooperative close being confirmed, then
|
||||||
// we can go straight to StateFullyResolved, as there won't be
|
// we can go straight to StateFullyResolved, as there won't be
|
||||||
// any contracts to resolve. The same is true in the case of a
|
// any contracts to resolve.
|
||||||
// breach.
|
case coopCloseTrigger:
|
||||||
case coopCloseTrigger, breachCloseTrigger:
|
|
||||||
nextState = StateFullyResolved
|
nextState = StateFullyResolved
|
||||||
|
|
||||||
// Otherwise, if this state advance was triggered by a
|
// Otherwise, if this state advance was triggered by a
|
||||||
@ -868,6 +867,14 @@ func (c *ChannelArbitrator) stateStep(
|
|||||||
fallthrough
|
fallthrough
|
||||||
case remoteCloseTrigger:
|
case remoteCloseTrigger:
|
||||||
nextState = StateContractClosed
|
nextState = StateContractClosed
|
||||||
|
|
||||||
|
case breachCloseTrigger:
|
||||||
|
nextContractState, err := c.checkLegacyBreach()
|
||||||
|
if nextContractState == StateError {
|
||||||
|
return nextContractState, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nextState = nextContractState
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're in this state, then we've decided to broadcast the
|
// If we're in this state, then we've decided to broadcast the
|
||||||
@ -890,7 +897,23 @@ func (c *ChannelArbitrator) stateStep(
|
|||||||
c.cfg.ChanPoint, trigger, StateContractClosed)
|
c.cfg.ChanPoint, trigger, StateContractClosed)
|
||||||
return StateContractClosed, closeTx, nil
|
return StateContractClosed, closeTx, nil
|
||||||
|
|
||||||
case coopCloseTrigger, breachCloseTrigger:
|
case breachCloseTrigger:
|
||||||
|
nextContractState, err := c.checkLegacyBreach()
|
||||||
|
if nextContractState == StateError {
|
||||||
|
log.Infof("ChannelArbitrator(%v): unable to "+
|
||||||
|
"advance breach close resolution: %v",
|
||||||
|
c.cfg.ChanPoint, nextContractState)
|
||||||
|
return StateError, closeTx, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("ChannelArbitrator(%v): detected %s close "+
|
||||||
|
"after closing channel, fast-forwarding to %s"+
|
||||||
|
" to resolve contract", c.cfg.ChanPoint,
|
||||||
|
trigger, nextContractState)
|
||||||
|
|
||||||
|
return nextContractState, closeTx, nil
|
||||||
|
|
||||||
|
case coopCloseTrigger:
|
||||||
log.Infof("ChannelArbitrator(%v): detected %s "+
|
log.Infof("ChannelArbitrator(%v): detected %s "+
|
||||||
"close after closing channel, fast-forwarding "+
|
"close after closing channel, fast-forwarding "+
|
||||||
"to %s to resolve contract",
|
"to %s to resolve contract",
|
||||||
@ -994,10 +1017,18 @@ func (c *ChannelArbitrator) stateStep(
|
|||||||
case localCloseTrigger, remoteCloseTrigger:
|
case localCloseTrigger, remoteCloseTrigger:
|
||||||
nextState = StateContractClosed
|
nextState = StateContractClosed
|
||||||
|
|
||||||
// If a coop close or breach was confirmed, jump straight to
|
// If a coop close was confirmed, jump straight to the fully
|
||||||
// the fully resolved state.
|
// resolved state.
|
||||||
case coopCloseTrigger, breachCloseTrigger:
|
case coopCloseTrigger:
|
||||||
nextState = StateFullyResolved
|
nextState = StateFullyResolved
|
||||||
|
|
||||||
|
case breachCloseTrigger:
|
||||||
|
nextContractState, err := c.checkLegacyBreach()
|
||||||
|
if nextContractState == StateError {
|
||||||
|
return nextContractState, closeTx, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nextState = nextContractState
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("ChannelArbitrator(%v): trigger %v moving from "+
|
log.Infof("ChannelArbitrator(%v): trigger %v moving from "+
|
||||||
@ -1996,10 +2027,62 @@ func (c *ChannelArbitrator) prepContractResolutions(
|
|||||||
commitHash := contractResolutions.CommitHash
|
commitHash := contractResolutions.CommitHash
|
||||||
failureMsg := &lnwire.FailPermanentChannelFailure{}
|
failureMsg := &lnwire.FailPermanentChannelFailure{}
|
||||||
|
|
||||||
|
var htlcResolvers []ContractResolver
|
||||||
|
|
||||||
|
// We instantiate an anchor resolver if the commitment tx has an
|
||||||
|
// anchor.
|
||||||
|
if contractResolutions.AnchorResolution != nil {
|
||||||
|
anchorResolver := newAnchorResolver(
|
||||||
|
contractResolutions.AnchorResolution.AnchorSignDescriptor,
|
||||||
|
contractResolutions.AnchorResolution.CommitAnchor,
|
||||||
|
height, c.cfg.ChanPoint, resolverCfg,
|
||||||
|
)
|
||||||
|
htlcResolvers = append(htlcResolvers, anchorResolver)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is a breach close, we'll create a breach resolver, determine
|
||||||
|
// the htlc's to fail back, and exit. This is done because the other
|
||||||
|
// steps taken for non-breach-closes do not matter for breach-closes.
|
||||||
|
if contractResolutions.BreachResolution != nil {
|
||||||
|
breachResolver := newBreachResolver(resolverCfg)
|
||||||
|
htlcResolvers = append(htlcResolvers, breachResolver)
|
||||||
|
|
||||||
|
// We'll use the CommitSet, we'll fail back all outgoing HTLC's
|
||||||
|
// that exist on either of the remote commitments. The map is
|
||||||
|
// used to deduplicate any shared htlc's.
|
||||||
|
remoteOutgoing := make(map[uint64]channeldb.HTLC)
|
||||||
|
for htlcSetKey, htlcs := range confCommitSet.HtlcSets {
|
||||||
|
if !htlcSetKey.IsRemote {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, htlc := range htlcs {
|
||||||
|
if htlc.Incoming {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteOutgoing[htlc.HtlcIndex] = htlc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we'll loop over the map and create ResolutionMsgs for
|
||||||
|
// each of them.
|
||||||
|
for _, htlc := range remoteOutgoing {
|
||||||
|
failMsg := ResolutionMsg{
|
||||||
|
SourceChan: c.cfg.ShortChanID,
|
||||||
|
HtlcIndex: htlc.HtlcIndex,
|
||||||
|
Failure: failureMsg,
|
||||||
|
}
|
||||||
|
|
||||||
|
msgsToSend = append(msgsToSend, failMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return htlcResolvers, msgsToSend, nil
|
||||||
|
}
|
||||||
|
|
||||||
// For each HTLC, we'll either act immediately, meaning we'll instantly
|
// For each HTLC, we'll either act immediately, meaning we'll instantly
|
||||||
// fail the HTLC, or we'll act only once the transaction has been
|
// fail the HTLC, or we'll act only once the transaction has been
|
||||||
// confirmed, in which case we'll need an HTLC resolver.
|
// confirmed, in which case we'll need an HTLC resolver.
|
||||||
var htlcResolvers []ContractResolver
|
|
||||||
for htlcAction, htlcs := range htlcActions {
|
for htlcAction, htlcs := range htlcActions {
|
||||||
switch htlcAction {
|
switch htlcAction {
|
||||||
|
|
||||||
@ -2145,17 +2228,6 @@ func (c *ChannelArbitrator) prepContractResolutions(
|
|||||||
htlcResolvers = append(htlcResolvers, resolver)
|
htlcResolvers = append(htlcResolvers, resolver)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We instantiate an anchor resolver if the commitmentment tx has an
|
|
||||||
// anchor.
|
|
||||||
if contractResolutions.AnchorResolution != nil {
|
|
||||||
anchorResolver := newAnchorResolver(
|
|
||||||
contractResolutions.AnchorResolution.AnchorSignDescriptor,
|
|
||||||
contractResolutions.AnchorResolution.CommitAnchor,
|
|
||||||
height, c.cfg.ChanPoint, resolverCfg,
|
|
||||||
)
|
|
||||||
htlcResolvers = append(htlcResolvers, anchorResolver)
|
|
||||||
}
|
|
||||||
|
|
||||||
return htlcResolvers, msgsToSend, nil
|
return htlcResolvers, msgsToSend, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2574,14 +2646,59 @@ func (c *ChannelArbitrator) channelAttendant(bestHeight int32) {
|
|||||||
// the ChainWatcher and BreachArbiter, we don't have to do
|
// the ChainWatcher and BreachArbiter, we don't have to do
|
||||||
// anything in particular, so just advance our state and
|
// anything in particular, so just advance our state and
|
||||||
// gracefully exit.
|
// gracefully exit.
|
||||||
case <-c.cfg.ChainEvents.ContractBreach:
|
case breachInfo := <-c.cfg.ChainEvents.ContractBreach:
|
||||||
log.Infof("ChannelArbitrator(%v): remote party has "+
|
log.Infof("ChannelArbitrator(%v): remote party has "+
|
||||||
"breached channel!", c.cfg.ChanPoint)
|
"breached channel!", c.cfg.ChanPoint)
|
||||||
|
|
||||||
|
// In the breach case, we'll only have anchor and
|
||||||
|
// breach resolutions.
|
||||||
|
contractRes := &ContractResolutions{
|
||||||
|
CommitHash: breachInfo.CommitHash,
|
||||||
|
BreachResolution: breachInfo.BreachResolution,
|
||||||
|
AnchorResolution: breachInfo.AnchorResolution,
|
||||||
|
}
|
||||||
|
|
||||||
|
// We'll transition to the ContractClosed state and log
|
||||||
|
// the set of resolutions such that they can be turned
|
||||||
|
// into resolvers later on. We'll also insert the
|
||||||
|
// CommitSet of the latest set of commitments.
|
||||||
|
err := c.log.LogContractResolutions(contractRes)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Unable to write resolutions: %v",
|
||||||
|
err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = c.log.InsertConfirmedCommitSet(
|
||||||
|
&breachInfo.CommitSet,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Unable to write commit set: %v",
|
||||||
|
err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// The channel is finally marked pending closed here as
|
||||||
|
// the breacharbiter and channel arbitrator have
|
||||||
|
// persisted the relevant states.
|
||||||
|
closeSummary := &breachInfo.CloseSummary
|
||||||
|
err = c.cfg.MarkChannelClosed(
|
||||||
|
closeSummary,
|
||||||
|
channeldb.ChanStatusRemoteCloseInitiator,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Unable to mark channel closed: %v",
|
||||||
|
err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Breached channel=%v marked pending-closed",
|
||||||
|
breachInfo.BreachResolution.FundingOutPoint)
|
||||||
|
|
||||||
// We'll advance our state machine until it reaches a
|
// We'll advance our state machine until it reaches a
|
||||||
// terminal state.
|
// terminal state.
|
||||||
_, _, err := c.advanceState(
|
_, _, err = c.advanceState(
|
||||||
uint32(bestHeight), breachCloseTrigger, nil,
|
uint32(bestHeight), breachCloseTrigger,
|
||||||
|
&breachInfo.CommitSet,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Unable to advance state: %v", err)
|
log.Errorf("Unable to advance state: %v", err)
|
||||||
@ -2661,3 +2778,25 @@ func (c *ChannelArbitrator) channelAttendant(bestHeight int32) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkLegacyBreach returns StateFullyResolved if the channel was closed with
|
||||||
|
// a breach transaction before the channel arbitrator launched its own breach
|
||||||
|
// resolver. StateContractClosed is returned if this is a modern breach close
|
||||||
|
// with a breach resolver. StateError is returned if the log lookup failed.
|
||||||
|
func (c *ChannelArbitrator) checkLegacyBreach() (ArbitratorState, error) {
|
||||||
|
// A previous version of the channel arbitrator would make the breach
|
||||||
|
// close skip to StateFullyResolved. If there are no contract
|
||||||
|
// resolutions in the bolt arbitrator log, then this is an older breach
|
||||||
|
// close. Otherwise, if there are resolutions, the state should advance
|
||||||
|
// to StateContractClosed.
|
||||||
|
_, err := c.log.FetchContractResolutions()
|
||||||
|
if err == errNoResolutions {
|
||||||
|
// This is an older breach close still in the database.
|
||||||
|
return StateFullyResolved, nil
|
||||||
|
} else if err != nil {
|
||||||
|
return StateError, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a modern breach close with resolvers.
|
||||||
|
return StateContractClosed, nil
|
||||||
|
}
|
||||||
|
@ -205,6 +205,9 @@ type chanArbTestCtx struct {
|
|||||||
log ArbitratorLog
|
log ArbitratorLog
|
||||||
|
|
||||||
sweeper *mockSweeper
|
sweeper *mockSweeper
|
||||||
|
|
||||||
|
breachSubscribed chan struct{}
|
||||||
|
breachResolutionChan chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *chanArbTestCtx) CleanUp() {
|
func (c *chanArbTestCtx) CleanUp() {
|
||||||
@ -303,13 +306,17 @@ func withMarkClosed(markClosed func(*channeldb.ChannelCloseSummary,
|
|||||||
func createTestChannelArbitrator(t *testing.T, log ArbitratorLog,
|
func createTestChannelArbitrator(t *testing.T, log ArbitratorLog,
|
||||||
opts ...testChanArbOption) (*chanArbTestCtx, error) {
|
opts ...testChanArbOption) (*chanArbTestCtx, error) {
|
||||||
|
|
||||||
|
chanArbCtx := &chanArbTestCtx{
|
||||||
|
breachSubscribed: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
chanPoint := wire.OutPoint{}
|
chanPoint := wire.OutPoint{}
|
||||||
shortChanID := lnwire.ShortChannelID{}
|
shortChanID := lnwire.ShortChannelID{}
|
||||||
chanEvents := &ChainEventSubscription{
|
chanEvents := &ChainEventSubscription{
|
||||||
RemoteUnilateralClosure: make(chan *RemoteUnilateralCloseInfo, 1),
|
RemoteUnilateralClosure: make(chan *RemoteUnilateralCloseInfo, 1),
|
||||||
LocalUnilateralClosure: make(chan *LocalUnilateralCloseInfo, 1),
|
LocalUnilateralClosure: make(chan *LocalUnilateralCloseInfo, 1),
|
||||||
CooperativeClosure: make(chan *CooperativeCloseInfo, 1),
|
CooperativeClosure: make(chan *CooperativeCloseInfo, 1),
|
||||||
ContractBreach: make(chan *lnwallet.BreachRetribution, 1),
|
ContractBreach: make(chan *BreachCloseInfo, 1),
|
||||||
}
|
}
|
||||||
|
|
||||||
resolutionChan := make(chan []ResolutionMsg, 1)
|
resolutionChan := make(chan []ResolutionMsg, 1)
|
||||||
@ -346,6 +353,13 @@ func createTestChannelArbitrator(t *testing.T, log ArbitratorLog,
|
|||||||
|
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
|
SubscribeBreachComplete: func(op *wire.OutPoint,
|
||||||
|
c chan struct{}) (bool, error) {
|
||||||
|
|
||||||
|
chanArbCtx.breachResolutionChan = c
|
||||||
|
chanArbCtx.breachSubscribed <- struct{}{}
|
||||||
|
return false, nil
|
||||||
|
},
|
||||||
Clock: clock.NewDefaultClock(),
|
Clock: clock.NewDefaultClock(),
|
||||||
Sweeper: mockSweeper,
|
Sweeper: mockSweeper,
|
||||||
}
|
}
|
||||||
@ -425,16 +439,16 @@ func createTestChannelArbitrator(t *testing.T, log ArbitratorLog,
|
|||||||
|
|
||||||
chanArb := NewChannelArbitrator(*arbCfg, htlcSets, log)
|
chanArb := NewChannelArbitrator(*arbCfg, htlcSets, log)
|
||||||
|
|
||||||
return &chanArbTestCtx{
|
chanArbCtx.t = t
|
||||||
t: t,
|
chanArbCtx.chanArb = chanArb
|
||||||
chanArb: chanArb,
|
chanArbCtx.cleanUp = cleanUp
|
||||||
cleanUp: cleanUp,
|
chanArbCtx.resolvedChan = resolvedChan
|
||||||
resolvedChan: resolvedChan,
|
chanArbCtx.resolutions = resolutionChan
|
||||||
resolutions: resolutionChan,
|
chanArbCtx.log = log
|
||||||
log: log,
|
chanArbCtx.incubationRequests = incubateChan
|
||||||
incubationRequests: incubateChan,
|
chanArbCtx.sweeper = mockSweeper
|
||||||
sweeper: mockSweeper,
|
|
||||||
}, nil
|
return chanArbCtx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestChannelArbitratorCooperativeClose tests that the ChannelArbitertor
|
// TestChannelArbitratorCooperativeClose tests that the ChannelArbitertor
|
||||||
@ -661,11 +675,13 @@ func TestChannelArbitratorLocalForceClose(t *testing.T) {
|
|||||||
|
|
||||||
// TestChannelArbitratorBreachClose tests that the ChannelArbitrator goes
|
// TestChannelArbitratorBreachClose tests that the ChannelArbitrator goes
|
||||||
// through the expected states in case we notice a breach in the chain, and
|
// through the expected states in case we notice a breach in the chain, and
|
||||||
// gracefully exits.
|
// is able to properly progress the breachResolver and anchorResolver to a
|
||||||
|
// successful resolution.
|
||||||
func TestChannelArbitratorBreachClose(t *testing.T) {
|
func TestChannelArbitratorBreachClose(t *testing.T) {
|
||||||
log := &mockArbitratorLog{
|
log := &mockArbitratorLog{
|
||||||
state: StateDefault,
|
state: StateDefault,
|
||||||
newStates: make(chan ArbitratorState, 5),
|
newStates: make(chan ArbitratorState, 5),
|
||||||
|
resolvers: make(map[ContractResolver]struct{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
chanArbCtx, err := createTestChannelArbitrator(t, log)
|
chanArbCtx, err := createTestChannelArbitrator(t, log)
|
||||||
@ -673,6 +689,8 @@ func TestChannelArbitratorBreachClose(t *testing.T) {
|
|||||||
t.Fatalf("unable to create ChannelArbitrator: %v", err)
|
t.Fatalf("unable to create ChannelArbitrator: %v", err)
|
||||||
}
|
}
|
||||||
chanArb := chanArbCtx.chanArb
|
chanArb := chanArbCtx.chanArb
|
||||||
|
chanArb.cfg.PreimageDB = newMockWitnessBeacon()
|
||||||
|
chanArb.cfg.Registry = &mockRegistry{}
|
||||||
|
|
||||||
if err := chanArb.Start(nil); err != nil {
|
if err := chanArb.Start(nil); err != nil {
|
||||||
t.Fatalf("unable to start ChannelArbitrator: %v", err)
|
t.Fatalf("unable to start ChannelArbitrator: %v", err)
|
||||||
@ -686,13 +704,99 @@ func TestChannelArbitratorBreachClose(t *testing.T) {
|
|||||||
// It should start out in the default state.
|
// It should start out in the default state.
|
||||||
chanArbCtx.AssertState(StateDefault)
|
chanArbCtx.AssertState(StateDefault)
|
||||||
|
|
||||||
// Send a breach close event.
|
// We create two HTLCs, one incoming and one outgoing. We will later
|
||||||
chanArb.cfg.ChainEvents.ContractBreach <- &lnwallet.BreachRetribution{}
|
// assert that we only receive a ResolutionMsg for the outgoing HTLC.
|
||||||
|
outgoingIdx := uint64(2)
|
||||||
|
|
||||||
// It should transition StateDefault -> StateFullyResolved.
|
rHash1 := [lntypes.PreimageSize]byte{1, 2, 3}
|
||||||
chanArbCtx.AssertStateTransitions(
|
htlc1 := channeldb.HTLC{
|
||||||
StateFullyResolved,
|
RHash: rHash1,
|
||||||
)
|
OutputIndex: 2,
|
||||||
|
Incoming: false,
|
||||||
|
HtlcIndex: outgoingIdx,
|
||||||
|
LogIndex: 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
rHash2 := [lntypes.PreimageSize]byte{2, 2, 2}
|
||||||
|
htlc2 := channeldb.HTLC{
|
||||||
|
RHash: rHash2,
|
||||||
|
OutputIndex: 3,
|
||||||
|
Incoming: true,
|
||||||
|
HtlcIndex: 3,
|
||||||
|
LogIndex: 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
anchorRes := &lnwallet.AnchorResolution{
|
||||||
|
AnchorSignDescriptor: input.SignDescriptor{
|
||||||
|
Output: &wire.TxOut{Value: 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the BreachCloseInfo that the chain_watcher would normally
|
||||||
|
// send to the channel_arbitrator.
|
||||||
|
breachInfo := &BreachCloseInfo{
|
||||||
|
BreachResolution: &BreachResolution{
|
||||||
|
FundingOutPoint: wire.OutPoint{},
|
||||||
|
},
|
||||||
|
AnchorResolution: anchorRes,
|
||||||
|
CommitSet: CommitSet{
|
||||||
|
ConfCommitKey: &RemoteHtlcSet,
|
||||||
|
HtlcSets: map[HtlcSetKey][]channeldb.HTLC{
|
||||||
|
RemoteHtlcSet: {htlc1, htlc2},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CommitHash: chainhash.Hash{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a breach close event.
|
||||||
|
chanArb.cfg.ChainEvents.ContractBreach <- breachInfo
|
||||||
|
|
||||||
|
// It should transition StateDefault -> StateContractClosed.
|
||||||
|
chanArbCtx.AssertStateTransitions(StateContractClosed)
|
||||||
|
|
||||||
|
// We should receive one ResolutionMsg as there was only one outgoing
|
||||||
|
// HTLC at the time of the breach.
|
||||||
|
select {
|
||||||
|
case res := <-chanArbCtx.resolutions:
|
||||||
|
require.Equal(t, 1, len(res))
|
||||||
|
require.Equal(t, outgoingIdx, res[0].HtlcIndex)
|
||||||
|
case <-time.After(5 * time.Second):
|
||||||
|
t.Fatal("expected to receive a resolution msg")
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should now transition from StateContractClosed to
|
||||||
|
// StateWaitingFullResolution.
|
||||||
|
chanArbCtx.AssertStateTransitions(StateWaitingFullResolution)
|
||||||
|
|
||||||
|
// One of the resolvers should be an anchor resolver and the other
|
||||||
|
// should be a breach resolver.
|
||||||
|
require.Equal(t, 2, len(chanArb.activeResolvers))
|
||||||
|
|
||||||
|
var anchorExists, breachExists bool
|
||||||
|
for _, resolver := range chanArb.activeResolvers {
|
||||||
|
switch resolver.(type) {
|
||||||
|
case *anchorResolver:
|
||||||
|
anchorExists = true
|
||||||
|
case *breachResolver:
|
||||||
|
breachExists = true
|
||||||
|
default:
|
||||||
|
t.Fatalf("did not expect resolver %T", resolver)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.True(t, anchorExists && breachExists)
|
||||||
|
|
||||||
|
// The anchor resolver is expected to re-offer the anchor input to the
|
||||||
|
// sweeper.
|
||||||
|
<-chanArbCtx.sweeper.sweptInputs
|
||||||
|
|
||||||
|
// Wait for SubscribeBreachComplete to be called.
|
||||||
|
<-chanArbCtx.breachSubscribed
|
||||||
|
|
||||||
|
// We'll now close the breach channel so that the state transitions to
|
||||||
|
// StateFullyResolved.
|
||||||
|
close(chanArbCtx.breachResolutionChan)
|
||||||
|
|
||||||
|
chanArbCtx.AssertStateTransitions(StateFullyResolved)
|
||||||
|
|
||||||
// It should also mark the channel as resolved.
|
// It should also mark the channel as resolved.
|
||||||
select {
|
select {
|
||||||
@ -1318,12 +1422,14 @@ func TestChannelArbitratorPersistence(t *testing.T) {
|
|||||||
// TestChannelArbitratorForceCloseBreachedChannel tests that the channel
|
// TestChannelArbitratorForceCloseBreachedChannel tests that the channel
|
||||||
// arbitrator is able to handle a channel in the process of being force closed
|
// arbitrator is able to handle a channel in the process of being force closed
|
||||||
// is breached by the remote node. In these cases we expect the
|
// is breached by the remote node. In these cases we expect the
|
||||||
// ChannelArbitrator to gracefully exit, as the breach is handled by other
|
// ChannelArbitrator to properly execute the breachResolver flow and then
|
||||||
// subsystems.
|
// gracefully exit once the breachResolver receives the signal from what would
|
||||||
|
// normally be the breacharbiter.
|
||||||
func TestChannelArbitratorForceCloseBreachedChannel(t *testing.T) {
|
func TestChannelArbitratorForceCloseBreachedChannel(t *testing.T) {
|
||||||
log := &mockArbitratorLog{
|
log := &mockArbitratorLog{
|
||||||
state: StateDefault,
|
state: StateDefault,
|
||||||
newStates: make(chan ArbitratorState, 5),
|
newStates: make(chan ArbitratorState, 5),
|
||||||
|
resolvers: make(map[ContractResolver]struct{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
chanArbCtx, err := createTestChannelArbitrator(t, log)
|
chanArbCtx, err := createTestChannelArbitrator(t, log)
|
||||||
@ -1389,6 +1495,20 @@ func TestChannelArbitratorForceCloseBreachedChannel(t *testing.T) {
|
|||||||
t.Fatalf("no response received")
|
t.Fatalf("no response received")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Before restarting, we'll need to modify the arbitrator log to have
|
||||||
|
// a set of contract resolutions and a commit set.
|
||||||
|
log.resolutions = &ContractResolutions{
|
||||||
|
BreachResolution: &BreachResolution{
|
||||||
|
FundingOutPoint: wire.OutPoint{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
log.commitSet = &CommitSet{
|
||||||
|
ConfCommitKey: &RemoteHtlcSet,
|
||||||
|
HtlcSets: map[HtlcSetKey][]channeldb.HTLC{
|
||||||
|
RemoteHtlcSet: {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// We mimic that the channel is breached while the channel arbitrator
|
// We mimic that the channel is breached while the channel arbitrator
|
||||||
// is down. This means that on restart it will be started with a
|
// is down. This means that on restart it will be started with a
|
||||||
// pending close channel, of type BreachClose.
|
// pending close channel, of type BreachClose.
|
||||||
@ -1402,7 +1522,18 @@ func TestChannelArbitratorForceCloseBreachedChannel(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer chanArbCtx.CleanUp()
|
defer chanArbCtx.CleanUp()
|
||||||
|
|
||||||
// Finally it should advance to StateFullyResolved.
|
// We should transition to StateContractClosed.
|
||||||
|
chanArbCtx.AssertStateTransitions(
|
||||||
|
StateContractClosed, StateWaitingFullResolution,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Wait for SubscribeBreachComplete to be called.
|
||||||
|
<-chanArbCtx.breachSubscribed
|
||||||
|
|
||||||
|
// We'll close the breachResolutionChan to cleanup the breachResolver
|
||||||
|
// and make the state transition to StateFullyResolved.
|
||||||
|
close(chanArbCtx.breachResolutionChan)
|
||||||
|
|
||||||
chanArbCtx.AssertStateTransitions(StateFullyResolved)
|
chanArbCtx.AssertStateTransitions(StateFullyResolved)
|
||||||
|
|
||||||
// It should also mark the channel as resolved.
|
// It should also mark the channel as resolved.
|
||||||
|
@ -41,6 +41,9 @@ Postgres](https://github.com/lightningnetwork/lnd/pull/6111)
|
|||||||
|
|
||||||
## Bug fixes
|
## Bug fixes
|
||||||
|
|
||||||
|
* [A new resolver for breach closes was introduced that handles sweeping
|
||||||
|
anchor outputs and failing back HTLCs.](https://github.com/lightningnetwork/lnd/pull/6158)
|
||||||
|
|
||||||
* [Return the nearest known fee rate when a given conf target cannot be found
|
* [Return the nearest known fee rate when a given conf target cannot be found
|
||||||
from Web API fee estimator.](https://github.com/lightningnetwork/lnd/pull/6062)
|
from Web API fee estimator.](https://github.com/lightningnetwork/lnd/pull/6062)
|
||||||
|
|
||||||
|
42
server.go
42
server.go
@ -1026,6 +1026,20 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
|||||||
// breach events from the ChannelArbitrator to the breachArbiter,
|
// breach events from the ChannelArbitrator to the breachArbiter,
|
||||||
contractBreaches := make(chan *contractcourt.ContractBreachEvent, 1)
|
contractBreaches := make(chan *contractcourt.ContractBreachEvent, 1)
|
||||||
|
|
||||||
|
s.breachArbiter = contractcourt.NewBreachArbiter(&contractcourt.BreachConfig{
|
||||||
|
CloseLink: closeLink,
|
||||||
|
DB: s.chanStateDB,
|
||||||
|
Estimator: s.cc.FeeEstimator,
|
||||||
|
GenSweepScript: newSweepPkScriptGen(cc.Wallet),
|
||||||
|
Notifier: cc.ChainNotifier,
|
||||||
|
PublishTransaction: cc.Wallet.PublishTransaction,
|
||||||
|
ContractBreaches: contractBreaches,
|
||||||
|
Signer: cc.Wallet.Cfg.Signer,
|
||||||
|
Store: contractcourt.NewRetributionStore(
|
||||||
|
dbs.ChanStateDB,
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
s.chainArb = contractcourt.NewChainArbitrator(contractcourt.ChainArbitratorConfig{
|
s.chainArb = contractcourt.NewChainArbitrator(contractcourt.ChainArbitratorConfig{
|
||||||
ChainHash: *s.cfg.ActiveNetParams.GenesisHash,
|
ChainHash: *s.cfg.ActiveNetParams.GenesisHash,
|
||||||
IncomingBroadcastDelta: lncfg.DefaultIncomingBroadcastDelta,
|
IncomingBroadcastDelta: lncfg.DefaultIncomingBroadcastDelta,
|
||||||
@ -1074,8 +1088,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
|||||||
},
|
},
|
||||||
IsOurAddress: cc.Wallet.IsOurAddress,
|
IsOurAddress: cc.Wallet.IsOurAddress,
|
||||||
ContractBreach: func(chanPoint wire.OutPoint,
|
ContractBreach: func(chanPoint wire.OutPoint,
|
||||||
breachRet *lnwallet.BreachRetribution,
|
breachRet *lnwallet.BreachRetribution) error {
|
||||||
markClosed func() error) error {
|
|
||||||
|
|
||||||
// processACK will handle the breachArbiter ACKing the
|
// processACK will handle the breachArbiter ACKing the
|
||||||
// event.
|
// event.
|
||||||
@ -1087,8 +1100,9 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the breachArbiter successfully handled
|
// If the breachArbiter successfully handled
|
||||||
// the event, we can mark the channel closed.
|
// the event, we can signal that the handoff
|
||||||
finalErr <- markClosed()
|
// was successful.
|
||||||
|
finalErr <- nil
|
||||||
}
|
}
|
||||||
|
|
||||||
event := &contractcourt.ContractBreachEvent{
|
event := &contractcourt.ContractBreachEvent{
|
||||||
@ -1104,9 +1118,8 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
|||||||
return ErrServerShuttingDown
|
return ErrServerShuttingDown
|
||||||
}
|
}
|
||||||
|
|
||||||
// We'll wait for a final error to be available, either
|
// We'll wait for a final error to be available from
|
||||||
// from the breachArbiter or from our markClosed
|
// the breachArbiter.
|
||||||
// function closure.
|
|
||||||
select {
|
select {
|
||||||
case err := <-finalErr:
|
case err := <-finalErr:
|
||||||
return err
|
return err
|
||||||
@ -1125,22 +1138,9 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
|||||||
PaymentsExpirationGracePeriod: cfg.PaymentsExpirationGracePeriod,
|
PaymentsExpirationGracePeriod: cfg.PaymentsExpirationGracePeriod,
|
||||||
IsForwardedHTLC: s.htlcSwitch.IsForwardedHTLC,
|
IsForwardedHTLC: s.htlcSwitch.IsForwardedHTLC,
|
||||||
Clock: clock.NewDefaultClock(),
|
Clock: clock.NewDefaultClock(),
|
||||||
|
SubscribeBreachComplete: s.breachArbiter.SubscribeBreachComplete,
|
||||||
}, dbs.ChanStateDB)
|
}, dbs.ChanStateDB)
|
||||||
|
|
||||||
s.breachArbiter = contractcourt.NewBreachArbiter(&contractcourt.BreachConfig{
|
|
||||||
CloseLink: closeLink,
|
|
||||||
DB: s.chanStateDB,
|
|
||||||
Estimator: s.cc.FeeEstimator,
|
|
||||||
GenSweepScript: newSweepPkScriptGen(cc.Wallet),
|
|
||||||
Notifier: cc.ChainNotifier,
|
|
||||||
PublishTransaction: cc.Wallet.PublishTransaction,
|
|
||||||
ContractBreaches: contractBreaches,
|
|
||||||
Signer: cc.Wallet.Cfg.Signer,
|
|
||||||
Store: contractcourt.NewRetributionStore(
|
|
||||||
dbs.ChanStateDB,
|
|
||||||
),
|
|
||||||
})
|
|
||||||
|
|
||||||
// Select the configuration and furnding parameters for Bitcoin or
|
// Select the configuration and furnding parameters for Bitcoin or
|
||||||
// Litecoin, depending on the primary registered chain.
|
// Litecoin, depending on the primary registered chain.
|
||||||
primaryChain := cfg.registeredChains.PrimaryChain()
|
primaryChain := cfg.registeredChains.PrimaryChain()
|
||||||
|
Loading…
Reference in New Issue
Block a user