1
0
Fork 0
mirror of https://github.com/ACINQ/eclair.git synced 2025-02-21 22:11:46 +01:00

Allow plugins to set a dual funding contribution (#2829)

We support dual funding, but we cannot decide for the node operator when
to contribute to channels that are being opened to us. Node operators
may want very different policies depending on their goals. Instead of
trying to figure out a general set of policies, we let plugins decide
when to contribute to channels and how much. Node operators are thus
free to implement whatever logic makes sense for them in a plugin.
This commit is contained in:
Bastien Teinturier 2024-02-27 17:03:21 +01:00 committed by GitHub
parent 36a3c8897c
commit fd0cdf6fc1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 17 additions and 16 deletions

View file

@ -67,7 +67,7 @@ case class InterceptOpenChannelReceived(replyTo: ActorRef[InterceptOpenChannelRe
}
sealed trait InterceptOpenChannelResponse
case class AcceptOpenChannel(temporaryChannelId: ByteVector32, defaultParams: DefaultParams) extends InterceptOpenChannelResponse
case class AcceptOpenChannel(temporaryChannelId: ByteVector32, defaultParams: DefaultParams, localFundingAmount_opt: Option[Satoshi]) extends InterceptOpenChannelResponse
case class RejectOpenChannel(temporaryChannelId: ByteVector32, error: Error) extends InterceptOpenChannelResponse
// @formatter:on

View file

@ -167,7 +167,8 @@ private class OpenChannelInterceptor(peer: ActorRef[Any],
nodeParams.pluginOpenChannelInterceptor match {
case Some(plugin) => queryPlugin(plugin, request, localParams, ChannelConfig.standard, channelType)
case None =>
peer ! SpawnChannelNonInitiator(request.open, ChannelConfig.standard, channelType, localParams, request.peerConnection.toClassic)
// NB: we don't add a contribution to the funding amount.
peer ! SpawnChannelNonInitiator(request.open, ChannelConfig.standard, channelType, localParams, None, request.peerConnection.toClassic)
waitForRequest()
}
case PendingChannelsRateLimiterResponse(PendingChannelsRateLimiter.ChannelRateLimited) =>
@ -186,7 +187,7 @@ private class OpenChannelInterceptor(peer: ActorRef[Any],
receiveCommandMessage[QueryPluginCommands](context, "queryPlugin") {
case PluginOpenChannelResponse(pluginResponse: AcceptOpenChannel) =>
val localParams1 = updateLocalParams(localParams, pluginResponse.defaultParams)
peer ! SpawnChannelNonInitiator(request.open, channelConfig, channelType, localParams1, request.peerConnection.toClassic)
peer ! SpawnChannelNonInitiator(request.open, channelConfig, channelType, localParams1, pluginResponse.localFundingAmount_opt, request.peerConnection.toClassic)
timers.cancel(PluginTimeout)
waitForRequest()
case PluginOpenChannelResponse(pluginResponse: RejectOpenChannel) =>

View file

@ -199,7 +199,7 @@ class Peer(val nodeParams: NodeParams,
stay()
}
case Event(SpawnChannelNonInitiator(open, channelConfig, channelType, localParams, peerConnection), d: ConnectedData) =>
case Event(SpawnChannelNonInitiator(open, channelConfig, channelType, localParams, localFundingAmount_opt, peerConnection), d: ConnectedData) =>
val temporaryChannelId = open.fold(_.temporaryChannelId, _.temporaryChannelId)
if (peerConnection == d.peerConnection) {
val channel = spawnChannel()
@ -209,8 +209,7 @@ class Peer(val nodeParams: NodeParams,
channel ! INPUT_INIT_CHANNEL_NON_INITIATOR(open.temporaryChannelId, None, dualFunded = false, None, localParams, d.peerConnection, d.remoteInit, channelConfig, channelType)
channel ! open
case Right(open) =>
// NB: we don't add a contribution to the funding amount.
channel ! INPUT_INIT_CHANNEL_NON_INITIATOR(open.temporaryChannelId, None, dualFunded = true, None, localParams, d.peerConnection, d.remoteInit, channelConfig, channelType)
channel ! INPUT_INIT_CHANNEL_NON_INITIATOR(open.temporaryChannelId, localFundingAmount_opt, dualFunded = true, None, localParams, d.peerConnection, d.remoteInit, channelConfig, channelType)
channel ! open
}
stay() using d.copy(channels = d.channels + (TemporaryChannelId(temporaryChannelId) -> channel))
@ -545,7 +544,7 @@ object Peer {
}
case class SpawnChannelInitiator(replyTo: akka.actor.typed.ActorRef[OpenChannelResponse], cmd: Peer.OpenChannel, channelConfig: ChannelConfig, channelType: SupportedChannelType, localParams: LocalParams)
case class SpawnChannelNonInitiator(open: Either[protocol.OpenChannel, protocol.OpenDualFundedChannel], channelConfig: ChannelConfig, channelType: SupportedChannelType, localParams: LocalParams, peerConnection: ActorRef)
case class SpawnChannelNonInitiator(open: Either[protocol.OpenChannel, protocol.OpenDualFundedChannel], channelConfig: ChannelConfig, channelType: SupportedChannelType, localParams: LocalParams, localFundingAmount_opt: Option[Satoshi], peerConnection: ActorRef)
case class GetPeerInfo(replyTo: Option[typed.ActorRef[PeerInfoResponse]])
sealed trait PeerInfoResponse { def nodeId: PublicKey }

View file

@ -87,13 +87,14 @@ class OpenChannelInterceptorSpec extends ScalaTestWithActorTestKit(ConfigFactory
val openChannelNonInitiator = OpenChannelNonInitiator(remoteNodeId, Left(openChannel), Features.empty, Features.empty, peerConnection.ref, remoteAddress)
openChannelInterceptor ! openChannelNonInitiator
pendingChannelsRateLimiter.expectMessageType[AddOrRejectChannel].replyTo ! PendingChannelsRateLimiter.AcceptOpenChannel
pluginInterceptor.expectMessageType[InterceptOpenChannelReceived].replyTo ! AcceptOpenChannel(randomBytes32(), defaultParams)
val updatedLocalParams = peer.expectMessageType[SpawnChannelNonInitiator].localParams
assert(updatedLocalParams.dustLimit == defaultParams.dustLimit)
assert(updatedLocalParams.htlcMinimum == defaultParams.htlcMinimum)
assert(updatedLocalParams.maxAcceptedHtlcs == defaultParams.maxAcceptedHtlcs)
assert(updatedLocalParams.maxHtlcValueInFlightMsat == defaultParams.maxHtlcValueInFlightMsat)
assert(updatedLocalParams.toSelfDelay == defaultParams.toSelfDelay)
pluginInterceptor.expectMessageType[InterceptOpenChannelReceived].replyTo ! AcceptOpenChannel(randomBytes32(), defaultParams, Some(50_000 sat))
val response = peer.expectMessageType[SpawnChannelNonInitiator]
assert(response.localFundingAmount_opt.contains(50_000 sat))
assert(response.localParams.dustLimit == defaultParams.dustLimit)
assert(response.localParams.htlcMinimum == defaultParams.htlcMinimum)
assert(response.localParams.maxAcceptedHtlcs == defaultParams.maxAcceptedHtlcs)
assert(response.localParams.maxHtlcValueInFlightMsat == defaultParams.maxHtlcValueInFlightMsat)
assert(response.localParams.toSelfDelay == defaultParams.toSelfDelay)
}
test("continue channel open if no interceptor plugin registered and pending channels rate limiter accepts it") { f =>
@ -146,7 +147,7 @@ class OpenChannelInterceptorSpec extends ScalaTestWithActorTestKit(ConfigFactory
assert(peer.expectMessageType[OutgoingMessage].msg.asInstanceOf[Error].channelId == ByteVector32.One)
// original request accepted after plugin accepts it
pluginInterceptor.expectMessageType[InterceptOpenChannelReceived].replyTo ! AcceptOpenChannel(randomBytes32(), defaultParams)
pluginInterceptor.expectMessageType[InterceptOpenChannelReceived].replyTo ! AcceptOpenChannel(randomBytes32(), defaultParams, None)
assert(peer.expectMessageType[SpawnChannelNonInitiator].open == Left(openChannel))
eventListener.expectMessageType[ChannelAborted]
}

View file

@ -629,7 +629,7 @@ class PeerSpec extends FixtureSpec {
val open = createOpenChannelMessage()
system.eventStream.subscribe(probe.ref, classOf[ChannelAborted])
connect(remoteNodeId, peer, peerConnection, switchboard)
peer ! SpawnChannelNonInitiator(Left(open), ChannelConfig.standard, ChannelTypes.Standard(), localParams, ActorRef.noSender)
peer ! SpawnChannelNonInitiator(Left(open), ChannelConfig.standard, ChannelTypes.Standard(), localParams, None, ActorRef.noSender)
val channelAborted = probe.expectMsgType[ChannelAborted]
assert(channelAborted.remoteNodeId == remoteNodeId)
assert(channelAborted.channelId == open.temporaryChannelId)