mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-02-22 14:22:37 +01:00
Merge pull request #5618 from Crypt-iQ/coop_switch_sync_08092021
multi: optimistically shutdown link during coop close
This commit is contained in:
commit
ac5f88bae8
12 changed files with 490 additions and 57 deletions
|
@ -131,6 +131,8 @@ you.
|
||||||
* Locally force closed channels are now [kept in the channel.backup file until
|
* Locally force closed channels are now [kept in the channel.backup file until
|
||||||
their time lock has fully matured](https://github.com/lightningnetwork/lnd/pull/5528).
|
their time lock has fully matured](https://github.com/lightningnetwork/lnd/pull/5528).
|
||||||
|
|
||||||
|
* [Cooperative closes optimistically shutdown the associated `link` before closing the channel.](https://github.com/lightningnetwork/lnd/pull/5618)
|
||||||
|
|
||||||
## Build System
|
## Build System
|
||||||
|
|
||||||
* [A new pre-submit check has been
|
* [A new pre-submit check has been
|
||||||
|
|
|
@ -88,6 +88,11 @@ type ChannelUpdateHandler interface {
|
||||||
// MayAddOutgoingHtlc returns an error if we may not add an outgoing
|
// MayAddOutgoingHtlc returns an error if we may not add an outgoing
|
||||||
// htlc to the channel.
|
// htlc to the channel.
|
||||||
MayAddOutgoingHtlc() error
|
MayAddOutgoingHtlc() error
|
||||||
|
|
||||||
|
// ShutdownIfChannelClean shuts the link down if the channel state is
|
||||||
|
// clean. This can be used with dynamic commitment negotiation or coop
|
||||||
|
// close negotiation which require a clean channel state.
|
||||||
|
ShutdownIfChannelClean() error
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChannelLink is an interface which represents the subsystem for managing the
|
// ChannelLink is an interface which represents the subsystem for managing the
|
||||||
|
|
|
@ -301,6 +301,13 @@ type localUpdateAddMsg struct {
|
||||||
err chan error
|
err chan error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// shutdownReq contains an error channel that will be used by the channelLink
|
||||||
|
// to send an error if shutdown failed. If shutdown succeeded, the channel will
|
||||||
|
// be closed.
|
||||||
|
type shutdownReq struct {
|
||||||
|
err chan error
|
||||||
|
}
|
||||||
|
|
||||||
// channelLink is the service which drives a channel's commitment update
|
// channelLink is the service which drives a channel's commitment update
|
||||||
// state-machine. In the event that an HTLC needs to be propagated to another
|
// state-machine. In the event that an HTLC needs to be propagated to another
|
||||||
// link, the forward handler from config is used which sends HTLC to the
|
// link, the forward handler from config is used which sends HTLC to the
|
||||||
|
@ -369,6 +376,10 @@ type channelLink struct {
|
||||||
// sub-systems with the latest set of active HTLC's on our channel.
|
// sub-systems with the latest set of active HTLC's on our channel.
|
||||||
htlcUpdates chan *contractcourt.ContractUpdate
|
htlcUpdates chan *contractcourt.ContractUpdate
|
||||||
|
|
||||||
|
// shutdownRequest is a channel that the channelLink will listen on to
|
||||||
|
// service shutdown requests from ShutdownIfChannelClean calls.
|
||||||
|
shutdownRequest chan *shutdownReq
|
||||||
|
|
||||||
// updateFeeTimer is the timer responsible for updating the link's
|
// updateFeeTimer is the timer responsible for updating the link's
|
||||||
// commitment fee every time it fires.
|
// commitment fee every time it fires.
|
||||||
updateFeeTimer *time.Timer
|
updateFeeTimer *time.Timer
|
||||||
|
@ -414,12 +425,13 @@ func NewChannelLink(cfg ChannelLinkConfig,
|
||||||
channel: channel,
|
channel: channel,
|
||||||
shortChanID: channel.ShortChanID(),
|
shortChanID: channel.ShortChanID(),
|
||||||
// TODO(roasbeef): just do reserve here?
|
// TODO(roasbeef): just do reserve here?
|
||||||
htlcUpdates: make(chan *contractcourt.ContractUpdate),
|
htlcUpdates: make(chan *contractcourt.ContractUpdate),
|
||||||
hodlMap: make(map[channeldb.CircuitKey]hodlHtlc),
|
shutdownRequest: make(chan *shutdownReq),
|
||||||
hodlQueue: queue.NewConcurrentQueue(10),
|
hodlMap: make(map[channeldb.CircuitKey]hodlHtlc),
|
||||||
log: build.NewPrefixLog(logPrefix, log),
|
hodlQueue: queue.NewConcurrentQueue(10),
|
||||||
quit: make(chan struct{}),
|
log: build.NewPrefixLog(logPrefix, log),
|
||||||
localUpdateAdd: make(chan *localUpdateAddMsg),
|
quit: make(chan struct{}),
|
||||||
|
localUpdateAdd: make(chan *localUpdateAddMsg),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1176,6 +1188,20 @@ func (l *channelLink) htlcManager() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case req := <-l.shutdownRequest:
|
||||||
|
// If the channel is clean, we send nil on the err chan
|
||||||
|
// and return to prevent the htlcManager goroutine from
|
||||||
|
// processing any more updates. The full link shutdown
|
||||||
|
// will be triggered by RemoveLink in the peer.
|
||||||
|
if l.channel.IsChannelClean() {
|
||||||
|
req.err <- nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, the channel has lingering updates, send
|
||||||
|
// an error and continue.
|
||||||
|
req.err <- ErrLinkFailedShutdown
|
||||||
|
|
||||||
case <-l.quit:
|
case <-l.quit:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -2434,6 +2460,29 @@ func (l *channelLink) HandleChannelUpdate(message lnwire.Message) {
|
||||||
l.mailBox.AddMessage(message)
|
l.mailBox.AddMessage(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ShutdownIfChannelClean triggers a link shutdown if the channel is in a clean
|
||||||
|
// state and errors if the channel has lingering updates.
|
||||||
|
//
|
||||||
|
// NOTE: Part of the ChannelUpdateHandler interface.
|
||||||
|
func (l *channelLink) ShutdownIfChannelClean() error {
|
||||||
|
errChan := make(chan error, 1)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case l.shutdownRequest <- &shutdownReq{
|
||||||
|
err: errChan,
|
||||||
|
}:
|
||||||
|
case <-l.quit:
|
||||||
|
return ErrLinkShuttingDown
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-errChan:
|
||||||
|
return err
|
||||||
|
case <-l.quit:
|
||||||
|
return ErrLinkShuttingDown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// updateChannelFee updates the commitment fee-per-kw on this channel by
|
// updateChannelFee updates the commitment fee-per-kw on this channel by
|
||||||
// committing to an update_fee message.
|
// committing to an update_fee message.
|
||||||
func (l *channelLink) updateChannelFee(feePerKw chainfee.SatPerKWeight) error {
|
func (l *channelLink) updateChannelFee(feePerKw chainfee.SatPerKWeight) error {
|
||||||
|
|
|
@ -6532,6 +6532,91 @@ func TestPendingCommitTicker(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestShutdownIfChannelClean tests that a link will exit the htlcManager loop
|
||||||
|
// if and only if the underlying channel state is clean.
|
||||||
|
func TestShutdownIfChannelClean(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
const chanAmt = btcutil.SatoshiPerBitcoin * 5
|
||||||
|
const chanReserve = btcutil.SatoshiPerBitcoin * 1
|
||||||
|
aliceLink, bobChannel, batchTicker, start, cleanUp, _, err :=
|
||||||
|
newSingleLinkTestHarness(chanAmt, chanReserve)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var (
|
||||||
|
coreLink = aliceLink.(*channelLink)
|
||||||
|
aliceMsgs = coreLink.cfg.Peer.(*mockPeer).sentMsgs
|
||||||
|
)
|
||||||
|
|
||||||
|
shutdownAssert := func(expectedErr error) {
|
||||||
|
err = aliceLink.ShutdownIfChannelClean()
|
||||||
|
if expectedErr != nil {
|
||||||
|
require.Error(t, err, expectedErr)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = start()
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer cleanUp()
|
||||||
|
|
||||||
|
ctx := linkTestContext{
|
||||||
|
t: t,
|
||||||
|
aliceLink: aliceLink,
|
||||||
|
bobChannel: bobChannel,
|
||||||
|
aliceMsgs: aliceMsgs,
|
||||||
|
}
|
||||||
|
|
||||||
|
// First send an HTLC from Bob to Alice and assert that the link can't
|
||||||
|
// be shutdown while the update is outstanding.
|
||||||
|
htlc := generateHtlc(t, coreLink, 0)
|
||||||
|
|
||||||
|
// <---add-----
|
||||||
|
ctx.sendHtlcBobToAlice(htlc)
|
||||||
|
// <---sig-----
|
||||||
|
ctx.sendCommitSigBobToAlice(1)
|
||||||
|
// ----rev---->
|
||||||
|
ctx.receiveRevAndAckAliceToBob()
|
||||||
|
shutdownAssert(ErrLinkFailedShutdown)
|
||||||
|
|
||||||
|
// ----sig---->
|
||||||
|
ctx.receiveCommitSigAliceToBob(1)
|
||||||
|
shutdownAssert(ErrLinkFailedShutdown)
|
||||||
|
|
||||||
|
// <---rev-----
|
||||||
|
ctx.sendRevAndAckBobToAlice()
|
||||||
|
shutdownAssert(ErrLinkFailedShutdown)
|
||||||
|
|
||||||
|
// ---settle-->
|
||||||
|
ctx.receiveSettleAliceToBob()
|
||||||
|
shutdownAssert(ErrLinkFailedShutdown)
|
||||||
|
|
||||||
|
// ----sig---->
|
||||||
|
ctx.receiveCommitSigAliceToBob(0)
|
||||||
|
shutdownAssert(ErrLinkFailedShutdown)
|
||||||
|
|
||||||
|
// <---rev-----
|
||||||
|
ctx.sendRevAndAckBobToAlice()
|
||||||
|
shutdownAssert(ErrLinkFailedShutdown)
|
||||||
|
|
||||||
|
// <---sig-----
|
||||||
|
ctx.sendCommitSigBobToAlice(0)
|
||||||
|
shutdownAssert(ErrLinkFailedShutdown)
|
||||||
|
|
||||||
|
// ----rev---->
|
||||||
|
ctx.receiveRevAndAckAliceToBob()
|
||||||
|
shutdownAssert(nil)
|
||||||
|
|
||||||
|
// Now that the link has exited the htlcManager loop, attempt to
|
||||||
|
// trigger the batch ticker. It should not be possible.
|
||||||
|
select {
|
||||||
|
case batchTicker <- time.Now():
|
||||||
|
t.Fatalf("expected batch ticker to be inactive")
|
||||||
|
case <-time.After(5 * time.Second):
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// assertFailureCode asserts that an error is of type ClearTextError and that
|
// assertFailureCode asserts that an error is of type ClearTextError and that
|
||||||
// the failure code is as expected.
|
// the failure code is as expected.
|
||||||
func assertFailureCode(t *testing.T, err error, code lnwire.FailCode) {
|
func assertFailureCode(t *testing.T, err error, code lnwire.FailCode) {
|
||||||
|
|
|
@ -5,6 +5,9 @@ import "github.com/go-errors/errors"
|
||||||
var (
|
var (
|
||||||
// ErrLinkShuttingDown signals that the link is shutting down.
|
// ErrLinkShuttingDown signals that the link is shutting down.
|
||||||
ErrLinkShuttingDown = errors.New("link shutting down")
|
ErrLinkShuttingDown = errors.New("link shutting down")
|
||||||
|
|
||||||
|
// ErrLinkFailedShutdown signals that a requested shutdown failed.
|
||||||
|
ErrLinkFailedShutdown = errors.New("link failed to shutdown")
|
||||||
)
|
)
|
||||||
|
|
||||||
// errorCode encodes the possible types of errors that will make us fail the
|
// errorCode encodes the possible types of errors that will make us fail the
|
||||||
|
|
|
@ -757,6 +757,7 @@ func (f *mockChannelLink) ChannelPoint() *wire.OutPoint { return
|
||||||
func (f *mockChannelLink) Stop() {}
|
func (f *mockChannelLink) Stop() {}
|
||||||
func (f *mockChannelLink) EligibleToForward() bool { return f.eligible }
|
func (f *mockChannelLink) EligibleToForward() bool { return f.eligible }
|
||||||
func (f *mockChannelLink) MayAddOutgoingHtlc() error { return nil }
|
func (f *mockChannelLink) MayAddOutgoingHtlc() error { return nil }
|
||||||
|
func (f *mockChannelLink) ShutdownIfChannelClean() error { return nil }
|
||||||
func (f *mockChannelLink) setLiveShortChanID(sid lnwire.ShortChannelID) { f.shortChanID = sid }
|
func (f *mockChannelLink) setLiveShortChanID(sid lnwire.ShortChannelID) { f.shortChanID = sid }
|
||||||
func (f *mockChannelLink) UpdateShortChanID() (lnwire.ShortChannelID, error) {
|
func (f *mockChannelLink) UpdateShortChanID() (lnwire.ShortChannelID, error) {
|
||||||
f.eligible = true
|
f.eligible = true
|
||||||
|
|
|
@ -79,11 +79,6 @@ type ChanCloseCfg struct {
|
||||||
// Channel is the channel that should be closed.
|
// Channel is the channel that should be closed.
|
||||||
Channel *lnwallet.LightningChannel
|
Channel *lnwallet.LightningChannel
|
||||||
|
|
||||||
// UnregisterChannel is a function closure that allows the ChanCloser to
|
|
||||||
// unregister a channel. Once this has been done, no further HTLC's should
|
|
||||||
// be routed through the channel.
|
|
||||||
UnregisterChannel func(lnwire.ChannelID)
|
|
||||||
|
|
||||||
// BroadcastTx broadcasts the passed transaction to the network.
|
// BroadcastTx broadcasts the passed transaction to the network.
|
||||||
BroadcastTx func(*wire.MsgTx, string) error
|
BroadcastTx func(*wire.MsgTx, string) error
|
||||||
|
|
||||||
|
@ -212,8 +207,6 @@ func (c *ChanCloser) initChanShutdown() (*lnwire.Shutdown, error) {
|
||||||
// closing script.
|
// closing script.
|
||||||
shutdown := lnwire.NewShutdown(c.cid, c.localDeliveryScript)
|
shutdown := lnwire.NewShutdown(c.cid, c.localDeliveryScript)
|
||||||
|
|
||||||
// TODO(roasbeef): err if channel has htlc's?
|
|
||||||
|
|
||||||
// Before closing, we'll attempt to send a disable update for the channel.
|
// Before closing, we'll attempt to send a disable update for the channel.
|
||||||
// We do so before closing the channel as otherwise the current edge policy
|
// We do so before closing the channel as otherwise the current edge policy
|
||||||
// won't be retrievable from the graph.
|
// won't be retrievable from the graph.
|
||||||
|
@ -222,10 +215,6 @@ func (c *ChanCloser) initChanShutdown() (*lnwire.Shutdown, error) {
|
||||||
c.chanPoint, err)
|
c.chanPoint, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Before returning the shutdown message, we'll unregister the channel to
|
|
||||||
// ensure that it isn't seen as usable within the system.
|
|
||||||
c.cfg.UnregisterChannel(c.cid)
|
|
||||||
|
|
||||||
// Before continuing, mark the channel as cooperatively closed with a nil
|
// Before continuing, mark the channel as cooperatively closed with a nil
|
||||||
// txn. Even though we haven't negotiated the final txn, this guarantees
|
// txn. Even though we haven't negotiated the final txn, this guarantees
|
||||||
// that our listchannels rpc will be externally consistent, and reflect
|
// that our listchannels rpc will be externally consistent, and reflect
|
||||||
|
|
|
@ -4470,6 +4470,50 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSig lnwire.Sig,
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsChannelClean returns true if neither side has pending commitments, neither
|
||||||
|
// side has HTLC's, and all updates are locked in irrevocably. Internally, it
|
||||||
|
// utilizes the oweCommitment function by calling it for local and remote
|
||||||
|
// evaluation. We check if we have a pending commitment for our local state
|
||||||
|
// since this function may be called by sub-systems that are not the link (e.g.
|
||||||
|
// the rpcserver), and the ReceiveNewCommitment & RevokeCurrentCommitment calls
|
||||||
|
// are not atomic, even though link processing ensures no updates can happen in
|
||||||
|
// between.
|
||||||
|
func (lc *LightningChannel) IsChannelClean() bool {
|
||||||
|
lc.RLock()
|
||||||
|
defer lc.RUnlock()
|
||||||
|
|
||||||
|
// Check whether we have a pending commitment for our local state.
|
||||||
|
if lc.localCommitChain.hasUnackedCommitment() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether our counterparty has a pending commitment for their
|
||||||
|
// state.
|
||||||
|
if lc.remoteCommitChain.hasUnackedCommitment() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// We call ActiveHtlcs to ensure there are no HTLCs on either
|
||||||
|
// commitment.
|
||||||
|
if len(lc.channelState.ActiveHtlcs()) != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now check that both local and remote commitments are signing the
|
||||||
|
// same updates.
|
||||||
|
if lc.oweCommitment(true) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if lc.oweCommitment(false) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we reached this point, the channel has no HTLCs and both
|
||||||
|
// commitments sign the same updates.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// OweCommitment returns a boolean value reflecting whether we need to send
|
// OweCommitment returns a boolean value reflecting whether we need to send
|
||||||
// out a commitment signature because there are outstanding local updates and/or
|
// out a commitment signature because there are outstanding local updates and/or
|
||||||
// updates in the local commit tx that aren't reflected in the remote commit tx
|
// updates in the local commit tx that aren't reflected in the remote commit tx
|
||||||
|
|
|
@ -9669,3 +9669,150 @@ func TestMayAddOutgoingHtlcZeroValue(t *testing.T) {
|
||||||
require.NoError(t, aliceChannel.MayAddOutgoingHtlc())
|
require.NoError(t, aliceChannel.MayAddOutgoingHtlc())
|
||||||
require.NoError(t, bobChannel.MayAddOutgoingHtlc())
|
require.NoError(t, bobChannel.MayAddOutgoingHtlc())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestIsChannelClean tests that IsChannelClean returns the expected values
|
||||||
|
// in different channel states.
|
||||||
|
func TestIsChannelClean(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(
|
||||||
|
channeldb.ZeroHtlcTxFeeBit,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer cleanUp()
|
||||||
|
|
||||||
|
// Channel state should be clean at the start of the test.
|
||||||
|
assertCleanOrDirty(true, aliceChannel, bobChannel, t)
|
||||||
|
|
||||||
|
// Assert that neither side considers the channel clean when alice
|
||||||
|
// sends an htlc.
|
||||||
|
// ---add--->
|
||||||
|
htlc, preimage := createHTLC(0, lnwire.MilliSatoshi(5000000))
|
||||||
|
_, err = aliceChannel.AddHTLC(htlc, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = bobChannel.ReceiveHTLC(htlc)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertCleanOrDirty(false, aliceChannel, bobChannel, t)
|
||||||
|
|
||||||
|
// Assert that the channel remains dirty until the HTLC is completely
|
||||||
|
// removed from both commitments.
|
||||||
|
|
||||||
|
// ---sig--->
|
||||||
|
aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment()
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertCleanOrDirty(false, aliceChannel, bobChannel, t)
|
||||||
|
|
||||||
|
// <---rev---
|
||||||
|
bobRevocation, _, err := bobChannel.RevokeCurrentCommitment()
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertCleanOrDirty(false, aliceChannel, bobChannel, t)
|
||||||
|
|
||||||
|
// <---sig---
|
||||||
|
bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment()
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertCleanOrDirty(false, aliceChannel, bobChannel, t)
|
||||||
|
|
||||||
|
// ---rev--->
|
||||||
|
aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment()
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertCleanOrDirty(false, aliceChannel, bobChannel, t)
|
||||||
|
|
||||||
|
// <--settle--
|
||||||
|
err = bobChannel.SettleHTLC(preimage, 0, nil, nil, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = aliceChannel.ReceiveHTLCSettle(preimage, 0)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertCleanOrDirty(false, aliceChannel, bobChannel, t)
|
||||||
|
|
||||||
|
// <---sig---
|
||||||
|
bobSig, bobHtlcSigs, _, err = bobChannel.SignNextCommitment()
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertCleanOrDirty(false, aliceChannel, bobChannel, t)
|
||||||
|
|
||||||
|
// ---rev--->
|
||||||
|
aliceRevocation, _, err = aliceChannel.RevokeCurrentCommitment()
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertCleanOrDirty(false, aliceChannel, bobChannel, t)
|
||||||
|
|
||||||
|
// ---sig--->
|
||||||
|
aliceSig, aliceHtlcSigs, _, err = aliceChannel.SignNextCommitment()
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertCleanOrDirty(false, aliceChannel, bobChannel, t)
|
||||||
|
|
||||||
|
// <---rev---
|
||||||
|
bobRevocation, _, err = bobChannel.RevokeCurrentCommitment()
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertCleanOrDirty(true, aliceChannel, bobChannel, t)
|
||||||
|
|
||||||
|
// Now we check that update_fee is handled and state is dirty until it
|
||||||
|
// is completely locked in.
|
||||||
|
// ---fee--->
|
||||||
|
fee := chainfee.SatPerKWeight(333)
|
||||||
|
err = aliceChannel.UpdateFee(fee)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = bobChannel.ReceiveUpdateFee(fee)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertCleanOrDirty(false, aliceChannel, bobChannel, t)
|
||||||
|
|
||||||
|
// ---sig--->
|
||||||
|
aliceSig, aliceHtlcSigs, _, err = aliceChannel.SignNextCommitment()
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertCleanOrDirty(false, aliceChannel, bobChannel, t)
|
||||||
|
|
||||||
|
// <---rev---
|
||||||
|
bobRevocation, _, err = bobChannel.RevokeCurrentCommitment()
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertCleanOrDirty(false, aliceChannel, bobChannel, t)
|
||||||
|
|
||||||
|
// <---sig---
|
||||||
|
bobSig, bobHtlcSigs, _, err = bobChannel.SignNextCommitment()
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertCleanOrDirty(false, aliceChannel, bobChannel, t)
|
||||||
|
|
||||||
|
// The state should finally be clean after alice sends her revocation.
|
||||||
|
// ---rev--->
|
||||||
|
aliceRevocation, _, err = aliceChannel.RevokeCurrentCommitment()
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertCleanOrDirty(true, aliceChannel, bobChannel, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// assertCleanOrDirty is a helper function that asserts that both channels are
|
||||||
|
// clean if clean is true, and dirty if clean is false.
|
||||||
|
func assertCleanOrDirty(clean bool, alice, bob *LightningChannel,
|
||||||
|
t *testing.T) {
|
||||||
|
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
if clean {
|
||||||
|
require.True(t, alice.IsChannelClean())
|
||||||
|
require.True(t, bob.IsChannelClean())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.False(t, alice.IsChannelClean())
|
||||||
|
require.False(t, bob.IsChannelClean())
|
||||||
|
}
|
||||||
|
|
|
@ -1184,11 +1184,9 @@ func waitUntilLinkActive(p *Brontide,
|
||||||
|
|
||||||
// The link may already be active by this point, and we may have missed the
|
// The link may already be active by this point, and we may have missed the
|
||||||
// ActiveLinkEvent. Check if the link exists.
|
// ActiveLinkEvent. Check if the link exists.
|
||||||
links, _ := p.cfg.Switch.GetLinksByInterface(p.cfg.PubKeyBytes)
|
link := p.fetchLinkFromKeyAndCid(cid)
|
||||||
for _, link := range links {
|
if link != nil {
|
||||||
if link.ChanID() == cid {
|
return link
|
||||||
return link
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the link is nil, we must wait for it to be active.
|
// If the link is nil, we must wait for it to be active.
|
||||||
|
@ -1216,16 +1214,7 @@ func waitUntilLinkActive(p *Brontide,
|
||||||
// The link shouldn't be nil as we received an
|
// The link shouldn't be nil as we received an
|
||||||
// ActiveLinkEvent. If it is nil, we return nil and the
|
// ActiveLinkEvent. If it is nil, we return nil and the
|
||||||
// calling function should catch it.
|
// calling function should catch it.
|
||||||
links, _ = p.cfg.Switch.GetLinksByInterface(
|
return p.fetchLinkFromKeyAndCid(cid)
|
||||||
p.cfg.PubKeyBytes,
|
|
||||||
)
|
|
||||||
for _, link := range links {
|
|
||||||
if link.ChanID() == cid {
|
|
||||||
return link
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
|
|
||||||
case <-p.quit:
|
case <-p.quit:
|
||||||
return nil
|
return nil
|
||||||
|
@ -2409,12 +2398,11 @@ func (p *Brontide) fetchActiveChanCloser(chanID lnwire.ChannelID) (
|
||||||
// cooperative channel closure.
|
// cooperative channel closure.
|
||||||
chanCloser, ok := p.activeChanCloses[chanID]
|
chanCloser, ok := p.activeChanCloses[chanID]
|
||||||
if !ok {
|
if !ok {
|
||||||
// If we need to create a chan closer for the first time, then
|
// Optimistically try a link shutdown, erroring out if it
|
||||||
// we'll check to ensure that the channel is even in the proper
|
// failed.
|
||||||
// state to allow a co-op channel closure.
|
if err := p.tryLinkShutdown(chanID); err != nil {
|
||||||
if len(channel.ActiveHtlcs()) != 0 {
|
peerLog.Errorf("failed link shutdown: %v", err)
|
||||||
return nil, fmt.Errorf("cannot co-op close " +
|
return nil, err
|
||||||
"channel w/ active htlcs")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We'll create a valid closing state machine in order to
|
// We'll create a valid closing state machine in order to
|
||||||
|
@ -2454,9 +2442,8 @@ func (p *Brontide) fetchActiveChanCloser(chanID lnwire.ChannelID) (
|
||||||
|
|
||||||
chanCloser = chancloser.NewChanCloser(
|
chanCloser = chancloser.NewChanCloser(
|
||||||
chancloser.ChanCloseCfg{
|
chancloser.ChanCloseCfg{
|
||||||
Channel: channel,
|
Channel: channel,
|
||||||
UnregisterChannel: p.cfg.Switch.RemoveLink,
|
BroadcastTx: p.cfg.Wallet.PublishTransaction,
|
||||||
BroadcastTx: p.cfg.Wallet.PublishTransaction,
|
|
||||||
DisableChannel: func(chanPoint wire.OutPoint) error {
|
DisableChannel: func(chanPoint wire.OutPoint) error {
|
||||||
return p.cfg.ChanStatusMgr.RequestDisable(chanPoint, false)
|
return p.cfg.ChanStatusMgr.RequestDisable(chanPoint, false)
|
||||||
},
|
},
|
||||||
|
@ -2570,11 +2557,18 @@ func (p *Brontide) handleLocalCloseReq(req *htlcswitch.ChanClose) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Optimistically try a link shutdown, erroring out if it
|
||||||
|
// failed.
|
||||||
|
if err := p.tryLinkShutdown(chanID); err != nil {
|
||||||
|
peerLog.Errorf("failed link shutdown: %v", err)
|
||||||
|
req.Err <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
chanCloser := chancloser.NewChanCloser(
|
chanCloser := chancloser.NewChanCloser(
|
||||||
chancloser.ChanCloseCfg{
|
chancloser.ChanCloseCfg{
|
||||||
Channel: channel,
|
Channel: channel,
|
||||||
UnregisterChannel: p.cfg.Switch.RemoveLink,
|
BroadcastTx: p.cfg.Wallet.PublishTransaction,
|
||||||
BroadcastTx: p.cfg.Wallet.PublishTransaction,
|
|
||||||
DisableChannel: func(chanPoint wire.OutPoint) error {
|
DisableChannel: func(chanPoint wire.OutPoint) error {
|
||||||
return p.cfg.ChanStatusMgr.RequestDisable(chanPoint, false)
|
return p.cfg.ChanStatusMgr.RequestDisable(chanPoint, false)
|
||||||
},
|
},
|
||||||
|
@ -2701,6 +2695,55 @@ func (p *Brontide) handleLinkFailure(failure linkFailureReport) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tryLinkShutdown attempts to fetch a target link from the switch, calls
|
||||||
|
// ShutdownIfChannelClean to optimistically trigger a link shutdown, and
|
||||||
|
// removes the link from the switch. It returns an error if any step failed.
|
||||||
|
func (p *Brontide) tryLinkShutdown(cid lnwire.ChannelID) error {
|
||||||
|
// Fetch the appropriate link and call ShutdownIfChannelClean to ensure
|
||||||
|
// no other updates can occur.
|
||||||
|
chanLink := p.fetchLinkFromKeyAndCid(cid)
|
||||||
|
|
||||||
|
// If the link happens to be nil, return ErrChannelNotFound so we can
|
||||||
|
// ignore the close message.
|
||||||
|
if chanLink == nil {
|
||||||
|
return ErrChannelNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// Else, the link exists, so attempt to trigger shutdown. If this
|
||||||
|
// fails, we'll send an error message to the remote peer.
|
||||||
|
if err := chanLink.ShutdownIfChannelClean(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next, we remove the link from the switch to shut down all of the
|
||||||
|
// link's goroutines and remove it from the switch's internal maps. We
|
||||||
|
// don't call WipeChannel as the channel must still be in the
|
||||||
|
// activeChannels map to process coop close messages.
|
||||||
|
p.cfg.Switch.RemoveLink(cid)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetchLinkFromKeyAndCid fetches a link from the switch via the remote's
|
||||||
|
// public key and the channel id.
|
||||||
|
func (p *Brontide) fetchLinkFromKeyAndCid(
|
||||||
|
cid lnwire.ChannelID) htlcswitch.ChannelUpdateHandler {
|
||||||
|
|
||||||
|
var chanLink htlcswitch.ChannelUpdateHandler
|
||||||
|
|
||||||
|
// We don't need to check the error here, and can instead just loop
|
||||||
|
// over the slice and return nil.
|
||||||
|
links, _ := p.cfg.Switch.GetLinksByInterface(p.cfg.PubKeyBytes)
|
||||||
|
for _, link := range links {
|
||||||
|
if link.ChanID() == cid {
|
||||||
|
chanLink = link
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return chanLink
|
||||||
|
}
|
||||||
|
|
||||||
// finalizeChanClosure performs the final clean up steps once the cooperative
|
// finalizeChanClosure performs the final clean up steps once the cooperative
|
||||||
// closure transaction has been fully broadcast. The finalized closing state
|
// closure transaction has been fully broadcast. The finalized closing state
|
||||||
// machine should be passed in. Once the transaction has been sufficiently
|
// machine should be passed in. Once the transaction has been sufficiently
|
||||||
|
|
|
@ -39,8 +39,10 @@ func TestPeerChannelClosureAcceptFeeResponder(t *testing.T) {
|
||||||
}
|
}
|
||||||
broadcastTxChan := make(chan *wire.MsgTx)
|
broadcastTxChan := make(chan *wire.MsgTx)
|
||||||
|
|
||||||
|
mockSwitch := &mockMessageSwitch{}
|
||||||
|
|
||||||
alicePeer, bobChan, cleanUp, err := createTestPeer(
|
alicePeer, bobChan, cleanUp, err := createTestPeer(
|
||||||
notifier, broadcastTxChan, noUpdate,
|
notifier, broadcastTxChan, noUpdate, mockSwitch,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
t.Fatalf("unable to create test channels: %v", err)
|
||||||
|
@ -49,6 +51,9 @@ func TestPeerChannelClosureAcceptFeeResponder(t *testing.T) {
|
||||||
|
|
||||||
chanID := lnwire.NewChanIDFromOutPoint(bobChan.ChannelPoint())
|
chanID := lnwire.NewChanIDFromOutPoint(bobChan.ChannelPoint())
|
||||||
|
|
||||||
|
mockLink := newMockUpdateHandler(chanID)
|
||||||
|
mockSwitch.links = append(mockSwitch.links, mockLink)
|
||||||
|
|
||||||
// We send a shutdown request to Alice. She will now be the responding
|
// We send a shutdown request to Alice. She will now be the responding
|
||||||
// node in this shutdown procedure. We first expect Alice to answer
|
// node in this shutdown procedure. We first expect Alice to answer
|
||||||
// this shutdown request with a Shutdown message.
|
// this shutdown request with a Shutdown message.
|
||||||
|
@ -142,14 +147,20 @@ func TestPeerChannelClosureAcceptFeeInitiator(t *testing.T) {
|
||||||
}
|
}
|
||||||
broadcastTxChan := make(chan *wire.MsgTx)
|
broadcastTxChan := make(chan *wire.MsgTx)
|
||||||
|
|
||||||
|
mockSwitch := &mockMessageSwitch{}
|
||||||
|
|
||||||
alicePeer, bobChan, cleanUp, err := createTestPeer(
|
alicePeer, bobChan, cleanUp, err := createTestPeer(
|
||||||
notifier, broadcastTxChan, noUpdate,
|
notifier, broadcastTxChan, noUpdate, mockSwitch,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
t.Fatalf("unable to create test channels: %v", err)
|
||||||
}
|
}
|
||||||
defer cleanUp()
|
defer cleanUp()
|
||||||
|
|
||||||
|
chanID := lnwire.NewChanIDFromOutPoint(bobChan.ChannelPoint())
|
||||||
|
mockLink := newMockUpdateHandler(chanID)
|
||||||
|
mockSwitch.links = append(mockSwitch.links, mockLink)
|
||||||
|
|
||||||
// We make Alice send a shutdown request.
|
// We make Alice send a shutdown request.
|
||||||
updateChan := make(chan interface{}, 1)
|
updateChan := make(chan interface{}, 1)
|
||||||
errChan := make(chan error, 1)
|
errChan := make(chan error, 1)
|
||||||
|
@ -179,7 +190,6 @@ func TestPeerChannelClosureAcceptFeeInitiator(t *testing.T) {
|
||||||
aliceDeliveryScript := shutdownMsg.Address
|
aliceDeliveryScript := shutdownMsg.Address
|
||||||
|
|
||||||
// Bob will respond with his own Shutdown message.
|
// Bob will respond with his own Shutdown message.
|
||||||
chanID := shutdownMsg.ChannelID
|
|
||||||
alicePeer.chanCloseMsgs <- &closeMsg{
|
alicePeer.chanCloseMsgs <- &closeMsg{
|
||||||
cid: chanID,
|
cid: chanID,
|
||||||
msg: lnwire.NewShutdown(chanID,
|
msg: lnwire.NewShutdown(chanID,
|
||||||
|
@ -264,8 +274,10 @@ func TestPeerChannelClosureFeeNegotiationsResponder(t *testing.T) {
|
||||||
}
|
}
|
||||||
broadcastTxChan := make(chan *wire.MsgTx)
|
broadcastTxChan := make(chan *wire.MsgTx)
|
||||||
|
|
||||||
|
mockSwitch := &mockMessageSwitch{}
|
||||||
|
|
||||||
alicePeer, bobChan, cleanUp, err := createTestPeer(
|
alicePeer, bobChan, cleanUp, err := createTestPeer(
|
||||||
notifier, broadcastTxChan, noUpdate,
|
notifier, broadcastTxChan, noUpdate, mockSwitch,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
t.Fatalf("unable to create test channels: %v", err)
|
||||||
|
@ -274,6 +286,9 @@ func TestPeerChannelClosureFeeNegotiationsResponder(t *testing.T) {
|
||||||
|
|
||||||
chanID := lnwire.NewChanIDFromOutPoint(bobChan.ChannelPoint())
|
chanID := lnwire.NewChanIDFromOutPoint(bobChan.ChannelPoint())
|
||||||
|
|
||||||
|
mockLink := newMockUpdateHandler(chanID)
|
||||||
|
mockSwitch.links = append(mockSwitch.links, mockLink)
|
||||||
|
|
||||||
// Bob sends a shutdown request to Alice. She will now be the responding
|
// Bob sends a shutdown request to Alice. She will now be the responding
|
||||||
// node in this shutdown procedure. We first expect Alice to answer this
|
// node in this shutdown procedure. We first expect Alice to answer this
|
||||||
// Shutdown request with a Shutdown message.
|
// Shutdown request with a Shutdown message.
|
||||||
|
@ -458,14 +473,20 @@ func TestPeerChannelClosureFeeNegotiationsInitiator(t *testing.T) {
|
||||||
}
|
}
|
||||||
broadcastTxChan := make(chan *wire.MsgTx)
|
broadcastTxChan := make(chan *wire.MsgTx)
|
||||||
|
|
||||||
|
mockSwitch := &mockMessageSwitch{}
|
||||||
|
|
||||||
alicePeer, bobChan, cleanUp, err := createTestPeer(
|
alicePeer, bobChan, cleanUp, err := createTestPeer(
|
||||||
notifier, broadcastTxChan, noUpdate,
|
notifier, broadcastTxChan, noUpdate, mockSwitch,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
t.Fatalf("unable to create test channels: %v", err)
|
||||||
}
|
}
|
||||||
defer cleanUp()
|
defer cleanUp()
|
||||||
|
|
||||||
|
chanID := lnwire.NewChanIDFromOutPoint(bobChan.ChannelPoint())
|
||||||
|
mockLink := newMockUpdateHandler(chanID)
|
||||||
|
mockSwitch.links = append(mockSwitch.links, mockLink)
|
||||||
|
|
||||||
// We make the initiator send a shutdown request.
|
// We make the initiator send a shutdown request.
|
||||||
updateChan := make(chan interface{}, 1)
|
updateChan := make(chan interface{}, 1)
|
||||||
errChan := make(chan error, 1)
|
errChan := make(chan error, 1)
|
||||||
|
@ -496,7 +517,6 @@ func TestPeerChannelClosureFeeNegotiationsInitiator(t *testing.T) {
|
||||||
aliceDeliveryScript := shutdownMsg.Address
|
aliceDeliveryScript := shutdownMsg.Address
|
||||||
|
|
||||||
// Bob will answer the Shutdown message with his own Shutdown.
|
// Bob will answer the Shutdown message with his own Shutdown.
|
||||||
chanID := lnwire.NewChanIDFromOutPoint(bobChan.ChannelPoint())
|
|
||||||
respShutdown := lnwire.NewShutdown(chanID, dummyDeliveryScript)
|
respShutdown := lnwire.NewShutdown(chanID, dummyDeliveryScript)
|
||||||
alicePeer.chanCloseMsgs <- &closeMsg{
|
alicePeer.chanCloseMsgs <- &closeMsg{
|
||||||
cid: chanID,
|
cid: chanID,
|
||||||
|
@ -793,20 +813,27 @@ func TestCustomShutdownScript(t *testing.T) {
|
||||||
}
|
}
|
||||||
broadcastTxChan := make(chan *wire.MsgTx)
|
broadcastTxChan := make(chan *wire.MsgTx)
|
||||||
|
|
||||||
|
mockSwitch := &mockMessageSwitch{}
|
||||||
|
|
||||||
// Open a channel.
|
// Open a channel.
|
||||||
alicePeer, bobChan, cleanUp, err := createTestPeer(
|
alicePeer, bobChan, cleanUp, err := createTestPeer(
|
||||||
notifier, broadcastTxChan, test.update,
|
notifier, broadcastTxChan, test.update,
|
||||||
|
mockSwitch,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
t.Fatalf("unable to create test channels: %v", err)
|
||||||
}
|
}
|
||||||
defer cleanUp()
|
defer cleanUp()
|
||||||
|
|
||||||
|
chanPoint := bobChan.ChannelPoint()
|
||||||
|
chanID := lnwire.NewChanIDFromOutPoint(chanPoint)
|
||||||
|
mockLink := newMockUpdateHandler(chanID)
|
||||||
|
mockSwitch.links = append(mockSwitch.links, mockLink)
|
||||||
|
|
||||||
// Request initiator to cooperatively close the channel, with
|
// Request initiator to cooperatively close the channel, with
|
||||||
// a specified delivery address.
|
// a specified delivery address.
|
||||||
updateChan := make(chan interface{}, 1)
|
updateChan := make(chan interface{}, 1)
|
||||||
errChan := make(chan error, 1)
|
errChan := make(chan error, 1)
|
||||||
chanPoint := bobChan.ChannelPoint()
|
|
||||||
closeCommand := htlcswitch.ChanClose{
|
closeCommand := htlcswitch.ChanClose{
|
||||||
CloseType: htlcswitch.CloseRegular,
|
CloseType: htlcswitch.CloseRegular,
|
||||||
ChanPoint: chanPoint,
|
ChanPoint: chanPoint,
|
||||||
|
|
|
@ -54,7 +54,8 @@ var noUpdate = func(a, b *channeldb.OpenChannel) {}
|
||||||
// an updateChan function which can be used to modify the default values on
|
// an updateChan function which can be used to modify the default values on
|
||||||
// the channel states for each peer.
|
// the channel states for each peer.
|
||||||
func createTestPeer(notifier chainntnfs.ChainNotifier,
|
func createTestPeer(notifier chainntnfs.ChainNotifier,
|
||||||
publTx chan *wire.MsgTx, updateChan func(a, b *channeldb.OpenChannel)) (
|
publTx chan *wire.MsgTx, updateChan func(a, b *channeldb.OpenChannel),
|
||||||
|
mockSwitch *mockMessageSwitch) (
|
||||||
*Brontide, *lnwallet.LightningChannel, func(), error) {
|
*Brontide, *lnwallet.LightningChannel, func(), error) {
|
||||||
|
|
||||||
aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(
|
aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(
|
||||||
|
@ -306,7 +307,11 @@ func createTestPeer(notifier chainntnfs.ChainNotifier,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
htlcSwitch := &mockMessageSwitch{}
|
// If mockSwitch is not set by the caller, set it to the default as the
|
||||||
|
// caller does not need to control it.
|
||||||
|
if mockSwitch == nil {
|
||||||
|
mockSwitch = &mockMessageSwitch{}
|
||||||
|
}
|
||||||
|
|
||||||
nodeSignerAlice := netann.NewNodeSigner(aliceKeySigner)
|
nodeSignerAlice := netann.NewNodeSigner(aliceKeySigner)
|
||||||
|
|
||||||
|
@ -349,7 +354,7 @@ func createTestPeer(notifier chainntnfs.ChainNotifier,
|
||||||
PubKeyBytes: pubKey,
|
PubKeyBytes: pubKey,
|
||||||
ErrorBuffer: errBuffer,
|
ErrorBuffer: errBuffer,
|
||||||
ChainIO: chainIO,
|
ChainIO: chainIO,
|
||||||
Switch: htlcSwitch,
|
Switch: mockSwitch,
|
||||||
|
|
||||||
ChanActiveTimeout: chanActiveTimeout,
|
ChanActiveTimeout: chanActiveTimeout,
|
||||||
InterceptSwitch: htlcswitch.NewInterceptableSwitch(nil),
|
InterceptSwitch: htlcswitch.NewInterceptableSwitch(nil),
|
||||||
|
@ -375,7 +380,9 @@ func createTestPeer(notifier chainntnfs.ChainNotifier,
|
||||||
|
|
||||||
// mockMessageSwitch is a mock implementation of the messageSwitch interface
|
// mockMessageSwitch is a mock implementation of the messageSwitch interface
|
||||||
// used for testing without relying on a *htlcswitch.Switch in unit tests.
|
// used for testing without relying on a *htlcswitch.Switch in unit tests.
|
||||||
type mockMessageSwitch struct{}
|
type mockMessageSwitch struct {
|
||||||
|
links []htlcswitch.ChannelUpdateHandler
|
||||||
|
}
|
||||||
|
|
||||||
// BestHeight currently returns a dummy value.
|
// BestHeight currently returns a dummy value.
|
||||||
func (m *mockMessageSwitch) BestHeight() uint32 {
|
func (m *mockMessageSwitch) BestHeight() uint32 {
|
||||||
|
@ -397,13 +404,44 @@ func (m *mockMessageSwitch) CreateAndAddLink(cfg htlcswitch.ChannelLinkConfig,
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLinksByInterface currently returns dummy values.
|
// GetLinksByInterface returns the active links.
|
||||||
func (m *mockMessageSwitch) GetLinksByInterface(pub [33]byte) (
|
func (m *mockMessageSwitch) GetLinksByInterface(pub [33]byte) (
|
||||||
[]htlcswitch.ChannelUpdateHandler, error) {
|
[]htlcswitch.ChannelUpdateHandler, error) {
|
||||||
|
|
||||||
return nil, nil
|
return m.links, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mockUpdateHandler is a mock implementation of the ChannelUpdateHandler
|
||||||
|
// interface. It is used in mockMessageSwitch's GetLinksByInterface method.
|
||||||
|
type mockUpdateHandler struct {
|
||||||
|
cid lnwire.ChannelID
|
||||||
|
}
|
||||||
|
|
||||||
|
// newMockUpdateHandler creates a new mockUpdateHandler.
|
||||||
|
func newMockUpdateHandler(cid lnwire.ChannelID) *mockUpdateHandler {
|
||||||
|
return &mockUpdateHandler{
|
||||||
|
cid: cid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleChannelUpdate currently does nothing.
|
||||||
|
func (m *mockUpdateHandler) HandleChannelUpdate(msg lnwire.Message) {}
|
||||||
|
|
||||||
|
// ChanID returns the mockUpdateHandler's cid.
|
||||||
|
func (m *mockUpdateHandler) ChanID() lnwire.ChannelID { return m.cid }
|
||||||
|
|
||||||
|
// Bandwidth currently returns a dummy value.
|
||||||
|
func (m *mockUpdateHandler) Bandwidth() lnwire.MilliSatoshi { return 0 }
|
||||||
|
|
||||||
|
// EligibleToForward currently returns a dummy value.
|
||||||
|
func (m *mockUpdateHandler) EligibleToForward() bool { return false }
|
||||||
|
|
||||||
|
// MayAddOutgoingHtlc currently returns nil.
|
||||||
|
func (m *mockUpdateHandler) MayAddOutgoingHtlc() error { return nil }
|
||||||
|
|
||||||
|
// ShutdownIfChannelClean currently returns nil.
|
||||||
|
func (m *mockUpdateHandler) ShutdownIfChannelClean() error { return nil }
|
||||||
|
|
||||||
type mockMessageConn struct {
|
type mockMessageConn struct {
|
||||||
t *testing.T
|
t *testing.T
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue