Merge pull request #8270 from ProofOfKeags/feature/stfu
DynComms [1/n]: Implement Quiescence Protocol
This commit is contained in:
33 files changed
+1859
-39
No files matched your search
@@ -67,6 +67,10 @@
|
|||||||
signal relay was added. This signal has *no impact* on routing, and
|
signal relay was added. This signal has *no impact* on routing, and
|
||||||
is deployed experimentally to assist ongoing channel jamming research.
|
is deployed experimentally to assist ongoing channel jamming research.
|
||||||
|
|
||||||
|
* Add initial support for [quiescence](https://github.com/lightningnetwork/lnd/pull/8270).
|
||||||
|
This is a protocol gadget required for Dynamic Commitments and Splicing that
|
||||||
|
will be added later.
|
||||||
|
|
||||||
## Functional Enhancements
|
## Functional Enhancements
|
||||||
* [Add ability](https://github.com/lightningnetwork/lnd/pull/8998) to paginate
|
* [Add ability](https://github.com/lightningnetwork/lnd/pull/8998) to paginate
|
||||||
wallet transactions.
|
wallet transactions.
|
||||||
@@ -216,6 +220,7 @@ The underlying functionality between those two options remain the same.
|
|||||||
* Elle Mouton
|
* Elle Mouton
|
||||||
* George Tsagkarelis
|
* George Tsagkarelis
|
||||||
* hieblmi
|
* hieblmi
|
||||||
|
* Keagan McClelland
|
||||||
* Oliver Gugger
|
* Oliver Gugger
|
||||||
* Pins
|
* Pins
|
||||||
* Viktor Tigerström
|
* Viktor Tigerström
|
||||||
|
|||||||
@@ -84,6 +84,10 @@ var defaultSetDesc = setDesc{
|
|||||||
SetNodeAnn: {}, // N
|
SetNodeAnn: {}, // N
|
||||||
SetInvoice: {}, // 9
|
SetInvoice: {}, // 9
|
||||||
},
|
},
|
||||||
|
lnwire.QuiescenceOptional: {
|
||||||
|
SetInit: {}, // I
|
||||||
|
SetNodeAnn: {}, // N
|
||||||
|
},
|
||||||
lnwire.ShutdownAnySegwitOptional: {
|
lnwire.ShutdownAnySegwitOptional: {
|
||||||
SetInit: {}, // I
|
SetInit: {}, // I
|
||||||
SetNodeAnn: {}, // N
|
SetNodeAnn: {}, // N
|
||||||
|
|||||||
@@ -63,6 +63,9 @@ type Config struct {
|
|||||||
// NoRouteBlinding unsets route blinding feature bits.
|
// NoRouteBlinding unsets route blinding feature bits.
|
||||||
NoRouteBlinding bool
|
NoRouteBlinding bool
|
||||||
|
|
||||||
|
// NoQuiescence unsets quiescence feature bits.
|
||||||
|
NoQuiescence bool
|
||||||
|
|
||||||
// NoTaprootOverlay unsets the taproot overlay channel feature bits.
|
// NoTaprootOverlay unsets the taproot overlay channel feature bits.
|
||||||
NoTaprootOverlay bool
|
NoTaprootOverlay bool
|
||||||
|
|
||||||
@@ -199,6 +202,9 @@ func newManager(cfg Config, desc setDesc) (*Manager, error) {
|
|||||||
raw.Unset(lnwire.Bolt11BlindedPathsOptional)
|
raw.Unset(lnwire.Bolt11BlindedPathsOptional)
|
||||||
raw.Unset(lnwire.Bolt11BlindedPathsRequired)
|
raw.Unset(lnwire.Bolt11BlindedPathsRequired)
|
||||||
}
|
}
|
||||||
|
if cfg.NoQuiescence {
|
||||||
|
raw.Unset(lnwire.QuiescenceOptional)
|
||||||
|
}
|
||||||
if cfg.NoTaprootOverlay {
|
if cfg.NoTaprootOverlay {
|
||||||
raw.Unset(lnwire.SimpleTaprootOverlayChansOptional)
|
raw.Unset(lnwire.SimpleTaprootOverlayChansOptional)
|
||||||
raw.Unset(lnwire.SimpleTaprootOverlayChansRequired)
|
raw.Unset(lnwire.SimpleTaprootOverlayChansRequired)
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ require (
|
|||||||
github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb
|
github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb
|
||||||
github.com/lightningnetwork/lnd/cert v1.2.2
|
github.com/lightningnetwork/lnd/cert v1.2.2
|
||||||
github.com/lightningnetwork/lnd/clock v1.1.1
|
github.com/lightningnetwork/lnd/clock v1.1.1
|
||||||
github.com/lightningnetwork/lnd/fn v1.2.3
|
github.com/lightningnetwork/lnd/fn v1.2.5
|
||||||
github.com/lightningnetwork/lnd/healthcheck v1.2.6
|
github.com/lightningnetwork/lnd/healthcheck v1.2.6
|
||||||
github.com/lightningnetwork/lnd/kvdb v1.4.11
|
github.com/lightningnetwork/lnd/kvdb v1.4.11
|
||||||
github.com/lightningnetwork/lnd/queue v1.1.1
|
github.com/lightningnetwork/lnd/queue v1.1.1
|
||||||
|
|||||||
@@ -456,8 +456,8 @@ github.com/lightningnetwork/lnd/cert v1.2.2 h1:71YK6hogeJtxSxw2teq3eGeuy4rHGKcFf
|
|||||||
github.com/lightningnetwork/lnd/cert v1.2.2/go.mod h1:jQmFn/Ez4zhDgq2hnYSw8r35bqGVxViXhX6Cd7HXM6U=
|
github.com/lightningnetwork/lnd/cert v1.2.2/go.mod h1:jQmFn/Ez4zhDgq2hnYSw8r35bqGVxViXhX6Cd7HXM6U=
|
||||||
github.com/lightningnetwork/lnd/clock v1.1.1 h1:OfR3/zcJd2RhH0RU+zX/77c0ZiOnIMsDIBjgjWdZgA0=
|
github.com/lightningnetwork/lnd/clock v1.1.1 h1:OfR3/zcJd2RhH0RU+zX/77c0ZiOnIMsDIBjgjWdZgA0=
|
||||||
github.com/lightningnetwork/lnd/clock v1.1.1/go.mod h1:mGnAhPyjYZQJmebS7aevElXKTFDuO+uNFFfMXK1W8xQ=
|
github.com/lightningnetwork/lnd/clock v1.1.1/go.mod h1:mGnAhPyjYZQJmebS7aevElXKTFDuO+uNFFfMXK1W8xQ=
|
||||||
github.com/lightningnetwork/lnd/fn v1.2.3 h1:Q1OrgNSgQynVheBNa16CsKVov1JI5N2AR6G07x9Mles=
|
github.com/lightningnetwork/lnd/fn v1.2.5 h1:pGMz0BDUxrhvOtShD4FIysdVy+ulfFAnFvTKjZO5Pp8=
|
||||||
github.com/lightningnetwork/lnd/fn v1.2.3/go.mod h1:SyFohpVrARPKH3XVAJZlXdVe+IwMYc4OMAvrDY32kw0=
|
github.com/lightningnetwork/lnd/fn v1.2.5/go.mod h1:SyFohpVrARPKH3XVAJZlXdVe+IwMYc4OMAvrDY32kw0=
|
||||||
github.com/lightningnetwork/lnd/healthcheck v1.2.6 h1:1sWhqr93GdkWy4+6U7JxBfcyZIE78MhIHTJZfPx7qqI=
|
github.com/lightningnetwork/lnd/healthcheck v1.2.6 h1:1sWhqr93GdkWy4+6U7JxBfcyZIE78MhIHTJZfPx7qqI=
|
||||||
github.com/lightningnetwork/lnd/healthcheck v1.2.6/go.mod h1:Mu02um4CWY/zdTOvFje7WJgJcHyX2zq/FG3MhOAiGaQ=
|
github.com/lightningnetwork/lnd/healthcheck v1.2.6/go.mod h1:Mu02um4CWY/zdTOvFje7WJgJcHyX2zq/FG3MhOAiGaQ=
|
||||||
github.com/lightningnetwork/lnd/kvdb v1.4.11 h1:fk1HMVFrsVK3xqU7q+JWHRgBltw/a2qIg1E3zazMb/8=
|
github.com/lightningnetwork/lnd/kvdb v1.4.11 h1:fk1HMVFrsVK3xqU7q+JWHRgBltw/a2qIg1E3zazMb/8=
|
||||||
|
|||||||
@@ -170,6 +170,18 @@ type ChannelUpdateHandler interface {
|
|||||||
// will only ever be called once. If no CommitSig is owed in the
|
// will only ever be called once. If no CommitSig is owed in the
|
||||||
// argument's LinkDirection, then we will call this hook immediately.
|
// argument's LinkDirection, then we will call this hook immediately.
|
||||||
OnCommitOnce(LinkDirection, func())
|
OnCommitOnce(LinkDirection, func())
|
||||||
|
|
||||||
|
// InitStfu allows us to initiate quiescence on this link. It returns
|
||||||
|
// a receive only channel that will block until quiescence has been
|
||||||
|
// achieved, or definitively fails. The return value is the
|
||||||
|
// ChannelParty who holds the role of initiator or Err if the operation
|
||||||
|
// fails.
|
||||||
|
//
|
||||||
|
// This operation has been added to allow channels to be quiesced via
|
||||||
|
// RPC. It may be removed or reworked in the future as RPC initiated
|
||||||
|
// quiescence is a holdover until we have downstream protocols that use
|
||||||
|
// it.
|
||||||
|
InitStfu() <-chan fn.Result[lntypes.ChannelParty]
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommitHookID is a value that is used to uniquely identify hooks in the
|
// CommitHookID is a value that is used to uniquely identify hooks in the
|
||||||
|
|||||||
+192
-12
@@ -283,6 +283,10 @@ type ChannelLinkConfig struct {
|
|||||||
// invalid.
|
// invalid.
|
||||||
DisallowRouteBlinding bool
|
DisallowRouteBlinding bool
|
||||||
|
|
||||||
|
// DisallowQuiescence is a flag that can be used to disable the
|
||||||
|
// quiescence protocol.
|
||||||
|
DisallowQuiescence bool
|
||||||
|
|
||||||
// MaxFeeExposure is the threshold in milli-satoshis after which we'll
|
// MaxFeeExposure is the threshold in milli-satoshis after which we'll
|
||||||
// restrict the flow of HTLCs and fee updates.
|
// restrict the flow of HTLCs and fee updates.
|
||||||
MaxFeeExposure lnwire.MilliSatoshi
|
MaxFeeExposure lnwire.MilliSatoshi
|
||||||
@@ -392,6 +396,15 @@ type channelLink struct {
|
|||||||
// our next CommitSig.
|
// our next CommitSig.
|
||||||
incomingCommitHooks hookMap
|
incomingCommitHooks hookMap
|
||||||
|
|
||||||
|
// quiescer is the state machine that tracks where this channel is with
|
||||||
|
// respect to the quiescence protocol.
|
||||||
|
quiescer Quiescer
|
||||||
|
|
||||||
|
// quiescenceReqs is a queue of requests to quiesce this link. The
|
||||||
|
// members of the queue are send-only channels we should call back with
|
||||||
|
// the result.
|
||||||
|
quiescenceReqs chan StfuReq
|
||||||
|
|
||||||
// ContextGuard is a helper that encapsulates a wait group and quit
|
// ContextGuard is a helper that encapsulates a wait group and quit
|
||||||
// channel and allows contexts that either block or cancel on those
|
// channel and allows contexts that either block or cancel on those
|
||||||
// depending on the use case.
|
// depending on the use case.
|
||||||
@@ -467,6 +480,29 @@ func NewChannelLink(cfg ChannelLinkConfig,
|
|||||||
cfg.MaxFeeExposure = DefaultMaxFeeExposure
|
cfg.MaxFeeExposure = DefaultMaxFeeExposure
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var qsm Quiescer
|
||||||
|
if !cfg.DisallowQuiescence {
|
||||||
|
qsm = NewQuiescer(QuiescerCfg{
|
||||||
|
chanID: lnwire.NewChanIDFromOutPoint(
|
||||||
|
channel.ChannelPoint(),
|
||||||
|
),
|
||||||
|
channelInitiator: channel.Initiator(),
|
||||||
|
sendMsg: func(s lnwire.Stfu) error {
|
||||||
|
return cfg.Peer.SendMessage(false, &s)
|
||||||
|
},
|
||||||
|
timeoutDuration: defaultQuiescenceTimeout,
|
||||||
|
onTimeout: func() {
|
||||||
|
cfg.Peer.Disconnect(ErrQuiescenceTimeout)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
qsm = &quiescerNoop{}
|
||||||
|
}
|
||||||
|
|
||||||
|
quiescenceReqs := make(
|
||||||
|
chan fn.Req[fn.Unit, fn.Result[lntypes.ChannelParty]], 1,
|
||||||
|
)
|
||||||
|
|
||||||
return &channelLink{
|
return &channelLink{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
channel: channel,
|
channel: channel,
|
||||||
@@ -476,6 +512,8 @@ func NewChannelLink(cfg ChannelLinkConfig,
|
|||||||
flushHooks: newHookMap(),
|
flushHooks: newHookMap(),
|
||||||
outgoingCommitHooks: newHookMap(),
|
outgoingCommitHooks: newHookMap(),
|
||||||
incomingCommitHooks: newHookMap(),
|
incomingCommitHooks: newHookMap(),
|
||||||
|
quiescer: qsm,
|
||||||
|
quiescenceReqs: quiescenceReqs,
|
||||||
ContextGuard: fn.NewContextGuard(),
|
ContextGuard: fn.NewContextGuard(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -628,20 +666,37 @@ func (l *channelLink) WaitForShutdown() {
|
|||||||
// actively accept requests to forward HTLC's. We're able to forward HTLC's if
|
// actively accept requests to forward HTLC's. We're able to forward HTLC's if
|
||||||
// we are eligible to update AND the channel isn't currently flushing the
|
// we are eligible to update AND the channel isn't currently flushing the
|
||||||
// outgoing half of the channel.
|
// outgoing half of the channel.
|
||||||
|
//
|
||||||
|
// NOTE: MUST NOT be called from the main event loop.
|
||||||
func (l *channelLink) EligibleToForward() bool {
|
func (l *channelLink) EligibleToForward() bool {
|
||||||
return l.EligibleToUpdate() &&
|
l.RLock()
|
||||||
!l.IsFlushing(Outgoing)
|
defer l.RUnlock()
|
||||||
|
|
||||||
|
return l.eligibleToForward()
|
||||||
}
|
}
|
||||||
|
|
||||||
// EligibleToUpdate returns a bool indicating if the channel is able to update
|
// eligibleToForward returns a bool indicating if the channel is able to
|
||||||
|
// actively accept requests to forward HTLC's. We're able to forward HTLC's if
|
||||||
|
// we are eligible to update AND the channel isn't currently flushing the
|
||||||
|
// outgoing half of the channel.
|
||||||
|
//
|
||||||
|
// NOTE: MUST be called from the main event loop.
|
||||||
|
func (l *channelLink) eligibleToForward() bool {
|
||||||
|
return l.eligibleToUpdate() && !l.IsFlushing(Outgoing)
|
||||||
|
}
|
||||||
|
|
||||||
|
// eligibleToUpdate returns a bool indicating if the channel is able to update
|
||||||
// channel state. We're able to update channel state if we know the remote
|
// channel state. We're able to update channel state if we know the remote
|
||||||
// party's next revocation point. Otherwise, we can't initiate new channel
|
// party's next revocation point. Otherwise, we can't initiate new channel
|
||||||
// state. We also require that the short channel ID not be the all-zero source
|
// state. We also require that the short channel ID not be the all-zero source
|
||||||
// ID, meaning that the channel has had its ID finalized.
|
// ID, meaning that the channel has had its ID finalized.
|
||||||
func (l *channelLink) EligibleToUpdate() bool {
|
//
|
||||||
|
// NOTE: MUST be called from the main event loop.
|
||||||
|
func (l *channelLink) eligibleToUpdate() bool {
|
||||||
return l.channel.RemoteNextRevocation() != nil &&
|
return l.channel.RemoteNextRevocation() != nil &&
|
||||||
l.ShortChanID() != hop.Source &&
|
l.channel.ShortChanID() != hop.Source &&
|
||||||
l.isReestablished()
|
l.isReestablished() &&
|
||||||
|
l.quiescer.CanSendUpdates()
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnableAdds sets the ChannelUpdateHandler state to allow UpdateAddHtlc's in
|
// EnableAdds sets the ChannelUpdateHandler state to allow UpdateAddHtlc's in
|
||||||
@@ -705,6 +760,27 @@ func (l *channelLink) OnCommitOnce(direction LinkDirection, hook func()) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InitStfu allows us to initiate quiescence on this link. It returns a receive
|
||||||
|
// only channel that will block until quiescence has been achieved, or
|
||||||
|
// definitively fails.
|
||||||
|
//
|
||||||
|
// This operation has been added to allow channels to be quiesced via RPC. It
|
||||||
|
// may be removed or reworked in the future as RPC initiated quiescence is a
|
||||||
|
// holdover until we have downstream protocols that use it.
|
||||||
|
func (l *channelLink) InitStfu() <-chan fn.Result[lntypes.ChannelParty] {
|
||||||
|
req, out := fn.NewReq[fn.Unit, fn.Result[lntypes.ChannelParty]](
|
||||||
|
fn.Unit{},
|
||||||
|
)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case l.quiescenceReqs <- req:
|
||||||
|
case <-l.Quit:
|
||||||
|
req.Resolve(fn.Err[lntypes.ChannelParty](ErrLinkShuttingDown))
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// isReestablished returns true if the link has successfully completed the
|
// isReestablished returns true if the link has successfully completed the
|
||||||
// channel reestablishment dance.
|
// channel reestablishment dance.
|
||||||
func (l *channelLink) isReestablished() bool {
|
func (l *channelLink) isReestablished() bool {
|
||||||
@@ -1450,6 +1526,20 @@ func (l *channelLink) htlcManager() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case qReq := <-l.quiescenceReqs:
|
||||||
|
l.quiescer.InitStfu(qReq)
|
||||||
|
|
||||||
|
if l.noDanglingUpdates(lntypes.Local) {
|
||||||
|
err := l.quiescer.SendOwedStfu()
|
||||||
|
if err != nil {
|
||||||
|
l.stfuFailf(
|
||||||
|
"SendOwedStfu: %s", err.Error(),
|
||||||
|
)
|
||||||
|
res := fn.Err[lntypes.ChannelParty](err)
|
||||||
|
qReq.Resolve(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case <-l.Quit:
|
case <-l.Quit:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1584,13 +1674,14 @@ func (l *channelLink) handleDownstreamUpdateAdd(pkt *htlcPacket) error {
|
|||||||
return errors.New("not an UpdateAddHTLC packet")
|
return errors.New("not an UpdateAddHTLC packet")
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we are flushing the link in the outgoing direction we can't add
|
// If we are flushing the link in the outgoing direction or we have
|
||||||
// new htlcs to the link and we need to bounce it
|
// already sent Stfu, then we can't add new htlcs to the link and we
|
||||||
if l.IsFlushing(Outgoing) {
|
// need to bounce it.
|
||||||
|
if l.IsFlushing(Outgoing) || !l.quiescer.CanSendUpdates() {
|
||||||
l.mailBox.FailAdd(pkt)
|
l.mailBox.FailAdd(pkt)
|
||||||
|
|
||||||
return NewDetailedLinkError(
|
return NewDetailedLinkError(
|
||||||
&lnwire.FailPermanentChannelFailure{},
|
&lnwire.FailTemporaryChannelFailure{},
|
||||||
OutgoingFailureLinkNotEligible,
|
OutgoingFailureLinkNotEligible,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1693,6 +1784,15 @@ func (l *channelLink) handleDownstreamUpdateAdd(pkt *htlcPacket) error {
|
|||||||
//
|
//
|
||||||
// TODO(roasbeef): add sync ntfn to ensure switch always has consistent view?
|
// TODO(roasbeef): add sync ntfn to ensure switch always has consistent view?
|
||||||
func (l *channelLink) handleDownstreamPkt(pkt *htlcPacket) {
|
func (l *channelLink) handleDownstreamPkt(pkt *htlcPacket) {
|
||||||
|
if pkt.htlc.MsgType().IsChannelUpdate() &&
|
||||||
|
!l.quiescer.CanSendUpdates() {
|
||||||
|
|
||||||
|
l.log.Warnf("unable to process channel update. "+
|
||||||
|
"ChannelID=%v is quiescent.", l.ChanID)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
switch htlc := pkt.htlc.(type) {
|
switch htlc := pkt.htlc.(type) {
|
||||||
case *lnwire.UpdateAddHTLC:
|
case *lnwire.UpdateAddHTLC:
|
||||||
// Handle add message. The returned error can be ignored,
|
// Handle add message. The returned error can be ignored,
|
||||||
@@ -1937,6 +2037,13 @@ func (l *channelLink) cleanupSpuriousResponse(pkt *htlcPacket) {
|
|||||||
// updates from the upstream peer. The upstream peer is the peer whom we have a
|
// updates from the upstream peer. The upstream peer is the peer whom we have a
|
||||||
// direct channel with, updating our respective commitment chains.
|
// direct channel with, updating our respective commitment chains.
|
||||||
func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) {
|
func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) {
|
||||||
|
// First check if the message is an update and we are capable of
|
||||||
|
// receiving updates right now.
|
||||||
|
if msg.MsgType().IsChannelUpdate() && !l.quiescer.CanRecvUpdates() {
|
||||||
|
l.stfuFailf("update received after stfu: %T", msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
case *lnwire.UpdateAddHTLC:
|
case *lnwire.UpdateAddHTLC:
|
||||||
if l.IsFlushing(Incoming) {
|
if l.IsFlushing(Incoming) {
|
||||||
@@ -2325,6 +2432,15 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we need to send out an Stfu, this would be the time to do
|
||||||
|
// so.
|
||||||
|
if l.noDanglingUpdates(lntypes.Local) {
|
||||||
|
err = l.quiescer.SendOwedStfu()
|
||||||
|
if err != nil {
|
||||||
|
l.stfuFailf("sendOwedStfu: %v", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Now that we have finished processing the incoming CommitSig
|
// Now that we have finished processing the incoming CommitSig
|
||||||
// and sent out our RevokeAndAck, we invoke the flushHooks if
|
// and sent out our RevokeAndAck, we invoke the flushHooks if
|
||||||
// the channel state is clean.
|
// the channel state is clean.
|
||||||
@@ -2391,8 +2507,22 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we can send updates then we can process adds in case we
|
||||||
|
// are the exit hop and need to send back resolutions, or in
|
||||||
|
// case there are validity issues with the packets. Otherwise
|
||||||
|
// we defer the action until resume.
|
||||||
|
//
|
||||||
|
// We are free to process the settles and fails without this
|
||||||
|
// check since processing those can't result in further updates
|
||||||
|
// to this channel link.
|
||||||
|
if l.quiescer.CanSendUpdates() {
|
||||||
|
l.processRemoteAdds(fwdPkg)
|
||||||
|
} else {
|
||||||
|
l.quiescer.OnResume(func() {
|
||||||
|
l.processRemoteAdds(fwdPkg)
|
||||||
|
})
|
||||||
|
}
|
||||||
l.processRemoteSettleFails(fwdPkg)
|
l.processRemoteSettleFails(fwdPkg)
|
||||||
l.processRemoteAdds(fwdPkg)
|
|
||||||
|
|
||||||
// If the link failed during processing the adds, we must
|
// If the link failed during processing the adds, we must
|
||||||
// return to ensure we won't attempted to update the state
|
// return to ensure we won't attempted to update the state
|
||||||
@@ -2458,6 +2588,12 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) {
|
|||||||
// Update the mailbox's feerate as well.
|
// Update the mailbox's feerate as well.
|
||||||
l.mailBox.SetFeeRate(fee)
|
l.mailBox.SetFeeRate(fee)
|
||||||
|
|
||||||
|
case *lnwire.Stfu:
|
||||||
|
err := l.handleStfu(msg)
|
||||||
|
if err != nil {
|
||||||
|
l.stfuFailf("handleStfu: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
// In the case where we receive a warning message from our peer, just
|
// In the case where we receive a warning message from our peer, just
|
||||||
// log it and move on. We choose not to disconnect from our peer,
|
// log it and move on. We choose not to disconnect from our peer,
|
||||||
// although we "MAY" do so according to the specification.
|
// although we "MAY" do so according to the specification.
|
||||||
@@ -2490,6 +2626,50 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleStfu implements the top-level logic for handling the Stfu message from
|
||||||
|
// our peer.
|
||||||
|
func (l *channelLink) handleStfu(stfu *lnwire.Stfu) error {
|
||||||
|
if !l.noDanglingUpdates(lntypes.Remote) {
|
||||||
|
return ErrPendingRemoteUpdates
|
||||||
|
}
|
||||||
|
err := l.quiescer.RecvStfu(*stfu)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we can immediately send an Stfu response back, we will.
|
||||||
|
if l.noDanglingUpdates(lntypes.Local) {
|
||||||
|
return l.quiescer.SendOwedStfu()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// stfuFailf fails the link in the case where the requirements of the quiescence
|
||||||
|
// protocol are violated. In all cases we opt to drop the connection as only
|
||||||
|
// link state (as opposed to channel state) is affected.
|
||||||
|
func (l *channelLink) stfuFailf(format string, args ...interface{}) {
|
||||||
|
l.failf(LinkFailureError{
|
||||||
|
code: ErrStfuViolation,
|
||||||
|
FailureAction: LinkFailureDisconnect,
|
||||||
|
PermanentFailure: false,
|
||||||
|
Warning: true,
|
||||||
|
}, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// noDanglingUpdates returns true when there are 0 updates that were originally
|
||||||
|
// issued by whose on either the Local or Remote commitment transaction.
|
||||||
|
func (l *channelLink) noDanglingUpdates(whose lntypes.ChannelParty) bool {
|
||||||
|
pendingOnLocal := l.channel.NumPendingUpdates(
|
||||||
|
whose, lntypes.Local,
|
||||||
|
)
|
||||||
|
pendingOnRemote := l.channel.NumPendingUpdates(
|
||||||
|
whose, lntypes.Remote,
|
||||||
|
)
|
||||||
|
|
||||||
|
return pendingOnLocal == 0 && pendingOnRemote == 0
|
||||||
|
}
|
||||||
|
|
||||||
// ackDownStreamPackets is responsible for removing htlcs from a link's mailbox
|
// ackDownStreamPackets is responsible for removing htlcs from a link's mailbox
|
||||||
// for packets delivered from server, and cleaning up any circuits closed by
|
// for packets delivered from server, and cleaning up any circuits closed by
|
||||||
// signing a previous commitment txn. This method ensures that the circuits are
|
// signing a previous commitment txn. This method ensures that the circuits are
|
||||||
@@ -3293,7 +3473,7 @@ func (l *channelLink) updateChannelFee(feePerKw chainfee.SatPerKWeight) error {
|
|||||||
|
|
||||||
// We skip sending the UpdateFee message if the channel is not
|
// We skip sending the UpdateFee message if the channel is not
|
||||||
// currently eligible to forward messages.
|
// currently eligible to forward messages.
|
||||||
if !l.EligibleToUpdate() {
|
if !l.eligibleToUpdate() {
|
||||||
l.log.Debugf("skipping fee update for inactive channel")
|
l.log.Debugf("skipping fee update for inactive channel")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7540,3 +7540,92 @@ func TestLinkFlushHooksCalled(t *testing.T) {
|
|||||||
ctx.receiveRevAndAckAliceToBob()
|
ctx.receiveRevAndAckAliceToBob()
|
||||||
assertHookCalled(true)
|
assertHookCalled(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestLinkQuiescenceExitHopProcessingDeferred ensures that we do not send back
|
||||||
|
// htlc resolution messages in the case where the link is quiescent AND we are
|
||||||
|
// the exit hop. This is needed because we handle exit hop processing in the
|
||||||
|
// link instead of the switch and we process htlc resolutions when we receive
|
||||||
|
// a RevokeAndAck. Because of this we need to ensure that we hold off on
|
||||||
|
// processing the remote adds when we are quiescent. Later, when the channel
|
||||||
|
// update traffic is allowed to resume, we will need to verify that the actions
|
||||||
|
// we didn't run during the initial RevokeAndAck are run.
|
||||||
|
func TestLinkQuiescenceExitHopProcessingDeferred(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Initialize two channel state machines for testing.
|
||||||
|
alice, bob, err := createMirroredChannel(
|
||||||
|
t, btcutil.SatoshiPerBitcoin, btcutil.SatoshiPerBitcoin,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Build a single edge network to test channel quiescence.
|
||||||
|
network := newTwoHopNetwork(
|
||||||
|
t, alice.channel, bob.channel, testStartingHeight,
|
||||||
|
)
|
||||||
|
aliceLink := network.aliceChannelLink
|
||||||
|
bobLink := network.bobChannelLink
|
||||||
|
|
||||||
|
// Generate an invoice for Bob so that Alice can pay him.
|
||||||
|
htlcID := uint64(0)
|
||||||
|
htlc, invoice := generateHtlcAndInvoice(t, htlcID)
|
||||||
|
err = network.bobServer.registry.AddInvoice(
|
||||||
|
nil, *invoice, htlc.PaymentHash,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Establish a payment circuit for Alice
|
||||||
|
circuit := &PaymentCircuit{
|
||||||
|
Incoming: CircuitKey{
|
||||||
|
HtlcID: htlcID,
|
||||||
|
},
|
||||||
|
PaymentHash: htlc.PaymentHash,
|
||||||
|
}
|
||||||
|
circuitMap := network.aliceServer.htlcSwitch.circuits
|
||||||
|
_, err = circuitMap.CommitCircuits(circuit)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Add a switch packet to Alice's switch so that she can initialize the
|
||||||
|
// payment attempt.
|
||||||
|
err = aliceLink.handleSwitchPacket(&htlcPacket{
|
||||||
|
incomingHTLCID: htlcID,
|
||||||
|
htlc: htlc,
|
||||||
|
circuit: circuit,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// give alice enough time to fire the update_add
|
||||||
|
// TODO(proofofkeags): make this not depend on a flakey sleep.
|
||||||
|
<-time.After(time.Millisecond)
|
||||||
|
|
||||||
|
// bob initiates stfu which he can do immediately since he doesn't have
|
||||||
|
// local updates
|
||||||
|
<-bobLink.InitStfu()
|
||||||
|
|
||||||
|
// wait for other possible messages to play out
|
||||||
|
<-time.After(1 * time.Second)
|
||||||
|
|
||||||
|
ensureNoUpdateAfterStfu := func(t *testing.T, trace []lnwire.Message) {
|
||||||
|
stfuReceived := false
|
||||||
|
for _, msg := range trace {
|
||||||
|
if msg.MsgType() == lnwire.MsgStfu {
|
||||||
|
stfuReceived = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if stfuReceived && msg.MsgType().IsChannelUpdate() {
|
||||||
|
t.Fatalf("channel update after stfu: %v",
|
||||||
|
msg.MsgType())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
network.aliceServer.protocolTraceMtx.Lock()
|
||||||
|
ensureNoUpdateAfterStfu(t, network.aliceServer.protocolTrace)
|
||||||
|
network.aliceServer.protocolTraceMtx.Unlock()
|
||||||
|
|
||||||
|
network.bobServer.protocolTraceMtx.Lock()
|
||||||
|
ensureNoUpdateAfterStfu(t, network.bobServer.protocolTrace)
|
||||||
|
network.bobServer.protocolTraceMtx.Unlock()
|
||||||
|
|
||||||
|
// TODO(proofofkeags): make sure these actions are run on resume.
|
||||||
|
}
|
||||||
@@ -51,6 +51,12 @@ const (
|
|||||||
// circuit map. This is non-fatal and will resolve itself (usually
|
// circuit map. This is non-fatal and will resolve itself (usually
|
||||||
// within several minutes).
|
// within several minutes).
|
||||||
ErrCircuitError
|
ErrCircuitError
|
||||||
|
|
||||||
|
// ErrStfuViolation indicates that the quiescence protocol has been
|
||||||
|
// violated, either because Stfu has been sent/received at an invalid
|
||||||
|
// time, or that an update has been sent/received while the channel is
|
||||||
|
// quiesced.
|
||||||
|
ErrStfuViolation
|
||||||
)
|
)
|
||||||
|
|
||||||
// LinkFailureAction is an enum-like type that describes the action that should
|
// LinkFailureAction is an enum-like type that describes the action that should
|
||||||
@@ -122,6 +128,8 @@ func (e LinkFailureError) Error() string {
|
|||||||
return "unable to resume channel, recovery required"
|
return "unable to resume channel, recovery required"
|
||||||
case ErrCircuitError:
|
case ErrCircuitError:
|
||||||
return "non-fatal circuit map error"
|
return "non-fatal circuit map error"
|
||||||
|
case ErrStfuViolation:
|
||||||
|
return "quiescence protocol executed improperly"
|
||||||
default:
|
default:
|
||||||
return "unknown error"
|
return "unknown error"
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-2
@@ -153,8 +153,10 @@ type mockServer struct {
|
|||||||
|
|
||||||
t testing.TB
|
t testing.TB
|
||||||
|
|
||||||
name string
|
name string
|
||||||
messages chan lnwire.Message
|
messages chan lnwire.Message
|
||||||
|
protocolTraceMtx sync.Mutex
|
||||||
|
protocolTrace []lnwire.Message
|
||||||
|
|
||||||
id [33]byte
|
id [33]byte
|
||||||
htlcSwitch *Switch
|
htlcSwitch *Switch
|
||||||
@@ -289,6 +291,10 @@ func (s *mockServer) Start() error {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case msg := <-s.messages:
|
case msg := <-s.messages:
|
||||||
|
s.protocolTraceMtx.Lock()
|
||||||
|
s.protocolTrace = append(s.protocolTrace, msg)
|
||||||
|
s.protocolTraceMtx.Unlock()
|
||||||
|
|
||||||
var shouldSkip bool
|
var shouldSkip bool
|
||||||
|
|
||||||
for _, interceptor := range s.interceptorFuncs {
|
for _, interceptor := range s.interceptorFuncs {
|
||||||
@@ -627,6 +633,8 @@ func (s *mockServer) readHandler(message lnwire.Message) error {
|
|||||||
targetChan = msg.ChanID
|
targetChan = msg.ChanID
|
||||||
case *lnwire.UpdateFee:
|
case *lnwire.UpdateFee:
|
||||||
targetChan = msg.ChanID
|
targetChan = msg.ChanID
|
||||||
|
case *lnwire.Stfu:
|
||||||
|
targetChan = msg.ChanID
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown message type: %T", msg)
|
return fmt.Errorf("unknown message type: %T", msg)
|
||||||
}
|
}
|
||||||
@@ -950,6 +958,14 @@ func (f *mockChannelLink) OnFlushedOnce(func()) {
|
|||||||
func (f *mockChannelLink) OnCommitOnce(LinkDirection, func()) {
|
func (f *mockChannelLink) OnCommitOnce(LinkDirection, func()) {
|
||||||
// TODO(proofofkeags): Implement
|
// TODO(proofofkeags): Implement
|
||||||
}
|
}
|
||||||
|
func (f *mockChannelLink) InitStfu() <-chan fn.Result[lntypes.ChannelParty] {
|
||||||
|
// TODO(proofofkeags): Implement
|
||||||
|
c := make(chan fn.Result[lntypes.ChannelParty], 1)
|
||||||
|
|
||||||
|
c <- fn.Errf[lntypes.ChannelParty]("InitStfu not implemented")
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
func (f *mockChannelLink) FundingCustomBlob() fn.Option[tlv.Blob] {
|
func (f *mockChannelLink) FundingCustomBlob() fn.Option[tlv.Blob] {
|
||||||
return fn.None[tlv.Blob]()
|
return fn.None[tlv.Blob]()
|
||||||
|
|||||||
@@ -0,0 +1,575 @@
|
|||||||
|
package htlcswitch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btclog/v2"
|
||||||
|
"github.com/lightningnetwork/lnd/build"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrInvalidStfu indicates that the Stfu we have received is invalid.
|
||||||
|
// This can happen in instances where we have not sent Stfu but we have
|
||||||
|
// received one with the initiator field set to false.
|
||||||
|
ErrInvalidStfu = fmt.Errorf("stfu received is invalid")
|
||||||
|
|
||||||
|
// ErrStfuAlreadySent indicates that this channel has already sent an
|
||||||
|
// Stfu message for this negotiation.
|
||||||
|
ErrStfuAlreadySent = fmt.Errorf("stfu already sent")
|
||||||
|
|
||||||
|
// ErrStfuAlreadyRcvd indicates that this channel has already received
|
||||||
|
// an Stfu message for this negotiation.
|
||||||
|
ErrStfuAlreadyRcvd = fmt.Errorf("stfu already received")
|
||||||
|
|
||||||
|
// ErrNoQuiescenceInitiator indicates that the caller has requested the
|
||||||
|
// quiescence initiator for a channel that is not yet quiescent.
|
||||||
|
ErrNoQuiescenceInitiator = fmt.Errorf(
|
||||||
|
"indeterminate quiescence initiator: channel is not quiescent",
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrPendingRemoteUpdates indicates that we have received an Stfu while
|
||||||
|
// the remote party has issued updates that are not yet bilaterally
|
||||||
|
// committed.
|
||||||
|
ErrPendingRemoteUpdates = fmt.Errorf(
|
||||||
|
"stfu received with pending remote updates",
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrPendingLocalUpdates indicates that we are attempting to send an
|
||||||
|
// Stfu while we have issued updates that are not yet bilaterally
|
||||||
|
// committed.
|
||||||
|
ErrPendingLocalUpdates = fmt.Errorf(
|
||||||
|
"stfu send attempted with pending local updates",
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrQuiescenceTimeout indicates that the quiescer has been quiesced
|
||||||
|
// beyond the allotted time.
|
||||||
|
ErrQuiescenceTimeout = fmt.Errorf(
|
||||||
|
"quiescence timeout",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultQuiescenceTimeout = 30 * time.Second
|
||||||
|
|
||||||
|
type StfuReq = fn.Req[fn.Unit, fn.Result[lntypes.ChannelParty]]
|
||||||
|
|
||||||
|
// Quiescer is the public interface of the quiescence mechanism. Callers of the
|
||||||
|
// quiescence API should not need any methods besides the ones detailed here.
|
||||||
|
type Quiescer interface {
|
||||||
|
// IsQuiescent returns true if the state machine has been driven all the
|
||||||
|
// way to completion. If this returns true, processes that depend on
|
||||||
|
// channel quiescence may proceed.
|
||||||
|
IsQuiescent() bool
|
||||||
|
|
||||||
|
// QuiescenceInitiator determines which ChannelParty is the initiator of
|
||||||
|
// quiescence for the purposes of downstream protocols. If the channel
|
||||||
|
// is not currently quiescent, this method will return
|
||||||
|
// ErrNoDownstreamLeader.
|
||||||
|
QuiescenceInitiator() fn.Result[lntypes.ChannelParty]
|
||||||
|
|
||||||
|
// InitStfu instructs the quiescer that we intend to begin a quiescence
|
||||||
|
// negotiation where we are the initiator. We don't yet send stfu yet
|
||||||
|
// because we need to wait for the link to give us a valid opportunity
|
||||||
|
// to do so.
|
||||||
|
InitStfu(req StfuReq)
|
||||||
|
|
||||||
|
// RecvStfu is called when we receive an Stfu message from the remote.
|
||||||
|
RecvStfu(stfu lnwire.Stfu) error
|
||||||
|
|
||||||
|
// CanRecvUpdates returns true if we haven't yet received an Stfu which
|
||||||
|
// would mark the end of the remote's ability to send updates.
|
||||||
|
CanRecvUpdates() bool
|
||||||
|
|
||||||
|
// CanSendUpdates returns true if we haven't yet sent an Stfu which
|
||||||
|
// would mark the end of our ability to send updates.
|
||||||
|
CanSendUpdates() bool
|
||||||
|
|
||||||
|
// SendOwedStfu sends Stfu if it owes one. It returns an error if the
|
||||||
|
// state machine is in an invalid state.
|
||||||
|
SendOwedStfu() error
|
||||||
|
|
||||||
|
// OnResume accepts a no return closure that will run when the quiescer
|
||||||
|
// is resumed.
|
||||||
|
OnResume(hook func())
|
||||||
|
|
||||||
|
// Resume runs all of the deferred actions that have accumulated while
|
||||||
|
// the channel has been quiescent and then resets the quiescer state to
|
||||||
|
// its initial state.
|
||||||
|
Resume()
|
||||||
|
}
|
||||||
|
|
||||||
|
// QuiescerCfg is a config structure used to initialize a quiescer giving it the
|
||||||
|
// appropriate functionality to interact with the channel state that the
|
||||||
|
// quiescer must syncrhonize with.
|
||||||
|
type QuiescerCfg struct {
|
||||||
|
// chanID marks what channel we are managing the state machine for. This
|
||||||
|
// is important because the quiescer needs to know the ChannelID to
|
||||||
|
// construct the Stfu message.
|
||||||
|
chanID lnwire.ChannelID
|
||||||
|
|
||||||
|
// channelInitiator indicates which ChannelParty originally opened the
|
||||||
|
// channel. This is used to break ties when both sides of the channel
|
||||||
|
// send Stfu claiming to be the initiator.
|
||||||
|
channelInitiator lntypes.ChannelParty
|
||||||
|
|
||||||
|
// sendMsg is a function that can be used to send an Stfu message over
|
||||||
|
// the wire.
|
||||||
|
sendMsg func(lnwire.Stfu) error
|
||||||
|
|
||||||
|
// timeoutDuration is the Duration that we will wait from the moment the
|
||||||
|
// channel is considered quiescent before we call the onTimeout function
|
||||||
|
timeoutDuration time.Duration
|
||||||
|
|
||||||
|
// onTimeout is a function that will be called in the event that the
|
||||||
|
// Quiescer has not been resumed before the timeout is reached. If
|
||||||
|
// Quiescer.Resume is called before the timeout has been raeached, then
|
||||||
|
// onTimeout will not be called until the quiescer reaches a quiescent
|
||||||
|
// state again.
|
||||||
|
onTimeout func()
|
||||||
|
}
|
||||||
|
|
||||||
|
// QuiescerLive is a state machine that tracks progression through the
|
||||||
|
// quiescence protocol.
|
||||||
|
type QuiescerLive struct {
|
||||||
|
cfg QuiescerCfg
|
||||||
|
|
||||||
|
// log is a quiescer-scoped logging instance.
|
||||||
|
log btclog.Logger
|
||||||
|
|
||||||
|
// localInit indicates whether our path through this state machine was
|
||||||
|
// initiated by our node. This can be true or false independently of
|
||||||
|
// remoteInit.
|
||||||
|
localInit bool
|
||||||
|
|
||||||
|
// remoteInit indicates whether we received Stfu from our peer where the
|
||||||
|
// message indicated that the remote node believes it was the initiator.
|
||||||
|
// This can be true or false independently of localInit.
|
||||||
|
remoteInit bool
|
||||||
|
|
||||||
|
// sent tracks whether or not we have emitted Stfu for sending.
|
||||||
|
sent bool
|
||||||
|
|
||||||
|
// received tracks whether or not we have received Stfu from our peer.
|
||||||
|
received bool
|
||||||
|
|
||||||
|
// activeQuiescenceRequest is a possibly None Request that we should
|
||||||
|
// resolve when we complete quiescence.
|
||||||
|
activeQuiescenceReq fn.Option[StfuReq]
|
||||||
|
|
||||||
|
// resumeQueue is a slice of hooks that will be called when the quiescer
|
||||||
|
// is resumed. These are actions that needed to be deferred while the
|
||||||
|
// channel was quiescent.
|
||||||
|
resumeQueue []func()
|
||||||
|
|
||||||
|
// timeoutTimer is a field that is used to hold onto the timeout job
|
||||||
|
// when we reach quiescence.
|
||||||
|
timeoutTimer *time.Timer
|
||||||
|
|
||||||
|
sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewQuiescer creates a new quiescer for the given channel.
|
||||||
|
func NewQuiescer(cfg QuiescerCfg) Quiescer {
|
||||||
|
logPrefix := fmt.Sprintf("Quiescer(%v):", cfg.chanID)
|
||||||
|
|
||||||
|
return &QuiescerLive{
|
||||||
|
cfg: cfg,
|
||||||
|
log: build.NewPrefixLog(logPrefix, log),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecvStfu is called when we receive an Stfu message from the remote.
|
||||||
|
func (q *QuiescerLive) RecvStfu(msg lnwire.Stfu) error {
|
||||||
|
q.Lock()
|
||||||
|
defer q.Unlock()
|
||||||
|
|
||||||
|
return q.recvStfu(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// recvStfu is called when we receive an Stfu message from the remote.
|
||||||
|
func (q *QuiescerLive) recvStfu(msg lnwire.Stfu) error {
|
||||||
|
// At the time of this writing, this check that we have already received
|
||||||
|
// an Stfu is not strictly necessary, according to the specification.
|
||||||
|
// However, it is fishy if we do and it is unclear how we should handle
|
||||||
|
// such a case so we will err on the side of caution.
|
||||||
|
if q.received {
|
||||||
|
return fmt.Errorf("%w for channel %v", ErrStfuAlreadyRcvd,
|
||||||
|
q.cfg.chanID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to check that the Stfu we are receiving is valid.
|
||||||
|
if !q.sent && !msg.Initiator {
|
||||||
|
return fmt.Errorf("%w for channel %v", ErrInvalidStfu,
|
||||||
|
q.cfg.chanID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !q.canRecvStfu() {
|
||||||
|
return fmt.Errorf("%w for channel %v", ErrPendingRemoteUpdates,
|
||||||
|
q.cfg.chanID)
|
||||||
|
}
|
||||||
|
|
||||||
|
q.received = true
|
||||||
|
|
||||||
|
// If the remote party sets the initiator bit to true then we will
|
||||||
|
// remember that they are making a claim to the initiator role. This
|
||||||
|
// does not necessarily mean they will get it, though.
|
||||||
|
q.remoteInit = msg.Initiator
|
||||||
|
|
||||||
|
// Since we just received an Stfu, we may have a newly quiesced state.
|
||||||
|
// If so, we will try to resolve any outstanding StfuReqs.
|
||||||
|
q.tryResolveStfuReq()
|
||||||
|
|
||||||
|
if q.isQuiescent() {
|
||||||
|
q.startTimeout()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeStfu is called when we are ready to send an Stfu message. It returns the
|
||||||
|
// Stfu message to be sent.
|
||||||
|
func (q *QuiescerLive) MakeStfu() fn.Result[lnwire.Stfu] {
|
||||||
|
q.RLock()
|
||||||
|
defer q.RUnlock()
|
||||||
|
|
||||||
|
return q.makeStfu()
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeStfu is called when we are ready to send an Stfu message. It returns the
|
||||||
|
// Stfu message to be sent.
|
||||||
|
func (q *QuiescerLive) makeStfu() fn.Result[lnwire.Stfu] {
|
||||||
|
if q.sent {
|
||||||
|
return fn.Errf[lnwire.Stfu]("%w for channel %v",
|
||||||
|
ErrStfuAlreadySent, q.cfg.chanID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !q.canSendStfu() {
|
||||||
|
return fn.Errf[lnwire.Stfu]("%w for channel %v",
|
||||||
|
ErrPendingLocalUpdates, q.cfg.chanID)
|
||||||
|
}
|
||||||
|
|
||||||
|
stfu := lnwire.Stfu{
|
||||||
|
ChanID: q.cfg.chanID,
|
||||||
|
Initiator: q.localInit,
|
||||||
|
}
|
||||||
|
|
||||||
|
return fn.Ok(stfu)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OweStfu returns true if we owe the other party an Stfu. We owe the remote an
|
||||||
|
// Stfu when we have received but not yet sent an Stfu, or we are the initiator
|
||||||
|
// but have not yet sent an Stfu.
|
||||||
|
func (q *QuiescerLive) OweStfu() bool {
|
||||||
|
q.RLock()
|
||||||
|
defer q.RUnlock()
|
||||||
|
|
||||||
|
return q.oweStfu()
|
||||||
|
}
|
||||||
|
|
||||||
|
// oweStfu returns true if we owe the other party an Stfu. We owe the remote an
|
||||||
|
// Stfu when we have received but not yet sent an Stfu, or we are the initiator
|
||||||
|
// but have not yet sent an Stfu.
|
||||||
|
func (q *QuiescerLive) oweStfu() bool {
|
||||||
|
return (q.received || q.localInit) && !q.sent
|
||||||
|
}
|
||||||
|
|
||||||
|
// NeedStfu returns true if the remote owes us an Stfu. They owe us an Stfu when
|
||||||
|
// we have sent but not yet received an Stfu.
|
||||||
|
func (q *QuiescerLive) NeedStfu() bool {
|
||||||
|
q.RLock()
|
||||||
|
defer q.RUnlock()
|
||||||
|
|
||||||
|
return q.needStfu()
|
||||||
|
}
|
||||||
|
|
||||||
|
// needStfu returns true if the remote owes us an Stfu. They owe us an Stfu when
|
||||||
|
// we have sent but not yet received an Stfu.
|
||||||
|
func (q *QuiescerLive) needStfu() bool {
|
||||||
|
q.RLock()
|
||||||
|
defer q.RUnlock()
|
||||||
|
|
||||||
|
return q.sent && !q.received
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsQuiescent returns true if the state machine has been driven all the way to
|
||||||
|
// completion. If this returns true, processes that depend on channel quiescence
|
||||||
|
// may proceed.
|
||||||
|
func (q *QuiescerLive) IsQuiescent() bool {
|
||||||
|
q.RLock()
|
||||||
|
defer q.RUnlock()
|
||||||
|
|
||||||
|
return q.isQuiescent()
|
||||||
|
}
|
||||||
|
|
||||||
|
// isQuiescent returns true if the state machine has been driven all the way to
|
||||||
|
// completion. If this returns true, processes that depend on channel quiescence
|
||||||
|
// may proceed.
|
||||||
|
func (q *QuiescerLive) isQuiescent() bool {
|
||||||
|
return q.sent && q.received
|
||||||
|
}
|
||||||
|
|
||||||
|
// QuiescenceInitiator determines which ChannelParty is the initiator of
|
||||||
|
// quiescence for the purposes of downstream protocols. If the channel is not
|
||||||
|
// currently quiescent, this method will return ErrNoQuiescenceInitiator.
|
||||||
|
func (q *QuiescerLive) QuiescenceInitiator() fn.Result[lntypes.ChannelParty] {
|
||||||
|
q.RLock()
|
||||||
|
defer q.RUnlock()
|
||||||
|
|
||||||
|
return q.quiescenceInitiator()
|
||||||
|
}
|
||||||
|
|
||||||
|
// quiescenceInitiator determines which ChannelParty is the initiator of
|
||||||
|
// quiescence for the purposes of downstream protocols. If the channel is not
|
||||||
|
// currently quiescent, this method will return ErrNoQuiescenceInitiator.
|
||||||
|
func (q *QuiescerLive) quiescenceInitiator() fn.Result[lntypes.ChannelParty] {
|
||||||
|
switch {
|
||||||
|
case !q.isQuiescent():
|
||||||
|
return fn.Err[lntypes.ChannelParty](ErrNoQuiescenceInitiator)
|
||||||
|
|
||||||
|
case q.localInit && q.remoteInit:
|
||||||
|
// In the case of a tie, the channel initiator wins.
|
||||||
|
return fn.Ok(q.cfg.channelInitiator)
|
||||||
|
|
||||||
|
case q.localInit:
|
||||||
|
return fn.Ok(lntypes.Local)
|
||||||
|
|
||||||
|
case q.remoteInit:
|
||||||
|
return fn.Ok(lntypes.Remote)
|
||||||
|
}
|
||||||
|
|
||||||
|
// unreachable
|
||||||
|
return fn.Err[lntypes.ChannelParty](ErrNoQuiescenceInitiator)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanSendUpdates returns true if we haven't yet sent an Stfu which would mark
|
||||||
|
// the end of our ability to send updates.
|
||||||
|
func (q *QuiescerLive) CanSendUpdates() bool {
|
||||||
|
q.RLock()
|
||||||
|
defer q.RUnlock()
|
||||||
|
|
||||||
|
return q.canSendUpdates()
|
||||||
|
}
|
||||||
|
|
||||||
|
// canSendUpdates returns true if we haven't yet sent an Stfu which would mark
|
||||||
|
// the end of our ability to send updates.
|
||||||
|
func (q *QuiescerLive) canSendUpdates() bool {
|
||||||
|
return !q.sent && !q.localInit
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanRecvUpdates returns true if we haven't yet received an Stfu which would
|
||||||
|
// mark the end of the remote's ability to send updates.
|
||||||
|
func (q *QuiescerLive) CanRecvUpdates() bool {
|
||||||
|
q.RLock()
|
||||||
|
defer q.RUnlock()
|
||||||
|
|
||||||
|
return q.canRecvUpdates()
|
||||||
|
}
|
||||||
|
|
||||||
|
// canRecvUpdates returns true if we haven't yet received an Stfu which would
|
||||||
|
// mark the end of the remote's ability to send updates.
|
||||||
|
func (q *QuiescerLive) canRecvUpdates() bool {
|
||||||
|
return !q.received
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanSendStfu returns true if we can send an Stfu.
|
||||||
|
func (q *QuiescerLive) CanSendStfu(numPendingLocalUpdates uint64) bool {
|
||||||
|
q.RLock()
|
||||||
|
defer q.RUnlock()
|
||||||
|
|
||||||
|
return q.canSendStfu()
|
||||||
|
}
|
||||||
|
|
||||||
|
// canSendStfu returns true if we can send an Stfu.
|
||||||
|
func (q *QuiescerLive) canSendStfu() bool {
|
||||||
|
return !q.sent
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanRecvStfu returns true if we can receive an Stfu.
|
||||||
|
func (q *QuiescerLive) CanRecvStfu() bool {
|
||||||
|
q.RLock()
|
||||||
|
defer q.RUnlock()
|
||||||
|
|
||||||
|
return q.canRecvStfu()
|
||||||
|
}
|
||||||
|
|
||||||
|
// canRecvStfu returns true if we can receive an Stfu.
|
||||||
|
func (q *QuiescerLive) canRecvStfu() bool {
|
||||||
|
return !q.received
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendOwedStfu sends Stfu if it owes one. It returns an error if the state
|
||||||
|
// machine is in an invalid state.
|
||||||
|
func (q *QuiescerLive) SendOwedStfu() error {
|
||||||
|
q.Lock()
|
||||||
|
defer q.Unlock()
|
||||||
|
|
||||||
|
return q.sendOwedStfu()
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendOwedStfu sends Stfu if it owes one. It returns an error if the state
|
||||||
|
// machine is in an invalid state.
|
||||||
|
func (q *QuiescerLive) sendOwedStfu() error {
|
||||||
|
if !q.oweStfu() || !q.canSendStfu() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := q.makeStfu().Sink(q.cfg.sendMsg)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
q.sent = true
|
||||||
|
|
||||||
|
// Since we just sent an Stfu, we may have a newly quiesced
|
||||||
|
// state. If so, we will try to resolve any outstanding
|
||||||
|
// StfuReqs.
|
||||||
|
q.tryResolveStfuReq()
|
||||||
|
|
||||||
|
if q.isQuiescent() {
|
||||||
|
q.startTimeout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryResolveStfuReq attempts to resolve the active quiescence request if the
|
||||||
|
// state machine has reached a quiescent state.
|
||||||
|
func (q *QuiescerLive) TryResolveStfuReq() {
|
||||||
|
q.Lock()
|
||||||
|
defer q.Unlock()
|
||||||
|
|
||||||
|
q.tryResolveStfuReq()
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryResolveStfuReq attempts to resolve the active quiescence request if the
|
||||||
|
// state machine has reached a quiescent state.
|
||||||
|
func (q *QuiescerLive) tryResolveStfuReq() {
|
||||||
|
q.activeQuiescenceReq.WhenSome(
|
||||||
|
func(req StfuReq) {
|
||||||
|
if q.isQuiescent() {
|
||||||
|
req.Resolve(q.quiescenceInitiator())
|
||||||
|
q.activeQuiescenceReq = fn.None[StfuReq]()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitStfu instructs the quiescer that we intend to begin a quiescence
|
||||||
|
// negotiation where we are the initiator. We don't yet send stfu yet because
|
||||||
|
// we need to wait for the link to give us a valid opportunity to do so.
|
||||||
|
func (q *QuiescerLive) InitStfu(req StfuReq) {
|
||||||
|
q.Lock()
|
||||||
|
defer q.Unlock()
|
||||||
|
|
||||||
|
q.initStfu(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// initStfu instructs the quiescer that we intend to begin a quiescence
|
||||||
|
// negotiation where we are the initiator. We don't yet send stfu yet because
|
||||||
|
// we need to wait for the link to give us a valid opportunity to do so.
|
||||||
|
func (q *QuiescerLive) initStfu(req StfuReq) {
|
||||||
|
if q.localInit {
|
||||||
|
req.Resolve(fn.Errf[lntypes.ChannelParty](
|
||||||
|
"quiescence already requested",
|
||||||
|
))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
q.localInit = true
|
||||||
|
q.activeQuiescenceReq = fn.Some(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnResume accepts a no return closure that will run when the quiescer is
|
||||||
|
// resumed.
|
||||||
|
func (q *QuiescerLive) OnResume(hook func()) {
|
||||||
|
q.Lock()
|
||||||
|
defer q.Unlock()
|
||||||
|
|
||||||
|
q.onResume(hook)
|
||||||
|
}
|
||||||
|
|
||||||
|
// onResume accepts a no return closure that will run when the quiescer is
|
||||||
|
// resumed.
|
||||||
|
func (q *QuiescerLive) onResume(hook func()) {
|
||||||
|
q.resumeQueue = append(q.resumeQueue, hook)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resume runs all of the deferred actions that have accumulated while the
|
||||||
|
// channel has been quiescent and then resets the quiescer state to its initial
|
||||||
|
// state.
|
||||||
|
func (q *QuiescerLive) Resume() {
|
||||||
|
q.Lock()
|
||||||
|
defer q.Unlock()
|
||||||
|
|
||||||
|
q.resume()
|
||||||
|
}
|
||||||
|
|
||||||
|
// resume runs all of the deferred actions that have accumulated while the
|
||||||
|
// channel has been quiescent and then resets the quiescer state to its initial
|
||||||
|
// state.
|
||||||
|
func (q *QuiescerLive) resume() {
|
||||||
|
q.log.Debug("quiescence terminated, resuming htlc traffic")
|
||||||
|
|
||||||
|
// since we are resuming we want to cancel the quiescence timeout
|
||||||
|
// action.
|
||||||
|
q.cancelTimeout()
|
||||||
|
|
||||||
|
for _, hook := range q.resumeQueue {
|
||||||
|
hook()
|
||||||
|
}
|
||||||
|
q.localInit = false
|
||||||
|
q.remoteInit = false
|
||||||
|
q.sent = false
|
||||||
|
q.received = false
|
||||||
|
q.resumeQueue = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// startTimeout starts the timeout function that fires if the quiescer remains
|
||||||
|
// in a quiesced state for too long. If this function is called multiple times
|
||||||
|
// only the last one will have an effect.
|
||||||
|
func (q *QuiescerLive) startTimeout() {
|
||||||
|
if q.cfg.onTimeout == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
old := q.timeoutTimer
|
||||||
|
|
||||||
|
q.timeoutTimer = time.AfterFunc(q.cfg.timeoutDuration, q.cfg.onTimeout)
|
||||||
|
|
||||||
|
if old != nil {
|
||||||
|
old.Stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cancelTimeout cancels the timeout function that would otherwise fire if the
|
||||||
|
// quiescer remains in a quiesced state too long. If this function is called
|
||||||
|
// before startTimeout or after another call to cancelTimeout, the effect will
|
||||||
|
// be a noop.
|
||||||
|
func (q *QuiescerLive) cancelTimeout() {
|
||||||
|
if q.timeoutTimer != nil {
|
||||||
|
q.timeoutTimer.Stop()
|
||||||
|
q.timeoutTimer = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type quiescerNoop struct{}
|
||||||
|
|
||||||
|
var _ Quiescer = (*quiescerNoop)(nil)
|
||||||
|
|
||||||
|
func (q *quiescerNoop) InitStfu(req StfuReq) {
|
||||||
|
req.Resolve(fn.Errf[lntypes.ChannelParty]("quiescence not supported"))
|
||||||
|
}
|
||||||
|
func (q *quiescerNoop) RecvStfu(_ lnwire.Stfu) error { return nil }
|
||||||
|
func (q *quiescerNoop) CanRecvUpdates() bool { return true }
|
||||||
|
func (q *quiescerNoop) CanSendUpdates() bool { return true }
|
||||||
|
func (q *quiescerNoop) SendOwedStfu() error { return nil }
|
||||||
|
func (q *quiescerNoop) IsQuiescent() bool { return false }
|
||||||
|
func (q *quiescerNoop) OnResume(hook func()) { hook() }
|
||||||
|
func (q *quiescerNoop) Resume() {}
|
||||||
|
func (q *quiescerNoop) QuiescenceInitiator() fn.Result[lntypes.ChannelParty] {
|
||||||
|
return fn.Err[lntypes.ChannelParty](ErrNoQuiescenceInitiator)
|
||||||
|
}
|
||||||
@@ -0,0 +1,374 @@
|
|||||||
|
package htlcswitch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cid = lnwire.ChannelID(bytes.Repeat([]byte{0x00}, 32))
|
||||||
|
|
||||||
|
type quiescerTestHarness struct {
|
||||||
|
quiescer *QuiescerLive
|
||||||
|
conn <-chan lnwire.Stfu
|
||||||
|
}
|
||||||
|
|
||||||
|
func initQuiescerTestHarness(
|
||||||
|
channelInitiator lntypes.ChannelParty) *quiescerTestHarness {
|
||||||
|
|
||||||
|
conn := make(chan lnwire.Stfu, 1)
|
||||||
|
harness := &quiescerTestHarness{
|
||||||
|
conn: conn,
|
||||||
|
}
|
||||||
|
|
||||||
|
quiescer, _ := NewQuiescer(QuiescerCfg{
|
||||||
|
chanID: cid,
|
||||||
|
channelInitiator: channelInitiator,
|
||||||
|
sendMsg: func(msg lnwire.Stfu) error {
|
||||||
|
conn <- msg
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}).(*QuiescerLive)
|
||||||
|
|
||||||
|
harness.quiescer = quiescer
|
||||||
|
|
||||||
|
return harness
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestQuiescerDoubleRecvInvalid ensures that we get an error response when we
|
||||||
|
// receive the Stfu message twice during the lifecycle of the quiescer.
|
||||||
|
func TestQuiescerDoubleRecvInvalid(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
harness := initQuiescerTestHarness(lntypes.Local)
|
||||||
|
|
||||||
|
msg := lnwire.Stfu{
|
||||||
|
ChanID: cid,
|
||||||
|
Initiator: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := harness.quiescer.RecvStfu(msg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = harness.quiescer.RecvStfu(msg)
|
||||||
|
require.Error(t, err, ErrStfuAlreadyRcvd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestQuiescenceRemoteInit ensures that we can successfully traverse the state
|
||||||
|
// graph of quiescence beginning with the Remote party initiating quiescence.
|
||||||
|
func TestQuiescenceRemoteInit(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
harness := initQuiescerTestHarness(lntypes.Local)
|
||||||
|
|
||||||
|
msg := lnwire.Stfu{
|
||||||
|
ChanID: cid,
|
||||||
|
Initiator: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := harness.quiescer.RecvStfu(msg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = harness.quiescer.SendOwedStfu()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case msg := <-harness.conn:
|
||||||
|
require.False(t, msg.Initiator)
|
||||||
|
default:
|
||||||
|
t.Fatalf("stfu not sent when expected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuiescenceLocalInit(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
harness := initQuiescerTestHarness(lntypes.Local)
|
||||||
|
|
||||||
|
msg := lnwire.Stfu{
|
||||||
|
ChanID: cid,
|
||||||
|
Initiator: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
stfuReq, stfuRes := fn.NewReq[fn.Unit, fn.Result[lntypes.ChannelParty]](
|
||||||
|
fn.Unit{},
|
||||||
|
)
|
||||||
|
harness.quiescer.InitStfu(stfuReq)
|
||||||
|
|
||||||
|
err := harness.quiescer.SendOwedStfu()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case msg := <-harness.conn:
|
||||||
|
require.True(t, msg.Initiator)
|
||||||
|
default:
|
||||||
|
t.Fatalf("stfu not sent when expected")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = harness.quiescer.RecvStfu(msg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case party := <-stfuRes:
|
||||||
|
require.Equal(t, fn.Ok(lntypes.Local), party)
|
||||||
|
default:
|
||||||
|
t.Fatalf("quiescence request not resolved")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestQuiescenceInitiator ensures that the quiescenceInitiator is the Remote
|
||||||
|
// party when we have a receive first traversal of the quiescer's state graph.
|
||||||
|
func TestQuiescenceInitiator(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Remote Initiated
|
||||||
|
harness := initQuiescerTestHarness(lntypes.Local)
|
||||||
|
require.True(t, harness.quiescer.QuiescenceInitiator().IsErr())
|
||||||
|
|
||||||
|
// Receive
|
||||||
|
msg := lnwire.Stfu{
|
||||||
|
ChanID: cid,
|
||||||
|
Initiator: true,
|
||||||
|
}
|
||||||
|
require.NoError(t, harness.quiescer.RecvStfu(msg))
|
||||||
|
require.True(t, harness.quiescer.QuiescenceInitiator().IsErr())
|
||||||
|
|
||||||
|
// Send
|
||||||
|
require.NoError(t, harness.quiescer.SendOwedStfu())
|
||||||
|
require.Equal(
|
||||||
|
t, harness.quiescer.QuiescenceInitiator(),
|
||||||
|
fn.Ok(lntypes.Remote),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Local Initiated
|
||||||
|
harness = initQuiescerTestHarness(lntypes.Local)
|
||||||
|
require.True(t, harness.quiescer.quiescenceInitiator().IsErr())
|
||||||
|
|
||||||
|
req, res := fn.NewReq[fn.Unit, fn.Result[lntypes.ChannelParty]](
|
||||||
|
fn.Unit{},
|
||||||
|
)
|
||||||
|
harness.quiescer.initStfu(req)
|
||||||
|
req2, res2 := fn.NewReq[fn.Unit, fn.Result[lntypes.ChannelParty]](
|
||||||
|
fn.Unit{},
|
||||||
|
)
|
||||||
|
harness.quiescer.initStfu(req2)
|
||||||
|
select {
|
||||||
|
case initiator := <-res2:
|
||||||
|
require.True(t, initiator.IsErr())
|
||||||
|
default:
|
||||||
|
t.Fatal("quiescence request not resolved")
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(
|
||||||
|
t, harness.quiescer.sendOwedStfu(),
|
||||||
|
)
|
||||||
|
require.True(t, harness.quiescer.quiescenceInitiator().IsErr())
|
||||||
|
|
||||||
|
msg = lnwire.Stfu{
|
||||||
|
ChanID: cid,
|
||||||
|
Initiator: false,
|
||||||
|
}
|
||||||
|
require.NoError(t, harness.quiescer.recvStfu(msg))
|
||||||
|
require.True(t, harness.quiescer.quiescenceInitiator().IsOk())
|
||||||
|
|
||||||
|
select {
|
||||||
|
case initiator := <-res:
|
||||||
|
require.Equal(t, fn.Ok(lntypes.Local), initiator)
|
||||||
|
default:
|
||||||
|
t.Fatal("quiescence request not resolved")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestQuiescenceCantReceiveUpdatesAfterStfu tests that we can receive channel
|
||||||
|
// updates prior to but not after we receive Stfu.
|
||||||
|
func TestQuiescenceCantReceiveUpdatesAfterStfu(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
harness := initQuiescerTestHarness(lntypes.Local)
|
||||||
|
require.True(t, harness.quiescer.CanRecvUpdates())
|
||||||
|
|
||||||
|
msg := lnwire.Stfu{
|
||||||
|
ChanID: cid,
|
||||||
|
Initiator: true,
|
||||||
|
}
|
||||||
|
require.NoError(t, harness.quiescer.RecvStfu(msg))
|
||||||
|
require.False(t, harness.quiescer.CanRecvUpdates())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestQuiescenceCantSendUpdatesAfterStfu tests that we can send channel updates
|
||||||
|
// prior to but not after we send Stfu.
|
||||||
|
func TestQuiescenceCantSendUpdatesAfterStfu(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
harness := initQuiescerTestHarness(lntypes.Local)
|
||||||
|
require.True(t, harness.quiescer.CanSendUpdates())
|
||||||
|
|
||||||
|
msg := lnwire.Stfu{
|
||||||
|
ChanID: cid,
|
||||||
|
Initiator: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := harness.quiescer.RecvStfu(msg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = harness.quiescer.SendOwedStfu()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.False(t, harness.quiescer.CanSendUpdates())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestQuiescenceStfuNotNeededAfterRecv tests that after we receive an Stfu we
|
||||||
|
// do not needStfu either before or after receiving it if we do not initiate
|
||||||
|
// quiescence.
|
||||||
|
func TestQuiescenceStfuNotNeededAfterRecv(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
harness := initQuiescerTestHarness(lntypes.Local)
|
||||||
|
|
||||||
|
msg := lnwire.Stfu{
|
||||||
|
ChanID: cid,
|
||||||
|
Initiator: true,
|
||||||
|
}
|
||||||
|
require.False(t, harness.quiescer.NeedStfu())
|
||||||
|
|
||||||
|
require.NoError(t, harness.quiescer.RecvStfu(msg))
|
||||||
|
|
||||||
|
require.False(t, harness.quiescer.NeedStfu())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestQuiescenceInappropriateMakeStfuReturnsErr ensures that we cannot call
|
||||||
|
// makeStfu at times when it would be a protocol violation to send it.
|
||||||
|
func TestQuiescenceInappropriateMakeStfuReturnsErr(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
harness := initQuiescerTestHarness(lntypes.Local)
|
||||||
|
|
||||||
|
msg := lnwire.Stfu{
|
||||||
|
ChanID: cid,
|
||||||
|
Initiator: true,
|
||||||
|
}
|
||||||
|
require.NoError(t, harness.quiescer.RecvStfu(msg))
|
||||||
|
require.True(t, harness.quiescer.MakeStfu().IsOk())
|
||||||
|
|
||||||
|
require.NoError(t, harness.quiescer.SendOwedStfu())
|
||||||
|
require.True(t, harness.quiescer.MakeStfu().IsErr())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestQuiescerTieBreaker ensures that if both parties attempt to claim the
|
||||||
|
// initiator role that the result of the negotiation breaks the tie using the
|
||||||
|
// channel initiator.
|
||||||
|
func TestQuiescerTieBreaker(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
for _, initiator := range []lntypes.ChannelParty{
|
||||||
|
lntypes.Local, lntypes.Remote,
|
||||||
|
} {
|
||||||
|
harness := initQuiescerTestHarness(initiator)
|
||||||
|
|
||||||
|
msg := lnwire.Stfu{
|
||||||
|
ChanID: cid,
|
||||||
|
Initiator: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
req, res := fn.NewReq[fn.Unit, fn.Result[lntypes.ChannelParty]](
|
||||||
|
fn.Unit{},
|
||||||
|
)
|
||||||
|
|
||||||
|
harness.quiescer.InitStfu(req)
|
||||||
|
require.NoError(t, harness.quiescer.RecvStfu(msg))
|
||||||
|
require.NoError(t, harness.quiescer.SendOwedStfu())
|
||||||
|
|
||||||
|
select {
|
||||||
|
case party := <-res:
|
||||||
|
require.Equal(t, fn.Ok(initiator), party)
|
||||||
|
default:
|
||||||
|
t.Fatal("quiescence party unavailable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestQuiescerResume ensures that the hooks that are attached to the quiescer
|
||||||
|
// are called when we call the resume method and no earlier.
|
||||||
|
func TestQuiescerResume(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
harness := initQuiescerTestHarness(lntypes.Local)
|
||||||
|
|
||||||
|
msg := lnwire.Stfu{
|
||||||
|
ChanID: cid,
|
||||||
|
Initiator: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, harness.quiescer.RecvStfu(msg))
|
||||||
|
require.NoError(t, harness.quiescer.SendOwedStfu())
|
||||||
|
|
||||||
|
require.True(t, harness.quiescer.IsQuiescent())
|
||||||
|
var resumeHooksCalled = false
|
||||||
|
harness.quiescer.OnResume(func() {
|
||||||
|
resumeHooksCalled = true
|
||||||
|
})
|
||||||
|
require.False(t, resumeHooksCalled)
|
||||||
|
|
||||||
|
harness.quiescer.Resume()
|
||||||
|
require.True(t, resumeHooksCalled)
|
||||||
|
require.False(t, harness.quiescer.IsQuiescent())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuiescerTimeoutTriggers(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
harness := initQuiescerTestHarness(lntypes.Local)
|
||||||
|
|
||||||
|
msg := lnwire.Stfu{
|
||||||
|
ChanID: cid,
|
||||||
|
Initiator: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutGate := make(chan struct{})
|
||||||
|
|
||||||
|
harness.quiescer.cfg.timeoutDuration = time.Second
|
||||||
|
harness.quiescer.cfg.onTimeout = func() { close(timeoutGate) }
|
||||||
|
|
||||||
|
err := harness.quiescer.RecvStfu(msg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = harness.quiescer.SendOwedStfu()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-timeoutGate:
|
||||||
|
case <-time.After(2 * harness.quiescer.cfg.timeoutDuration):
|
||||||
|
t.Fatal("quiescence timeout did not trigger")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuiescerTimeoutAborts(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
harness := initQuiescerTestHarness(lntypes.Local)
|
||||||
|
|
||||||
|
msg := lnwire.Stfu{
|
||||||
|
ChanID: cid,
|
||||||
|
Initiator: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutGate := make(chan struct{})
|
||||||
|
|
||||||
|
harness.quiescer.cfg.timeoutDuration = time.Second
|
||||||
|
harness.quiescer.cfg.onTimeout = func() { close(timeoutGate) }
|
||||||
|
|
||||||
|
err := harness.quiescer.RecvStfu(msg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = harness.quiescer.SendOwedStfu()
|
||||||
|
require.NoError(t, err)
|
||||||
|
harness.quiescer.Resume()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-timeoutGate:
|
||||||
|
t.Fatal("quiescence timeout triggered despite being resumed")
|
||||||
|
case <-time.After(2 * harness.quiescer.cfg.timeoutDuration):
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -710,4 +710,8 @@ var allTestCases = []*lntest.TestCase{
|
|||||||
Name: "experimental endorsement",
|
Name: "experimental endorsement",
|
||||||
TestFunc: testExperimentalEndorsement,
|
TestFunc: testExperimentalEndorsement,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "quiescence",
|
||||||
|
TestFunc: testQuiescence,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package itest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
|
"github.com/lightningnetwork/lnd/lnrpc/devrpc"
|
||||||
|
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||||
|
"github.com/lightningnetwork/lnd/lntest"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// testQuiescence tests whether we can come to agreement on quiescence of a
|
||||||
|
// channel. We initiate quiescence via RPC and if it succeeds we verify that
|
||||||
|
// the expected initiator is the resulting initiator.
|
||||||
|
//
|
||||||
|
// NOTE FOR REVIEW: this could be improved by blasting the channel with HTLC
|
||||||
|
// traffic on both sides to increase the surface area of the change under test.
|
||||||
|
func testQuiescence(ht *lntest.HarnessTest) {
|
||||||
|
alice, bob := ht.Alice, ht.Bob
|
||||||
|
|
||||||
|
chanPoint := ht.OpenChannel(bob, alice, lntest.OpenChannelParams{
|
||||||
|
Amt: btcutil.Amount(1000000),
|
||||||
|
})
|
||||||
|
defer ht.CloseChannel(bob, chanPoint)
|
||||||
|
|
||||||
|
res := alice.RPC.Quiesce(&devrpc.QuiescenceRequest{
|
||||||
|
ChanId: chanPoint,
|
||||||
|
})
|
||||||
|
|
||||||
|
require.True(ht, res.Initiator)
|
||||||
|
|
||||||
|
req := &routerrpc.SendPaymentRequest{
|
||||||
|
Dest: ht.Alice.PubKey[:],
|
||||||
|
Amt: 100,
|
||||||
|
PaymentHash: ht.Random32Bytes(),
|
||||||
|
FinalCltvDelta: finalCltvDelta,
|
||||||
|
TimeoutSeconds: 60,
|
||||||
|
FeeLimitMsat: noFeeLimitMsat,
|
||||||
|
}
|
||||||
|
|
||||||
|
ht.SendPaymentAssertFail(
|
||||||
|
ht.Bob, req,
|
||||||
|
// This fails with insufficient balance because the bandwidth
|
||||||
|
// manager reports 0 bandwidth if a link is not eligible for
|
||||||
|
// forwarding, which is the case during quiescence.
|
||||||
|
lnrpc.PaymentFailureReason_FAILURE_REASON_INSUFFICIENT_BALANCE,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -141,6 +141,11 @@ func (l *ProtocolOptions) NoExperimentalEndorsement() bool {
|
|||||||
return l.NoExperimentalEndorsementOption
|
return l.NoExperimentalEndorsementOption
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NoQuiescence returns true if quiescence is disabled.
|
||||||
|
func (l *ProtocolOptions) NoQuiescence() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// CustomMessageOverrides returns the set of protocol messages that we override
|
// CustomMessageOverrides returns the set of protocol messages that we override
|
||||||
// to allow custom handling.
|
// to allow custom handling.
|
||||||
func (p ProtocolOptions) CustomMessageOverrides() []uint16 {
|
func (p ProtocolOptions) CustomMessageOverrides() []uint16 {
|
||||||
|
|||||||
@@ -73,6 +73,9 @@ type ProtocolOptions struct {
|
|||||||
// NoExperimentalEndorsementOption disables experimental endorsement.
|
// NoExperimentalEndorsementOption disables experimental endorsement.
|
||||||
NoExperimentalEndorsementOption bool `long:"no-experimental-endorsement" description:"do not forward experimental endorsement signals"`
|
NoExperimentalEndorsementOption bool `long:"no-experimental-endorsement" description:"do not forward experimental endorsement signals"`
|
||||||
|
|
||||||
|
// NoQuiescenceOption disables quiescence for all channels.
|
||||||
|
NoQuiescenceOption bool `long:"no-quiescence" description:"do not allow or advertise quiescence for any channel"`
|
||||||
|
|
||||||
// CustomMessage allows the custom message APIs to handle messages with
|
// CustomMessage allows the custom message APIs to handle messages with
|
||||||
// the provided protocol numbers, which fall outside the custom message
|
// the provided protocol numbers, which fall outside the custom message
|
||||||
// number range.
|
// number range.
|
||||||
@@ -136,6 +139,11 @@ func (l *ProtocolOptions) NoExperimentalEndorsement() bool {
|
|||||||
return l.NoExperimentalEndorsementOption
|
return l.NoExperimentalEndorsementOption
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NoQuiescence returns true if quiescence is disabled.
|
||||||
|
func (l *ProtocolOptions) NoQuiescence() bool {
|
||||||
|
return l.NoQuiescenceOption
|
||||||
|
}
|
||||||
|
|
||||||
// CustomMessageOverrides returns the set of protocol messages that we override
|
// CustomMessageOverrides returns the set of protocol messages that we override
|
||||||
// to allow custom handling.
|
// to allow custom handling.
|
||||||
func (l ProtocolOptions) CustomMessageOverrides() []uint16 {
|
func (l ProtocolOptions) CustomMessageOverrides() []uint16 {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ package devrpc
|
|||||||
import (
|
import (
|
||||||
"github.com/btcsuite/btcd/chaincfg"
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
|
"github.com/lightningnetwork/lnd/htlcswitch"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config is the primary configuration struct for the DEV RPC server. It
|
// Config is the primary configuration struct for the DEV RPC server. It
|
||||||
@@ -16,4 +17,5 @@ import (
|
|||||||
type Config struct {
|
type Config struct {
|
||||||
ActiveNetParams *chaincfg.Params
|
ActiveNetParams *chaincfg.Params
|
||||||
GraphDB *channeldb.ChannelGraph
|
GraphDB *channeldb.ChannelGraph
|
||||||
|
Switch *htlcswitch.Switch
|
||||||
}
|
}
|
||||||
+157
-19
@@ -59,6 +59,103 @@ func (*ImportGraphResponse) Descriptor() ([]byte, []int) {
|
|||||||
return file_devrpc_dev_proto_rawDescGZIP(), []int{0}
|
return file_devrpc_dev_proto_rawDescGZIP(), []int{0}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type QuiescenceRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
// The channel point of the channel we wish to quiesce
|
||||||
|
ChanId *lnrpc.ChannelPoint `protobuf:"bytes,1,opt,name=chan_id,json=chanId,proto3" json:"chan_id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *QuiescenceRequest) Reset() {
|
||||||
|
*x = QuiescenceRequest{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_devrpc_dev_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *QuiescenceRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*QuiescenceRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *QuiescenceRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_devrpc_dev_proto_msgTypes[1]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use QuiescenceRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*QuiescenceRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_devrpc_dev_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *QuiescenceRequest) GetChanId() *lnrpc.ChannelPoint {
|
||||||
|
if x != nil {
|
||||||
|
return x.ChanId
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type QuiescenceResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
// Indicates whether or not we hold the initiator role or not once the
|
||||||
|
// negotiation completes
|
||||||
|
Initiator bool `protobuf:"varint,1,opt,name=initiator,proto3" json:"initiator,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *QuiescenceResponse) Reset() {
|
||||||
|
*x = QuiescenceResponse{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_devrpc_dev_proto_msgTypes[2]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *QuiescenceResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*QuiescenceResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *QuiescenceResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_devrpc_dev_proto_msgTypes[2]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use QuiescenceResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*QuiescenceResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_devrpc_dev_proto_rawDescGZIP(), []int{2}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *QuiescenceResponse) GetInitiator() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.Initiator
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
var File_devrpc_dev_proto protoreflect.FileDescriptor
|
var File_devrpc_dev_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
var file_devrpc_dev_proto_rawDesc = []byte{
|
var file_devrpc_dev_proto_rawDesc = []byte{
|
||||||
@@ -66,15 +163,26 @@ var file_devrpc_dev_proto_rawDesc = []byte{
|
|||||||
0x74, 0x6f, 0x12, 0x06, 0x64, 0x65, 0x76, 0x72, 0x70, 0x63, 0x1a, 0x0f, 0x6c, 0x69, 0x67, 0x68,
|
0x74, 0x6f, 0x12, 0x06, 0x64, 0x65, 0x76, 0x72, 0x70, 0x63, 0x1a, 0x0f, 0x6c, 0x69, 0x67, 0x68,
|
||||||
0x74, 0x6e, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x15, 0x0a, 0x13, 0x49,
|
0x74, 0x6e, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x15, 0x0a, 0x13, 0x49,
|
||||||
0x6d, 0x70, 0x6f, 0x72, 0x74, 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
0x6d, 0x70, 0x6f, 0x72, 0x74, 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||||
0x73, 0x65, 0x32, 0x46, 0x0a, 0x03, 0x44, 0x65, 0x76, 0x12, 0x3f, 0x0a, 0x0b, 0x49, 0x6d, 0x70,
|
0x73, 0x65, 0x22, 0x41, 0x0a, 0x11, 0x51, 0x75, 0x69, 0x65, 0x73, 0x63, 0x65, 0x6e, 0x63, 0x65,
|
||||||
0x6f, 0x72, 0x74, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
|
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f,
|
||||||
0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, 0x70, 0x68, 0x1a, 0x1b, 0x2e,
|
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
|
||||||
0x64, 0x65, 0x76, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x47, 0x72, 0x61,
|
0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x06, 0x63,
|
||||||
0x70, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69,
|
0x68, 0x61, 0x6e, 0x49, 0x64, 0x22, 0x32, 0x0a, 0x12, 0x51, 0x75, 0x69, 0x65, 0x73, 0x63, 0x65,
|
||||||
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69,
|
0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x69,
|
||||||
0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e,
|
0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09,
|
||||||
0x72, 0x70, 0x63, 0x2f, 0x64, 0x65, 0x76, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
|
0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x32, 0x88, 0x01, 0x0a, 0x03, 0x44, 0x65,
|
||||||
0x6f, 0x33,
|
0x76, 0x12, 0x3f, 0x0a, 0x0b, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x47, 0x72, 0x61, 0x70, 0x68,
|
||||||
|
0x12, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c,
|
||||||
|
0x47, 0x72, 0x61, 0x70, 0x68, 0x1a, 0x1b, 0x2e, 0x64, 0x65, 0x76, 0x72, 0x70, 0x63, 0x2e, 0x49,
|
||||||
|
0x6d, 0x70, 0x6f, 0x72, 0x74, 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||||
|
0x73, 0x65, 0x12, 0x40, 0x0a, 0x07, 0x51, 0x75, 0x69, 0x65, 0x73, 0x63, 0x65, 0x12, 0x19, 0x2e,
|
||||||
|
0x64, 0x65, 0x76, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x69, 0x65, 0x73, 0x63, 0x65, 0x6e, 0x63,
|
||||||
|
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x64, 0x65, 0x76, 0x72, 0x70,
|
||||||
|
0x63, 0x2e, 0x51, 0x75, 0x69, 0x65, 0x73, 0x63, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70,
|
||||||
|
0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
|
||||||
|
0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77,
|
||||||
|
0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x64, 0x65,
|
||||||
|
0x76, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -89,19 +197,25 @@ func file_devrpc_dev_proto_rawDescGZIP() []byte {
|
|||||||
return file_devrpc_dev_proto_rawDescData
|
return file_devrpc_dev_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_devrpc_dev_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
var file_devrpc_dev_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
|
||||||
var file_devrpc_dev_proto_goTypes = []interface{}{
|
var file_devrpc_dev_proto_goTypes = []interface{}{
|
||||||
(*ImportGraphResponse)(nil), // 0: devrpc.ImportGraphResponse
|
(*ImportGraphResponse)(nil), // 0: devrpc.ImportGraphResponse
|
||||||
(*lnrpc.ChannelGraph)(nil), // 1: lnrpc.ChannelGraph
|
(*QuiescenceRequest)(nil), // 1: devrpc.QuiescenceRequest
|
||||||
|
(*QuiescenceResponse)(nil), // 2: devrpc.QuiescenceResponse
|
||||||
|
(*lnrpc.ChannelPoint)(nil), // 3: lnrpc.ChannelPoint
|
||||||
|
(*lnrpc.ChannelGraph)(nil), // 4: lnrpc.ChannelGraph
|
||||||
}
|
}
|
||||||
var file_devrpc_dev_proto_depIdxs = []int32{
|
var file_devrpc_dev_proto_depIdxs = []int32{
|
||||||
1, // 0: devrpc.Dev.ImportGraph:input_type -> lnrpc.ChannelGraph
|
3, // 0: devrpc.QuiescenceRequest.chan_id:type_name -> lnrpc.ChannelPoint
|
||||||
0, // 1: devrpc.Dev.ImportGraph:output_type -> devrpc.ImportGraphResponse
|
4, // 1: devrpc.Dev.ImportGraph:input_type -> lnrpc.ChannelGraph
|
||||||
1, // [1:2] is the sub-list for method output_type
|
1, // 2: devrpc.Dev.Quiesce:input_type -> devrpc.QuiescenceRequest
|
||||||
0, // [0:1] is the sub-list for method input_type
|
0, // 3: devrpc.Dev.ImportGraph:output_type -> devrpc.ImportGraphResponse
|
||||||
0, // [0:0] is the sub-list for extension type_name
|
2, // 4: devrpc.Dev.Quiesce:output_type -> devrpc.QuiescenceResponse
|
||||||
0, // [0:0] is the sub-list for extension extendee
|
3, // [3:5] is the sub-list for method output_type
|
||||||
0, // [0:0] is the sub-list for field type_name
|
1, // [1:3] is the sub-list for method input_type
|
||||||
|
1, // [1:1] is the sub-list for extension type_name
|
||||||
|
1, // [1:1] is the sub-list for extension extendee
|
||||||
|
0, // [0:1] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_devrpc_dev_proto_init() }
|
func init() { file_devrpc_dev_proto_init() }
|
||||||
@@ -122,6 +236,30 @@ func file_devrpc_dev_proto_init() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
file_devrpc_dev_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*QuiescenceRequest); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_devrpc_dev_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*QuiescenceResponse); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
type x struct{}
|
type x struct{}
|
||||||
out := protoimpl.TypeBuilder{
|
out := protoimpl.TypeBuilder{
|
||||||
@@ -129,7 +267,7 @@ func file_devrpc_dev_proto_init() {
|
|||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: file_devrpc_dev_proto_rawDesc,
|
RawDescriptor: file_devrpc_dev_proto_rawDesc,
|
||||||
NumEnums: 0,
|
NumEnums: 0,
|
||||||
NumMessages: 1,
|
NumMessages: 3,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 1,
|
NumServices: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -66,6 +66,40 @@ func local_request_Dev_ImportGraph_0(ctx context.Context, marshaler runtime.Mars
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func request_Dev_Quiesce_0(ctx context.Context, marshaler runtime.Marshaler, client DevClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
|
var protoReq QuiescenceRequest
|
||||||
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
|
newReader, berr := utilities.IOReaderFactory(req.Body)
|
||||||
|
if berr != nil {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
|
||||||
|
}
|
||||||
|
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := client.Quiesce(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||||
|
return msg, metadata, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func local_request_Dev_Quiesce_0(ctx context.Context, marshaler runtime.Marshaler, server DevServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
|
var protoReq QuiescenceRequest
|
||||||
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
|
newReader, berr := utilities.IOReaderFactory(req.Body)
|
||||||
|
if berr != nil {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
|
||||||
|
}
|
||||||
|
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := server.Quiesce(ctx, &protoReq)
|
||||||
|
return msg, metadata, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// RegisterDevHandlerServer registers the http handlers for service Dev to "mux".
|
// RegisterDevHandlerServer registers the http handlers for service Dev to "mux".
|
||||||
// UnaryRPC :call DevServer directly.
|
// UnaryRPC :call DevServer directly.
|
||||||
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
|
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
|
||||||
@@ -95,6 +129,29 @@ func RegisterDevHandlerServer(ctx context.Context, mux *runtime.ServeMux, server
|
|||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
mux.Handle("POST", pattern_Dev_Quiesce_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
|
defer cancel()
|
||||||
|
var stream runtime.ServerTransportStream
|
||||||
|
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||||
|
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||||
|
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/devrpc.Dev/Quiesce", runtime.WithHTTPPathPattern("/v2/dev/quiesce"))
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, md, err := local_request_Dev_Quiesce_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||||
|
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||||
|
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_Dev_Quiesce_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,13 +213,37 @@ func RegisterDevHandlerClient(ctx context.Context, mux *runtime.ServeMux, client
|
|||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
mux.Handle("POST", pattern_Dev_Quiesce_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
|
defer cancel()
|
||||||
|
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||||
|
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/devrpc.Dev/Quiesce", runtime.WithHTTPPathPattern("/v2/dev/quiesce"))
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, md, err := request_Dev_Quiesce_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||||
|
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_Dev_Quiesce_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
pattern_Dev_ImportGraph_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "dev", "importgraph"}, ""))
|
pattern_Dev_ImportGraph_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "dev", "importgraph"}, ""))
|
||||||
|
|
||||||
|
pattern_Dev_Quiesce_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "dev", "quiesce"}, ""))
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
forward_Dev_ImportGraph_0 = runtime.ForwardResponseMessage
|
forward_Dev_ImportGraph_0 = runtime.ForwardResponseMessage
|
||||||
|
|
||||||
|
forward_Dev_Quiesce_0 = runtime.ForwardResponseMessage
|
||||||
)
|
)
|
||||||
@@ -46,4 +46,29 @@ func RegisterDevJSONCallbacks(registry map[string]func(ctx context.Context,
|
|||||||
}
|
}
|
||||||
callback(string(respBytes), nil)
|
callback(string(respBytes), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registry["devrpc.Dev.Quiesce"] = func(ctx context.Context,
|
||||||
|
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
|
||||||
|
|
||||||
|
req := &QuiescenceRequest{}
|
||||||
|
err := marshaler.Unmarshal([]byte(reqJSON), req)
|
||||||
|
if err != nil {
|
||||||
|
callback("", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
client := NewDevClient(conn)
|
||||||
|
resp, err := client.Quiesce(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
callback("", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
respBytes, err := marshaler.Marshal(resp)
|
||||||
|
if err != nil {
|
||||||
|
callback("", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
callback(string(respBytes), nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -30,7 +30,25 @@ service Dev {
|
|||||||
used for development.
|
used for development.
|
||||||
*/
|
*/
|
||||||
rpc ImportGraph (lnrpc.ChannelGraph) returns (ImportGraphResponse);
|
rpc ImportGraph (lnrpc.ChannelGraph) returns (ImportGraphResponse);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Quiesce instructs a channel to initiate the quiescence (stfu) protocol. This
|
||||||
|
RPC is for testing purposes only. The commit that adds it will be removed
|
||||||
|
once interop is confirmed.
|
||||||
|
*/
|
||||||
|
rpc Quiesce (QuiescenceRequest) returns (QuiescenceResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
message ImportGraphResponse {
|
message ImportGraphResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message QuiescenceRequest {
|
||||||
|
// The channel point of the channel we wish to quiesce
|
||||||
|
lnrpc.ChannelPoint chan_id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message QuiescenceResponse {
|
||||||
|
// Indicates whether or not we hold the initiator role or not once the
|
||||||
|
// negotiation completes
|
||||||
|
bool initiator = 1;
|
||||||
|
}
|
||||||
@@ -48,12 +48,63 @@
|
|||||||
"Dev"
|
"Dev"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"/v2/dev/quiesce": {
|
||||||
|
"post": {
|
||||||
|
"summary": "Quiesce instructs a channel to initiate the quiescence (stfu) protocol. This\nRPC is for testing purposes only. The commit that adds it will be removed\nonce interop is confirmed.",
|
||||||
|
"operationId": "Dev_Quiesce",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A successful response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/devrpcQuiescenceResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "An unexpected error response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/rpcStatus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/devrpcQuiescenceRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Dev"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
"devrpcImportGraphResponse": {
|
"devrpcImportGraphResponse": {
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"devrpcQuiescenceRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"chan_id": {
|
||||||
|
"$ref": "#/definitions/lnrpcChannelPoint",
|
||||||
|
"title": "The channel point of the channel we wish to quiesce"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"devrpcQuiescenceResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"initiator": {
|
||||||
|
"type": "boolean",
|
||||||
|
"title": "Indicates whether or not we hold the initiator role or not once the\nnegotiation completes"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"lnrpcChannelEdge": {
|
"lnrpcChannelEdge": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -116,6 +167,25 @@
|
|||||||
},
|
},
|
||||||
"description": "Returns a new instance of the directed channel graph."
|
"description": "Returns a new instance of the directed channel graph."
|
||||||
},
|
},
|
||||||
|
"lnrpcChannelPoint": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"funding_txid_bytes": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "byte",
|
||||||
|
"description": "Txid of the funding transaction. When using REST, this field must be\nencoded as base64."
|
||||||
|
},
|
||||||
|
"funding_txid_str": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Hex-encoded string representing the byte-reversed hash of the funding\ntransaction."
|
||||||
|
},
|
||||||
|
"output_index": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"title": "The index of the output of the funding transaction"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"lnrpcFeature": {
|
"lnrpcFeature": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -6,3 +6,6 @@ http:
|
|||||||
- selector: devrpc.Dev.ImportGraph
|
- selector: devrpc.Dev.ImportGraph
|
||||||
post: "/v2/dev/importgraph"
|
post: "/v2/dev/importgraph"
|
||||||
body: "*"
|
body: "*"
|
||||||
|
- selector: devrpc.Dev.Quiesce
|
||||||
|
post: "/v2/dev/quiesce"
|
||||||
|
body: "*"
|
||||||
@@ -23,6 +23,10 @@ type DevClient interface {
|
|||||||
// ImportGraph imports a ChannelGraph into the graph database. Should only be
|
// ImportGraph imports a ChannelGraph into the graph database. Should only be
|
||||||
// used for development.
|
// used for development.
|
||||||
ImportGraph(ctx context.Context, in *lnrpc.ChannelGraph, opts ...grpc.CallOption) (*ImportGraphResponse, error)
|
ImportGraph(ctx context.Context, in *lnrpc.ChannelGraph, opts ...grpc.CallOption) (*ImportGraphResponse, error)
|
||||||
|
// Quiesce instructs a channel to initiate the quiescence (stfu) protocol. This
|
||||||
|
// RPC is for testing purposes only. The commit that adds it will be removed
|
||||||
|
// once interop is confirmed.
|
||||||
|
Quiesce(ctx context.Context, in *QuiescenceRequest, opts ...grpc.CallOption) (*QuiescenceResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type devClient struct {
|
type devClient struct {
|
||||||
@@ -42,6 +46,15 @@ func (c *devClient) ImportGraph(ctx context.Context, in *lnrpc.ChannelGraph, opt
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *devClient) Quiesce(ctx context.Context, in *QuiescenceRequest, opts ...grpc.CallOption) (*QuiescenceResponse, error) {
|
||||||
|
out := new(QuiescenceResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/devrpc.Dev/Quiesce", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
// DevServer is the server API for Dev service.
|
// DevServer is the server API for Dev service.
|
||||||
// All implementations must embed UnimplementedDevServer
|
// All implementations must embed UnimplementedDevServer
|
||||||
// for forward compatibility
|
// for forward compatibility
|
||||||
@@ -50,6 +63,10 @@ type DevServer interface {
|
|||||||
// ImportGraph imports a ChannelGraph into the graph database. Should only be
|
// ImportGraph imports a ChannelGraph into the graph database. Should only be
|
||||||
// used for development.
|
// used for development.
|
||||||
ImportGraph(context.Context, *lnrpc.ChannelGraph) (*ImportGraphResponse, error)
|
ImportGraph(context.Context, *lnrpc.ChannelGraph) (*ImportGraphResponse, error)
|
||||||
|
// Quiesce instructs a channel to initiate the quiescence (stfu) protocol. This
|
||||||
|
// RPC is for testing purposes only. The commit that adds it will be removed
|
||||||
|
// once interop is confirmed.
|
||||||
|
Quiesce(context.Context, *QuiescenceRequest) (*QuiescenceResponse, error)
|
||||||
mustEmbedUnimplementedDevServer()
|
mustEmbedUnimplementedDevServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,6 +77,9 @@ type UnimplementedDevServer struct {
|
|||||||
func (UnimplementedDevServer) ImportGraph(context.Context, *lnrpc.ChannelGraph) (*ImportGraphResponse, error) {
|
func (UnimplementedDevServer) ImportGraph(context.Context, *lnrpc.ChannelGraph) (*ImportGraphResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ImportGraph not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method ImportGraph not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedDevServer) Quiesce(context.Context, *QuiescenceRequest) (*QuiescenceResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method Quiesce not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedDevServer) mustEmbedUnimplementedDevServer() {}
|
func (UnimplementedDevServer) mustEmbedUnimplementedDevServer() {}
|
||||||
|
|
||||||
// UnsafeDevServer may be embedded to opt out of forward compatibility for this service.
|
// UnsafeDevServer may be embedded to opt out of forward compatibility for this service.
|
||||||
@@ -91,6 +111,24 @@ func _Dev_ImportGraph_Handler(srv interface{}, ctx context.Context, dec func(int
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _Dev_Quiesce_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(QuiescenceRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(DevServer).Quiesce(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/devrpc.Dev/Quiesce",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(DevServer).Quiesce(ctx, req.(*QuiescenceRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
// Dev_ServiceDesc is the grpc.ServiceDesc for Dev service.
|
// Dev_ServiceDesc is the grpc.ServiceDesc for Dev service.
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
// and not to be introspected or modified (even as a copy)
|
// and not to be introspected or modified (even as a copy)
|
||||||
@@ -102,6 +140,10 @@ var Dev_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "ImportGraph",
|
MethodName: "ImportGraph",
|
||||||
Handler: _Dev_ImportGraph_Handler,
|
Handler: _Dev_ImportGraph_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "Quiesce",
|
||||||
|
Handler: _Dev_Quiesce_Handler,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Streams: []grpc.StreamDesc{},
|
Streams: []grpc.StreamDesc{},
|
||||||
Metadata: "devrpc/dev.proto",
|
Metadata: "devrpc/dev.proto",
|
||||||
|
|||||||
@@ -18,8 +18,10 @@ import (
|
|||||||
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/lncfg"
|
"github.com/lightningnetwork/lnd/lncfg"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"gopkg.in/macaroon-bakery.v2/bakery"
|
"gopkg.in/macaroon-bakery.v2/bakery"
|
||||||
@@ -40,6 +42,10 @@ var (
|
|||||||
Entity: "offchain",
|
Entity: "offchain",
|
||||||
Action: "write",
|
Action: "write",
|
||||||
}},
|
}},
|
||||||
|
"/devrpc.Dev/Quiesce": {{
|
||||||
|
Entity: "offchain",
|
||||||
|
Action: "write",
|
||||||
|
}},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -56,6 +62,7 @@ type ServerShell struct {
|
|||||||
type Server struct {
|
type Server struct {
|
||||||
started int32 // To be used atomically.
|
started int32 // To be used atomically.
|
||||||
shutdown int32 // To be used atomically.
|
shutdown int32 // To be used atomically.
|
||||||
|
quit chan struct{}
|
||||||
|
|
||||||
// Required by the grpc-gateway/v2 library for forward compatibility.
|
// Required by the grpc-gateway/v2 library for forward compatibility.
|
||||||
// Must be after the atomically used variables to not break struct
|
// Must be after the atomically used variables to not break struct
|
||||||
@@ -78,7 +85,8 @@ func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) {
|
|||||||
// We don't create any new macaroons for this subserver, instead reuse
|
// We don't create any new macaroons for this subserver, instead reuse
|
||||||
// existing onchain/offchain permissions.
|
// existing onchain/offchain permissions.
|
||||||
server := &Server{
|
server := &Server{
|
||||||
cfg: cfg,
|
quit: make(chan struct{}),
|
||||||
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
|
|
||||||
return server, macPermissions, nil
|
return server, macPermissions, nil
|
||||||
@@ -103,6 +111,8 @@ func (s *Server) Stop() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
close(s.quit)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,3 +352,33 @@ func (s *Server) ImportGraph(ctx context.Context,
|
|||||||
|
|
||||||
return &ImportGraphResponse{}, nil
|
return &ImportGraphResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Quiesce initiates the quiescence process for the channel with the given
|
||||||
|
// channel ID. This method will block until the channel is fully quiesced.
|
||||||
|
func (s *Server) Quiesce(_ context.Context, in *QuiescenceRequest) (
|
||||||
|
*QuiescenceResponse, error) {
|
||||||
|
|
||||||
|
txid, err := lnrpc.GetChanPointFundingTxid(in.ChanId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
op := wire.NewOutPoint(txid, in.ChanId.OutputIndex)
|
||||||
|
cid := lnwire.NewChanIDFromOutPoint(*op)
|
||||||
|
ln, err := s.cfg.Switch.GetLink(cid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case result := <-ln.InitStfu():
|
||||||
|
mkResp := func(b lntypes.ChannelParty) *QuiescenceResponse {
|
||||||
|
return &QuiescenceResponse{Initiator: b.IsLocal()}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fn.MapOk(mkResp)(result).Unpack()
|
||||||
|
|
||||||
|
case <-s.quit:
|
||||||
|
return nil, fmt.Errorf("server shutting down")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,7 +43,7 @@ type HarnessRPC struct {
|
|||||||
ChainKit chainrpc.ChainKitClient
|
ChainKit chainrpc.ChainKitClient
|
||||||
NeutrinoKit neutrinorpc.NeutrinoKitClient
|
NeutrinoKit neutrinorpc.NeutrinoKitClient
|
||||||
Peer peersrpc.PeersClient
|
Peer peersrpc.PeersClient
|
||||||
DevRPC devrpc.DevClient
|
Dev devrpc.DevClient
|
||||||
|
|
||||||
// Name is the HarnessNode's name.
|
// Name is the HarnessNode's name.
|
||||||
Name string
|
Name string
|
||||||
@@ -75,7 +75,7 @@ func NewHarnessRPC(ctxt context.Context, t *testing.T, c *grpc.ClientConn,
|
|||||||
ChainKit: chainrpc.NewChainKitClient(c),
|
ChainKit: chainrpc.NewChainKitClient(c),
|
||||||
NeutrinoKit: neutrinorpc.NewNeutrinoKitClient(c),
|
NeutrinoKit: neutrinorpc.NewNeutrinoKitClient(c),
|
||||||
Peer: peersrpc.NewPeersClient(c),
|
Peer: peersrpc.NewPeersClient(c),
|
||||||
DevRPC: devrpc.NewDevClient(c),
|
Dev: devrpc.NewDevClient(c),
|
||||||
Name: name,
|
Name: name,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
|
"github.com/lightningnetwork/lnd/lnrpc/devrpc"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -730,3 +731,17 @@ func (h *HarnessRPC) LookupHtlcResolutionAssertErr(
|
|||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Quiesce makes an RPC call to the node's Quiesce method and returns the
|
||||||
|
// response.
|
||||||
|
func (h *HarnessRPC) Quiesce(
|
||||||
|
req *devrpc.QuiescenceRequest) *devrpc.QuiescenceResponse {
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
res, err := h.Dev.Quiesce(ctx, req)
|
||||||
|
h.NoError(err, "Quiesce returned an error")
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
@@ -171,6 +171,16 @@ const (
|
|||||||
// sender-generated preimages according to BOLT XX.
|
// sender-generated preimages according to BOLT XX.
|
||||||
AMPOptional FeatureBit = 31
|
AMPOptional FeatureBit = 31
|
||||||
|
|
||||||
|
// QuiescenceRequired is a required feature bit that denotes that a
|
||||||
|
// connection established with this node must support the quiescence
|
||||||
|
// protocol if it wants to have a channel relationship.
|
||||||
|
QuiescenceRequired FeatureBit = 34
|
||||||
|
|
||||||
|
// QuiescenceOptional is an optional feature bit that denotes that a
|
||||||
|
// connection established with this node is permitted to use the
|
||||||
|
// quiescence protocol.
|
||||||
|
QuiescenceOptional FeatureBit = 35
|
||||||
|
|
||||||
// ExplicitChannelTypeRequired is a required bit that denotes that a
|
// ExplicitChannelTypeRequired is a required bit that denotes that a
|
||||||
// connection established with this node is to use explicit channel
|
// connection established with this node is to use explicit channel
|
||||||
// commitment types for negotiation instead of the existing implicit
|
// commitment types for negotiation instead of the existing implicit
|
||||||
@@ -335,6 +345,8 @@ var Features = map[FeatureBit]string{
|
|||||||
WumboChannelsOptional: "wumbo-channels",
|
WumboChannelsOptional: "wumbo-channels",
|
||||||
AMPRequired: "amp",
|
AMPRequired: "amp",
|
||||||
AMPOptional: "amp",
|
AMPOptional: "amp",
|
||||||
|
QuiescenceRequired: "quiescence",
|
||||||
|
QuiescenceOptional: "quiescence",
|
||||||
PaymentMetadataOptional: "payment-metadata",
|
PaymentMetadataOptional: "payment-metadata",
|
||||||
PaymentMetadataRequired: "payment-metadata",
|
PaymentMetadataRequired: "payment-metadata",
|
||||||
ExplicitChannelTypeOptional: "explicit-commitment-type",
|
ExplicitChannelTypeOptional: "explicit-commitment-type",
|
||||||
|
|||||||
@@ -63,6 +63,25 @@ const (
|
|||||||
MsgKickoffSig = 777
|
MsgKickoffSig = 777
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// IsChannelUpdate is a filter function that discerns channel update messages
|
||||||
|
// from the other messages in the Lightning Network Protocol.
|
||||||
|
func (t MessageType) IsChannelUpdate() bool {
|
||||||
|
switch t {
|
||||||
|
case MsgUpdateAddHTLC:
|
||||||
|
return true
|
||||||
|
case MsgUpdateFulfillHTLC:
|
||||||
|
return true
|
||||||
|
case MsgUpdateFailHTLC:
|
||||||
|
return true
|
||||||
|
case MsgUpdateFailMalformedHTLC:
|
||||||
|
return true
|
||||||
|
case MsgUpdateFee:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ErrorEncodeMessage is used when failed to encode the message payload.
|
// ErrorEncodeMessage is used when failed to encode the message payload.
|
||||||
func ErrorEncodeMessage(err error) error {
|
func ErrorEncodeMessage(err error) error {
|
||||||
return fmt.Errorf("failed to encode message to buffer, got %w", err)
|
return fmt.Errorf("failed to encode message to buffer, got %w", err)
|
||||||
|
|||||||
@@ -411,6 +411,10 @@ type Config struct {
|
|||||||
// invalid.
|
// invalid.
|
||||||
DisallowRouteBlinding bool
|
DisallowRouteBlinding bool
|
||||||
|
|
||||||
|
// DisallowQuiescence is a flag that indicates whether the Brontide
|
||||||
|
// should have the quiescence feature disabled.
|
||||||
|
DisallowQuiescence bool
|
||||||
|
|
||||||
// MaxFeeExposure limits the number of outstanding fees in a channel.
|
// MaxFeeExposure limits the number of outstanding fees in a channel.
|
||||||
// This value will be passed to created links.
|
// This value will be passed to created links.
|
||||||
MaxFeeExposure lnwire.MilliSatoshi
|
MaxFeeExposure lnwire.MilliSatoshi
|
||||||
@@ -1324,6 +1328,8 @@ func (p *Brontide) addLink(chanPoint *wire.OutPoint,
|
|||||||
DisallowRouteBlinding: p.cfg.DisallowRouteBlinding,
|
DisallowRouteBlinding: p.cfg.DisallowRouteBlinding,
|
||||||
MaxFeeExposure: p.cfg.MaxFeeExposure,
|
MaxFeeExposure: p.cfg.MaxFeeExposure,
|
||||||
ShouldFwdExpEndorsement: p.cfg.ShouldFwdExpEndorsement,
|
ShouldFwdExpEndorsement: p.cfg.ShouldFwdExpEndorsement,
|
||||||
|
DisallowQuiescence: p.cfg.DisallowQuiescence ||
|
||||||
|
!p.remoteFeatures.HasFeature(lnwire.QuiescenceOptional),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Before adding our new link, purge the switch of any pending or live
|
// Before adding our new link, purge the switch of any pending or live
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/keychain"
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
"github.com/lightningnetwork/lnd/lntest/channels"
|
"github.com/lightningnetwork/lnd/lntest/channels"
|
||||||
"github.com/lightningnetwork/lnd/lntest/mock"
|
"github.com/lightningnetwork/lnd/lntest/mock"
|
||||||
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet"
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
@@ -477,6 +478,14 @@ func (m *mockUpdateHandler) OnCommitOnce(
|
|||||||
|
|
||||||
hook()
|
hook()
|
||||||
}
|
}
|
||||||
|
func (m *mockUpdateHandler) InitStfu() <-chan fn.Result[lntypes.ChannelParty] {
|
||||||
|
// TODO(proofofkeags): Implement
|
||||||
|
c := make(chan fn.Result[lntypes.ChannelParty], 1)
|
||||||
|
|
||||||
|
c <- fn.Errf[lntypes.ChannelParty]("InitStfu not yet implemented")
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
func newMockConn(t *testing.T, expectedMessages int) *mockMessageConn {
|
func newMockConn(t *testing.T, expectedMessages int) *mockMessageConn {
|
||||||
return &mockMessageConn{
|
return &mockMessageConn{
|
||||||
|
|||||||
@@ -587,6 +587,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
|||||||
NoTaprootOverlay: !cfg.ProtocolOptions.TaprootOverlayChans,
|
NoTaprootOverlay: !cfg.ProtocolOptions.TaprootOverlayChans,
|
||||||
NoRouteBlinding: cfg.ProtocolOptions.NoRouteBlinding(),
|
NoRouteBlinding: cfg.ProtocolOptions.NoRouteBlinding(),
|
||||||
NoExperimentalEndorsement: cfg.ProtocolOptions.NoExperimentalEndorsement(),
|
NoExperimentalEndorsement: cfg.ProtocolOptions.NoExperimentalEndorsement(),
|
||||||
|
NoQuiescence: cfg.ProtocolOptions.NoQuiescence(),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -4214,6 +4215,7 @@ func (s *server) peerConnected(conn net.Conn, connReq *connmgr.ConnReq,
|
|||||||
RequestAlias: s.aliasMgr.RequestAlias,
|
RequestAlias: s.aliasMgr.RequestAlias,
|
||||||
AddLocalAlias: s.aliasMgr.AddLocalAlias,
|
AddLocalAlias: s.aliasMgr.AddLocalAlias,
|
||||||
DisallowRouteBlinding: s.cfg.ProtocolOptions.NoRouteBlinding(),
|
DisallowRouteBlinding: s.cfg.ProtocolOptions.NoRouteBlinding(),
|
||||||
|
DisallowQuiescence: s.cfg.ProtocolOptions.NoQuiescence(),
|
||||||
MaxFeeExposure: thresholdMSats,
|
MaxFeeExposure: thresholdMSats,
|
||||||
Quit: s.quit,
|
Quit: s.quit,
|
||||||
AuxLeafStore: s.implCfg.AuxLeafStore,
|
AuxLeafStore: s.implCfg.AuxLeafStore,
|
||||||
|
|||||||
@@ -346,6 +346,10 @@ func (s *subRPCServerConfigs) PopulateDependencies(cfg *Config,
|
|||||||
reflect.ValueOf(graphDB),
|
reflect.ValueOf(graphDB),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
subCfgValue.FieldByName("Switch").Set(
|
||||||
|
reflect.ValueOf(htlcSwitch),
|
||||||
|
)
|
||||||
|
|
||||||
case *peersrpc.Config:
|
case *peersrpc.Config:
|
||||||
subCfgValue := extractReflectValue(subCfg)
|
subCfgValue := extractReflectValue(subCfg)
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user