Skip to content

Commit

Permalink
Allow plugins to set a dual funding contribution (#2829)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
t-bast authored Feb 27, 2024
1 parent 36a3c88 commit fd0cdf6
Show file tree
Hide file tree
Showing 5 changed files with 17 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) =>
Expand All @@ -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) =>
Expand Down
7 changes: 3 additions & 4 deletions eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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))
Expand Down Expand Up @@ -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 }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 =>
Expand Down Expand Up @@ -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]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit fd0cdf6

Please sign in to comment.