Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,17 @@ If you want to persist the data directory, you can make the volume to your host
docker run -ti --rm -v "/path_on_host:/data" -e "JAVA_OPTS=-Declair.printToConsole" acinq/eclair
```

## Plugins

For advanced usage, Eclair supports plugins written in Scala, Java, or any JVM-compatible language.

A valid plugin is a jar that contains an implementation of the [Plugin](eclair-node/src/main/scala/fr/acinq/eclair/Plugin.scala) interface.

Here is how to run Eclair with plugins:
```shell
java -jar eclair-node-<version>-<commit_id>.jar <plugin1.jar> <plugin2.jar> <...>
```

## Mainnet usage

Following are the minimum configuration files you need to use for Bitcoin Core and Eclair.
Expand Down
6 changes: 6 additions & 0 deletions eclair-node/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@
<artifactId>eclair-core_${scala.version.short}</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<!-- for plugins -->
<groupId>org.clapper</groupId>
<artifactId>classutil_${scala.version.short}</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
Expand Down
11 changes: 8 additions & 3 deletions eclair-node/src/main/scala/fr/acinq/eclair/Boot.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import akka.actor.ActorSystem
import grizzled.slf4j.Logging

import scala.concurrent.ExecutionContext
import scala.util.{Failure, Success}

/**
* Created by PM on 25/01/2016.
Expand All @@ -31,10 +32,15 @@ object Boot extends App with Logging {
val datadir = new File(System.getProperty("eclair.datadir", System.getProperty("user.home") + "/.eclair"))

try {
val plugins = Plugin.loadPlugins(args.map(new File(_)))
plugins.foreach(plugin => logger.info(s"loaded plugin ${plugin.getClass.getSimpleName}"))
implicit val system: ActorSystem = ActorSystem("eclair-node")
implicit val ec: ExecutionContext = system.dispatcher
new Setup(datadir).bootstrap onFailure {
case t: Throwable => onError(t)
val setup = new Setup(datadir)
plugins.foreach(_.onSetup(setup))
setup.bootstrap onComplete {
case Success(kit) => plugins.foreach(_.onKit(kit))
case Failure(t) => onError(t)
}
} catch {
case t: Throwable => onError(t)
Expand All @@ -47,4 +53,3 @@ object Boot extends App with Logging {
System.exit(1)
}
}

29 changes: 29 additions & 0 deletions eclair-node/src/main/scala/fr/acinq/eclair/Plugin.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package fr.acinq.eclair

import java.io.File
import java.net.{URL, URLClassLoader}

import org.clapper.classutil.ClassFinder

trait Plugin {

def onSetup(setup: Setup): Unit

def onKit(kit: Kit): Unit

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not sure i would expose the entire Kit, in this way plugins enlarge the attack surface of eclair. An insecure plugin can be exploited to access channel data.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It allows to do powerful stuff on top of eclair, and you are always taking risks by running someone else's code.


}

object Plugin {

def loadPlugins(jars: Seq[File]): Seq[Plugin] = {
val finder = ClassFinder(jars)
val classes = finder.getClasses
val urls = jars.map(f => new URL(s"file:${f.getCanonicalPath}"))
val loader = new URLClassLoader(urls.toArray, ClassLoader.getSystemClassLoader)
classes
.filter(_.isConcrete)
.filter(_.implements(classOf[Plugin].getName))
.map(c => Class.forName(c.name, true, loader).getDeclaredConstructor().newInstance().asInstanceOf[Plugin])
.toList
}
}