htlcswitch+peer: add flush api and lifecycle hooks to ChannelUpdateHandler
We also add dummy implementations to channelLink and various mocks.
This commit is contained in:
1 parent
ec5b824879
commit
9b2d1018f2
7 files changed
+318
-3
No files matched your search
@@ -138,8 +138,56 @@ type ChannelUpdateHandler interface {
|
||||
// clean. This can be used with dynamic commitment negotiation or coop
|
||||
// close negotiation which require a clean channel state.
|
||||
ShutdownIfChannelClean() error
|
||||
|
||||
// EnableAdds sets the ChannelUpdateHandler state to allow
|
||||
// UpdateAddHtlc's in the specified direction. It returns an error if
|
||||
// the state already allowed those adds.
|
||||
EnableAdds(direction LinkDirection) error
|
||||
|
||||
// DiableAdds sets the ChannelUpdateHandler state to allow
|
||||
// UpdateAddHtlc's in the specified direction. It returns an error if
|
||||
// the state already disallowed those adds.
|
||||
DisableAdds(direction LinkDirection) error
|
||||
|
||||
// IsFlushing returns true when UpdateAddHtlc's are disabled in the
|
||||
// direction of the argument.
|
||||
IsFlushing(direction LinkDirection) bool
|
||||
|
||||
// OnFlushedOnce adds a hook that will be called the next time the
|
||||
// channel state reaches zero htlcs. This hook will only ever be called
|
||||
// once. If the channel state already has zero htlcs, then this will be
|
||||
// called immediately.
|
||||
OnFlushedOnce(func())
|
||||
|
||||
// OnCommitOnce adds a hook that will be called the next time a
|
||||
// CommitSig message is sent in the argument's LinkDirection. This hook
|
||||
// will only ever be called once. If no CommitSig is owed in the
|
||||
// argument's LinkDirection, then we will call this hook immediately.
|
||||
OnCommitOnce(LinkDirection, func())
|
||||
}
|
||||
|
||||
// CommitHookID is a value that is used to uniquely identify hooks in the
|
||||
// ChannelUpdateHandler's commitment update lifecycle. You should never need to
|
||||
// construct one of these by hand, nor should you try.
|
||||
type CommitHookID uint64
|
||||
|
||||
// FlushHookID is a value that is used to uniquely identify hooks in the
|
||||
// ChannelUpdateHandler's flush lifecycle. You should never need to construct
|
||||
// one of these by hand, nor should you try.
|
||||
type FlushHookID uint64
|
||||
|
||||
// LinkDirection is used to query and change any link state on a per-direction
|
||||
// basis.
|
||||
type LinkDirection bool
|
||||
|
||||
const (
|
||||
// Incoming is the direction from the remote peer to our node.
|
||||
Incoming LinkDirection = false
|
||||
|
||||
// Outgoing is the direction from our node to the remote peer.
|
||||
Outgoing LinkDirection = true
|
||||
)
|
||||
|
||||
// ChannelLink is an interface which represents the subsystem for managing the
|
||||
// incoming htlc requests, applying the changes to the channel, and also
|
||||
// propagating/forwarding it to htlc switch.
|
||||
|
||||
@@ -547,6 +547,45 @@ func (l *channelLink) EligibleToForward() bool {
|
||||
l.isReestablished()
|
||||
}
|
||||
|
||||
// EnableAdds sets the ChannelUpdateHandler state to allow UpdateAddHtlc's in
|
||||
// the specified direction. It returns an error if the state already allowed
|
||||
// those adds.
|
||||
func (l *channelLink) EnableAdds(LinkDirection) error {
|
||||
// TODO(proofofkeags): Implement
|
||||
return nil
|
||||
}
|
||||
|
||||
// DiableAdds sets the ChannelUpdateHandler state to allow UpdateAddHtlc's in
|
||||
// the specified direction. It returns an error if the state already disallowed
|
||||
// those adds.
|
||||
func (l *channelLink) DisableAdds(LinkDirection) error {
|
||||
// TODO(proofofkeags): Implement
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsFlushing returns true when UpdateAddHtlc's are disabled in the direction of
|
||||
// the argument.
|
||||
func (l *channelLink) IsFlushing(LinkDirection) bool {
|
||||
// TODO(proofofkeags): Implement
|
||||
return false
|
||||
}
|
||||
|
||||
// OnFlushedOnce adds a hook that will be called the next time the
|
||||
// channel state reaches zero htlcs. This hook will only ever be called
|
||||
// once. If the channel state already has zero htlcs, then this will be
|
||||
// called immediately.
|
||||
func (l *channelLink) OnFlushedOnce(func()) {
|
||||
// TODO(proofofkeags): Implement
|
||||
}
|
||||
|
||||
// OnCommitOnce adds a hook that will be called the next time a CommitSig
|
||||
// message is sent in the argument's LinkDirection. This hook will only ever be
|
||||
// called once. If no CommitSig is owed in the argument's LinkDirection, then
|
||||
// we will call this hook immediately.
|
||||
func (l *channelLink) OnCommitOnce(LinkDirection, func()) {
|
||||
// TODO(proofofkeags): Implement
|
||||
}
|
||||
|
||||
// isReestablished returns true if the link has successfully completed the
|
||||
// channel reestablishment dance.
|
||||
func (l *channelLink) isReestablished() bool {
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
prand "math/rand"
|
||||
"net"
|
||||
"reflect"
|
||||
"runtime"
|
||||
@@ -7041,3 +7042,190 @@ func TestChannelLinkShortFailureRelay(t *testing.T) {
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// TestLinkFlushApiDirectionIsolation tests whether the calls to EnableAdds and
|
||||
// DisableAdds are correctly isolated based off of direction (Incoming and
|
||||
// Outgoing). This means that the state of the Outgoing flush should always be
|
||||
// unaffected by calls to EnableAdds/DisableAdds on the Incoming direction and
|
||||
// vice-versa.
|
||||
func TestLinkFlushApiDirectionIsolation(t *testing.T) {
|
||||
aliceLink, _, _, _, _, _ :=
|
||||
newSingleLinkTestHarness(
|
||||
t, 5*btcutil.SatoshiPerBitcoin,
|
||||
1*btcutil.SatoshiPerBitcoin,
|
||||
)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
if prand.Uint64()%2 == 0 {
|
||||
//nolint:errcheck
|
||||
aliceLink.EnableAdds(Outgoing)
|
||||
require.False(t, aliceLink.IsFlushing(Outgoing))
|
||||
} else {
|
||||
//nolint:errcheck
|
||||
aliceLink.DisableAdds(Outgoing)
|
||||
require.True(t, aliceLink.IsFlushing(Outgoing))
|
||||
}
|
||||
require.False(t, aliceLink.IsFlushing(Incoming))
|
||||
}
|
||||
|
||||
//nolint:errcheck
|
||||
aliceLink.EnableAdds(Outgoing)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
if prand.Uint64()%2 == 0 {
|
||||
//nolint:errcheck
|
||||
aliceLink.EnableAdds(Incoming)
|
||||
require.False(t, aliceLink.IsFlushing(Incoming))
|
||||
} else {
|
||||
//nolint:errcheck
|
||||
aliceLink.DisableAdds(Incoming)
|
||||
require.True(t, aliceLink.IsFlushing(Incoming))
|
||||
}
|
||||
require.False(t, aliceLink.IsFlushing(Outgoing))
|
||||
}
|
||||
}
|
||||
|
||||
// TestLinkFlushApiGateStateIdempotence tests whether successive calls to
|
||||
// EnableAdds or DisableAdds (without the other one in between) result in both
|
||||
// no state change in the flush state AND that the second call results in an
|
||||
// error (to inform the caller that the call was unnecessary in case it implies
|
||||
// a bug in their logic).
|
||||
func TestLinkFlushApiGateStateIdempotence(t *testing.T) {
|
||||
aliceLink, _, _, _, _, _ :=
|
||||
newSingleLinkTestHarness(
|
||||
t, 5*btcutil.SatoshiPerBitcoin,
|
||||
1*btcutil.SatoshiPerBitcoin,
|
||||
)
|
||||
|
||||
for _, dir := range []LinkDirection{Incoming, Outgoing} {
|
||||
require.Nil(t, aliceLink.DisableAdds(dir))
|
||||
require.True(t, aliceLink.IsFlushing(dir))
|
||||
|
||||
require.NotNil(t, aliceLink.DisableAdds(dir))
|
||||
require.True(t, aliceLink.IsFlushing(dir))
|
||||
|
||||
require.Nil(t, aliceLink.EnableAdds(dir))
|
||||
require.False(t, aliceLink.IsFlushing(dir))
|
||||
|
||||
require.NotNil(t, aliceLink.EnableAdds(dir))
|
||||
require.False(t, aliceLink.IsFlushing(dir))
|
||||
}
|
||||
}
|
||||
|
||||
func TestLinkOutgoingCommitHooksCalled(t *testing.T) {
|
||||
aliceLink, _, batchTicker, start, _, err :=
|
||||
newSingleLinkTestHarness(
|
||||
t, 5*btcutil.SatoshiPerBitcoin,
|
||||
btcutil.SatoshiPerBitcoin,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, start(), "could not start link")
|
||||
|
||||
hookCalled := make(chan struct{})
|
||||
aliceLink.OnCommitOnce(Outgoing, func() {
|
||||
close(hookCalled)
|
||||
})
|
||||
|
||||
select {
|
||||
case <-hookCalled:
|
||||
t.Fatal("hook called prematurely")
|
||||
case <-time.NewTimer(time.Second).C:
|
||||
}
|
||||
|
||||
batchTicker <- time.Now()
|
||||
|
||||
// Send a second tick just to ensure the hook isn't called more than
|
||||
// once.
|
||||
batchTicker <- time.Now()
|
||||
|
||||
select {
|
||||
case <-hookCalled:
|
||||
case <-time.NewTimer(time.Second).C:
|
||||
t.Fatal("hook not called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLinkFlushHooksCalled(t *testing.T) {
|
||||
aliceLink, bobChannel, _, start, _, err :=
|
||||
newSingleLinkTestHarness(
|
||||
t, 5*btcutil.SatoshiPerBitcoin,
|
||||
btcutil.SatoshiPerBitcoin,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, start(), "could not start link")
|
||||
|
||||
//nolint:forcetypeassert
|
||||
aliceMsgs := aliceLink.(*channelLink).cfg.Peer.(*mockPeer).sentMsgs
|
||||
|
||||
ctx := linkTestContext{
|
||||
t: t,
|
||||
aliceLink: aliceLink,
|
||||
bobChannel: bobChannel,
|
||||
aliceMsgs: aliceMsgs,
|
||||
}
|
||||
|
||||
hookCalled := make(chan struct{})
|
||||
|
||||
assertHookCalled := func(shouldBeCalled bool) {
|
||||
select {
|
||||
case <-hookCalled:
|
||||
require.True(
|
||||
t, shouldBeCalled, "hook called prematurely",
|
||||
)
|
||||
case <-time.NewTimer(time.Millisecond).C:
|
||||
require.False(t, shouldBeCalled, "hook not called")
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:forcetypeassert
|
||||
htlc := generateHtlc(t, aliceLink.(*channelLink), 0)
|
||||
|
||||
// A <- add -- B
|
||||
ctx.sendHtlcBobToAlice(htlc)
|
||||
|
||||
// A <- sig -- B
|
||||
ctx.sendCommitSigBobToAlice(1)
|
||||
|
||||
// A -- rev -> B
|
||||
ctx.receiveRevAndAckAliceToBob()
|
||||
|
||||
// Register flush hook
|
||||
aliceLink.OnFlushedOnce(func() {
|
||||
close(hookCalled)
|
||||
})
|
||||
|
||||
// Channel is not clean, hook should not be called
|
||||
assertHookCalled(false)
|
||||
|
||||
// A -- sig -> B
|
||||
ctx.receiveCommitSigAliceToBob(1)
|
||||
assertHookCalled(false)
|
||||
|
||||
// A <- rev -- B
|
||||
ctx.sendRevAndAckBobToAlice()
|
||||
assertHookCalled(false)
|
||||
|
||||
// A -- set -> B
|
||||
ctx.receiveSettleAliceToBob()
|
||||
assertHookCalled(false)
|
||||
|
||||
// A -- sig -> B
|
||||
ctx.receiveCommitSigAliceToBob(0)
|
||||
assertHookCalled(false)
|
||||
|
||||
// A <- rev -- B
|
||||
ctx.sendRevAndAckBobToAlice()
|
||||
assertHookCalled(false)
|
||||
|
||||
// A <- sig -- B
|
||||
ctx.sendCommitSigBobToAlice(0)
|
||||
// since there is no pause point between alice receiving CommitSig and
|
||||
// sending RevokeAndAck, we don't assert the hook hasn't been called
|
||||
// here.
|
||||
|
||||
// A -- rev -> B
|
||||
ctx.receiveRevAndAckAliceToBob()
|
||||
assertHookCalled(true)
|
||||
}
|
||||
@@ -907,6 +907,25 @@ func (f *mockChannelLink) UpdateShortChanID() (lnwire.ShortChannelID, error) {
|
||||
return f.shortChanID, nil
|
||||
}
|
||||
|
||||
func (f *mockChannelLink) EnableAdds(linkDirection LinkDirection) error {
|
||||
// TODO(proofofkeags): Implement
|
||||
return nil
|
||||
}
|
||||
func (f *mockChannelLink) DisableAdds(linkDirection LinkDirection) error {
|
||||
// TODO(proofofkeags): Implement
|
||||
return nil
|
||||
}
|
||||
func (f *mockChannelLink) IsFlushing(linkDirection LinkDirection) bool {
|
||||
// TODO(proofofkeags): Implement
|
||||
return false
|
||||
}
|
||||
func (f *mockChannelLink) OnFlushedOnce(func()) {
|
||||
// TODO(proofofkeags): Implement
|
||||
}
|
||||
func (f *mockChannelLink) OnCommitOnce(LinkDirection, func()) {
|
||||
// TODO(proofofkeags): Implement
|
||||
}
|
||||
|
||||
var _ ChannelLink = (*mockChannelLink)(nil)
|
||||
|
||||
func newDB() (*channeldb.DB, func(), error) {
|
||||
|
||||
Reference in new issue
Block a user