Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add example channel interceptor plugin #4

Open
wants to merge 18 commits into
base: master
Choose a base branch
from

Conversation

remyers
Copy link

@remyers remyers commented Jan 4, 2023

This PR creates an example of a plugin that intercepts channel open messages sent to a node and continues the process of accepting a valid request, or returns a failure message to the sender.

This example plugin reads a custom configuration file with parameters min-active-channels and min-total-capacity and rejects OpenChannel requests from remote peers if they have less than the minimum public active channels or public total capacity.

Depends on Eclair PR# 2552.

@remyers remyers force-pushed the channel-interceptor-plugin branch from 4865b5a to 7da270d Compare January 9, 2023 14:54
@remyers remyers marked this pull request as ready for review January 27, 2023 12:22
Copy link
Member

@t-bast t-bast left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's going to be a bit painful, but I'd like to rename that plugin...it is currently a sample plugin that shouldn't be used, but I don't think this is very useful to have. We know we'll want a channel interceptor plugin for dual-funding, that decides how much funds (if any) the non-initiator should put into the channel. Since eclair only allows one plugin that intercepts channel requests, that plugin should implement optional additional restrictions (what you currently do in this PR) and liquidity allocation logic (what I'll add later when doing liquidity ads).

So it would make sense to name it to something that reflects that right now, this way I can simply extend it later when working on liquidity ads. Node operators would likely fork that plugin to tweak the liquidity allocation logic (and add new restrictions if needed).

I think that the funding aspect will be more important to users than the additional restrictions (especially since we moved most of the restrictions directly inside eclair), so I believe naming the plugin channel-funding makes more sense? WDYT?


Disclaimer: this plugin is for demonstration purposes only.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the license here? The whole repository already has an Apache-2 license?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why I did that; fixed in 2ac483e.

Comment on lines 25 to 27
## Commands

There are no cli commands for this plugin.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unnecessary, if there are no commands, we don't even need to mention it?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2ac483e

Comment on lines 47 to 48
// load or generate seed
config = loadConfiguration(setup.datadir)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: wrong copy-paste for the comment. Also, why do you load the config early since you only use it once inside onKit? If you loaded it inside onKit you wouldn't need to have a var config: Config = _.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is necessary because of the two-step initialization of plugins. We can only access setup.datadir in OnSetup which we need to load config. We can either have a var config or load the parameters from config in OnSetup and create a var to hold them until OnKit is called.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed the nit in 2ac483e

Comment on lines 29 to 30
assert(minActiveChannels >= 0)
assert(minTotalCapacity >= Satoshi(0))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should assert on exact values here, it would make more sense.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ee57ca2

Comment on lines 57 to 66
private sealed trait ErrorResponse
private object LessThanMinActiveChannelsError extends ErrorResponse {
override def toString: String = s"rejected, less than $minActiveChannels active channels"
}
private object LessThanMinTotalCapacityError extends ErrorResponse {
override def toString: String = s"rejected, less than $minTotalCapacity total capacity"
}
private object NoPublicChannelsError extends ErrorResponse {
override def toString: String = s"rejected, no public channels"
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There errors are used only once and only for their string value, we can remove them and inline the error message?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in fc2f404

case WrappedGetNodeResponse(o, PublicNode(_, _, totalCapacity)) if totalCapacity < minTotalCapacity =>
rejectOpenChannel(o, LessThanMinTotalCapacityError)
Behaviors.same
case WrappedGetNodeResponse(o, UnknownNode(_)) =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be configurable: otherwise when using this plugin we'd have no way of accepting channels from private nodes. There should be a boolean in the config, something like allow-private-nodes = true

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 798a849

Comment on lines 39 to 51
val publicKey: Crypto.PublicKey = PrivateKey(ByteVector32.One).publicKey
val fundingAmount: Satoshi = 1 sat
val remoteNodeId: Crypto.PublicKey = PrivateKey(ByteVector32.One).publicKey
val temporaryChannelId: ByteVector32 = ByteVector32.Zeroes
val openChannel: OpenChannel = OpenChannel(ByteVector32.Zeroes, temporaryChannelId, fundingAmount, 0 msat, 1 sat, UInt64(1), 1 sat, 1 msat, FeeratePerKw(1 sat), CltvExpiryDelta(1), 1, publicKey, publicKey, publicKey, publicKey, publicKey, publicKey, ChannelFlags.Private)
val openDualChannel: OpenDualFundedChannel = OpenDualFundedChannel(ByteVector32.Zeroes, ByteVector32.One, FeeratePerKw(5000 sat), FeeratePerKw(4000 sat), 250_000 sat, 500 sat, UInt64(50_000), 15 msat, CltvExpiryDelta(144), 483, 650_000, publicKey(1), publicKey(2), publicKey(3), publicKey(4), publicKey(5), publicKey(6), publicKey(7), ChannelFlags(true))
val defaultParams: DefaultParams = DefaultParams(100 sat, 100000 msat, 100 msat, CltvExpiryDelta(288), 10)
val channels: Map[Peer.FinalChannelId, actor.ActorRef] = Map(Peer.FinalChannelId(randomBytes32()) -> system.classicSystem.deadLetters)
val connectedData: Peer.ConnectedData = Peer.ConnectedData(NodeAddress.fromParts("1.2.3.4", 42000).get, system.classicSystem.deadLetters, Init(TestConstants.Alice.nodeParams.features.initFeatures()), Init(TestConstants.Bob.nodeParams.features.initFeatures()), channels.map { case (k: ChannelId, v) => (k, v) })
val openChannelNonInitiator: OpenChannelNonInitiator = OpenChannelNonInitiator(remoteNodeId, Left(openChannel), connectedData.localFeatures, connectedData.remoteFeatures, connectedData.peerConnection.toTyped)
val bobAnnouncement: NodeAnnouncement = announcement(TestConstants.Bob.nodeParams.nodeId)

def publicKey(fill: Byte): PublicKey = PrivateKey(ByteVector.fill(32)(fill)).publicKey
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like what we did in the eclair PR, this can be greatly simplified, some of these values are unused or used only once, it deserves a clean-up to be less confusing and focus only on the test itself.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, cleaned up in dc34b5a.

@remyers
Copy link
Author

remyers commented Feb 23, 2023

I think that the funding aspect will be more important to users than the additional restrictions (especially since we moved most of the restrictions directly inside eclair), so I believe naming the plugin channel-funding makes more sense? WDYT?

It makes sense to create a potentially useful plugin rather than a generic example. It's good to also demonstrate other plugin features like using the config file to select which inbound peers though too so people can rift on that for their own plugins.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants