-
Notifications
You must be signed in to change notification settings - Fork 266
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
Plugin messaging #1528
Plugin messaging #1528
Conversation
…e revealed or exposed to messaging can set it to None.
eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageCodecs.scala
Outdated
Show resolved
Hide resolved
Hi @btcontract We've begun hacking away at eclair to introduce some of the PTLC work (and future DLC work) we've been writing about at Suredbits for awhile now. Would these two use cases apply here? https://github.com/bitcoin-s/eclair/pulls?q=is%3Apr+label%3Aptlc+ |
@Christewart I don't yet understand how PTLC/DLC works but if this involves communication between two peers then yes, a related plugin could define a completely independant system of messages which is convenient for your use case, no need for tlv data even. |
I think this part is unnecessary, that is exactly why we have feature bits in place so I wouldn't want to add this to the base eclair layer. And from an abstraction's point of view, it's also better. A node connecting to you doesn't care that a feature works via a plugin or via the base code; it shouldn't even care what implementation you're running, as long as you support the feature they want. And ideally in the long run, custom features that users like will end up being a standard implemented by many, which is better for the ecosystem. There is a risk that another app uses the same feature bits for its experiments, but I don't think it's an issue in practice because if you think they support your feature because they've activate the same feature bit, when you'll exchange custom messages they will fail to decode and either side will close the connection. My suggestion is the following flow instead:
With that system, we could even allow unknown even messages, which ensures that if you connect to a peer that turned on the same feature bits but doesn't really support the feature, the connection will be closed as soon as a message is sent/received because it's unknown to one side. |
Here's what's done so far:
Do we really need to do it this way? Having actual plugin-specific message as |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great, it looks like this won't be too much added code and will add a lot of usefulness to plugins!
While doing this sealed trait Feature became just trait Feature to reference it in Plugin.scala, is this acceptable?
Yes I think it's totally acceptable, the Feature
trait is not something we want to exhaustively pattern-match on so I'd go with it.
At start-up, plugins inject the list of message types (the 2-byte number at the beginning of every lightning message) they're interested in
Do we really need to do it this way? Having actual plugin-specific message as data: ByteVector inside of PluginMessage gives plugin devs more freedom, as an extreme example some plugin may decide to exchange data in, say, Json format (converted to bytes).
Yes I think my suggestion is more in-line with how lightning messages are used and will evolve (and fits what we're doing on the Acinq node for Phoenix features).
When the peer receives an unknown message, it checks in its nodeParams
if something subscribed to that message type; if yes, it simply sends it to the eventStrean. That doesn't prevent you from creating a message RequestSomethingFromPlugin
for which the inner data is JSON or protobuf. WDYT?
eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageCodecs.scala
Outdated
Show resolved
Hide resolved
eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageCodecs.scala
Outdated
Show resolved
Hide resolved
To make sure I understand this right:
|
Yes that's exactly what I had in mind (if there's not blocking point when implementing that). |
I've started on this, but right at the outset this feels a bit futile since:
Am I missing something? |
Yes that's totally ok, it is a
That's ok too, it's up to the plugin to decide how it wants to use it. |
Okay, this might be overly complex, maybe there are easier ways but anyway:
|
eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageCodecs.scala
Outdated
Show resolved
Hide resolved
eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageCodecs.scala
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is shaping up nicely, let us know when you've tested this E2E with a sample plugin!
if (pluginMessageTags.contains(unknownMsg.tag)) { | ||
context.system.eventStream.publish(UnknownMessageReceived(self, remoteNodeId, unknownMsg)) | ||
} | ||
case Event(unknownMsg: UnknownMessage, _: ConnectedData) if pluginMessageTags.contains(unknownMsg.tag) => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Besides mentioned fixes this one makes an UnknownMessage
without subscribers to be logged in next case instead of failing silently.
This allows a plugin to listen to `PeerConnected`, then check if `ConnectedData.remoteInit` supports plugin's feature bit, then push some data to remote peer if it does.
I've made a sample here: https://github.com/btcontract/eclair-plugin-messaging-sample. It pushes a json-encoded local feerate to remote peer on connect if remote peer supports this plugin (makes use of It compiles but I could not figure out how to build my Eclair snapshot jar such that it could run. Looks like something changed in build process and jars do not contain all dependencies inside now? Some instructions on building it would be appreciated. |
We now use standard maven builds (to get reproducible builds) instead of capsule. |
My bad, a zip file with everything needed was being created all the time, but I did not pay attention to it. OK, I've just tested this with two Eclair instances and it works, I can see |
Great, the overall architecture sounds good, let me know when this is ready for review. |
eclair-core/src/test/scala/fr/acinq/eclair/wire/LightningMessageCodecsSpec.scala
Outdated
Show resolved
Hide resolved
eclair-core/src/test/scala/fr/acinq/eclair/wire/LightningMessageCodecsSpec.scala
Outdated
Show resolved
Hide resolved
There is a slight complication when using To solve it I've defined a method |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a slight complication when using
ConnectionInfo
class: inonTransition
onlynextStateData
is visible which isPeer.Data
, notConnectedData
.
While I agree that nextStateData
isn't properly typed in transitions, we know that for transitions that go to the CONNECTED
state (which is where the PeerConnected
event is sent) it will always be a ConnectedData
, so that shouldn't be an Option
.
You'll need to case nextStateData.asInstanceOf[ConnectedData]
in the transition.
Once we migrate this actor to Akka typed we probably won't need this explicit cast.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thanks!
This is a draft of how plugin messaging between two peers could work.
It makes sense to enable plugins to communicate with each other via an already existing and authenticated socket connection rather than for example getting them to open another HTTP endpoint just for that.
The idea is as follows:
QuerySupportedPlugins
message.ReplySupportedPlugins
so interested peer knows what to expect.PluginMessage
withpluginId
attached, aPeer.scala
would issue an event on seeing such a message which in turn would be intercepted by plugin and further processed according to plugin's specific rules.