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

now using akka-http-json and akka-http-client (removed acinq-tools dependency)

This commit is contained in:
pm47 2016-11-08 18:57:22 +01:00
parent 6cd0198fc3
commit 3fc25da461
9 changed files with 170 additions and 139 deletions

View file

@ -48,6 +48,7 @@
</build>
<dependencies>
<!-- AKKA -->
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-actor_${scala.version.short}</artifactId>
@ -61,44 +62,64 @@
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-http-core_${scala.version.short}</artifactId>
<version>${akka.version}</version>
<version>3.0.0-RC1</version>
</dependency>
<!-- JSON -->
<dependency>
<groupId>org.json4s</groupId>
<artifactId>json4s-jackson_${scala.version.short}</artifactId>
<version>3.5.0</version>
</dependency>
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-http-experimental_${scala.version.short}</artifactId>
<version>${akka.version}</version>
<groupId>de.heikoseeberger</groupId>
<artifactId>akka-http-json4s_${scala.version.short}</artifactId>
<version>1.10.1</version>
</dependency>
<!-- BITCOIN -->
<dependency>
<groupId>fr.acinq</groupId>
<artifactId>bitcoin-lib_${scala.version.short}</artifactId>
<version>${bitcoinlib.version}</version>
</dependency>
<dependency>
<groupId>fr.acinq.acinq-tools</groupId>
<artifactId>bitcoin-async-api_${scala.version.short}</artifactId>
<version>${acinqtools.version}</version>
</dependency>
<!-- SERIALIZATION -->
<dependency>
<groupId>fr.acinq.eclair</groupId>
<artifactId>lightning-types_${scala.version.short}</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>com.trueaccord.scalapb</groupId>
<artifactId>scalapb-runtime_${scala.version.short}</artifactId>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>com.trueaccord.scalapb</groupId>
<artifactId>scalapb-runtime_${scala.version.short}</artifactId>
<version>${scalapb.version}</version>
</dependency>
<dependency>
<groupId>com.trueaccord.lenses</groupId>
<artifactId>lenses_${scala.version.short}</artifactId>
<version>0.4</version>
</dependency>
<!-- LOGGING -->
<dependency>
<groupId>org.clapper</groupId>
<artifactId>grizzled-slf4j_${scala.version.short}</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<!--conditional logging -->
<groupId>janino</groupId>
<artifactId>janino</artifactId>
<version>2.5.10</version>
</dependency>
<!-- OTHER -->
<dependency>
<groupId>org.kitteh.irc</groupId>
<artifactId>client-lib</artifactId>
@ -115,21 +136,17 @@
<version>0.9.2</version>
</dependency>
<dependency>
<!-- This is to get rid of '[WARNING] warning: Class javax.annotation.Nonnull not found - continuing with a stub.' compile errors git s-->
<!-- This is to get rid of '[WARNING] warning: Class javax.annotation.Nonnull not found - continuing with a stub.' compile errors -->
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<!--conditional logging -->
<groupId>janino</groupId>
<artifactId>janino</artifactId>
<version>2.5.10</version>
</dependency>
<!-- TESTS -->
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-testkit_${scala.version.short}</artifactId>
<version>${akka.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>

View file

@ -2,16 +2,15 @@ package fr.acinq.eclair
import javafx.application.{Application, Platform}
import akka.actor.Actor.Receive
import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import akka.event.Logging
import akka.http.scaladsl.Http
import akka.stream.ActorMaterializer
import akka.util.Timeout
import com.typesafe.config.ConfigFactory
import fr.acinq.bitcoin.{BitcoinJsonRPCClient, Satoshi}
import fr.acinq.bitcoin.Satoshi
import fr.acinq.eclair.api.Service
import fr.acinq.eclair.blockchain.peer.PeerClient
import fr.acinq.eclair.blockchain.rpc.BitcoinJsonRPCClient
import fr.acinq.eclair.blockchain.{ExtendedBitcoinClient, PeerWatcher}
import fr.acinq.eclair.channel._
import fr.acinq.eclair.gui.FxApp
@ -45,6 +44,10 @@ class Setup() extends Logging {
logger.info(s"nodeid=${Globals.Node.publicKey}")
val config = ConfigFactory.load()
implicit lazy val system = ActorSystem()
implicit val materializer = ActorMaterializer()
implicit val timeout = Timeout(30 seconds)
val bitcoin_client = new ExtendedBitcoinClient(new BitcoinJsonRPCClient(
user = config.getString("eclair.bitcoind.rpcuser"),
password = config.getString("eclair.bitcoind.rpcpassword"),
@ -57,10 +60,6 @@ class Setup() extends Logging {
assert(chain == "testnet" || chain == "regtest" || chain == "segnet4", "you should be on testnet or regtest or segnet4")
val bitcoinVersion = Await.result(bitcoin_client.client.invoke("getinfo").map(json => (json \ "version").extract[String]), 10 seconds)
implicit lazy val system = ActorSystem()
implicit val materializer = ActorMaterializer()
implicit val timeout = Timeout(30 seconds)
val fatalEventPromise = Promise[FatalEvent]()
system.actorOf(Props(new Actor {
system.eventStream.subscribe(self, classOf[FatalEvent])

View file

@ -1,47 +1,51 @@
package fr.acinq.eclair.api
import akka.actor.ActorRef
import akka.http.scaladsl.model.headers.RawHeader
import akka.http.scaladsl.model.{ContentTypes, HttpEntity, HttpResponse, StatusCodes}
import akka.util.Timeout
import akka.http.scaladsl.model.HttpMethods._
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.model.headers.CacheDirectives.{`max-age`, `no-store`, public}
import akka.http.scaladsl.model.headers.HttpOriginRange.*
import akka.http.scaladsl.model.headers._
import akka.http.scaladsl.server.Directives._
import akka.pattern.ask
import akka.util.Timeout
import de.heikoseeberger.akkahttpjson4s.Json4sSupport
import de.heikoseeberger.akkahttpjson4s.Json4sSupport.ShouldWritePretty
import fr.acinq.bitcoin.{BinaryData, Satoshi}
import fr.acinq.eclair._
import fr.acinq.eclair.channel._
import grizzled.slf4j.Logging
import org.json4s.JsonAST.JString
import org.json4s._
import org.json4s.jackson.JsonMethods._
import org.json4s.jackson.Serialization
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success}
import akka.pattern.ask
import fr.acinq.eclair.channel.Register.{ListChannels, SendCommand}
import fr.acinq.eclair.channel._
import fr.acinq.eclair.payment.CreatePayment
import fr.acinq.eclair.router.ChannelDesc
import grizzled.slf4j.Logging
import org.json4s.JsonAST.{JDouble, JInt, JString}
import org.json4s.{JValue, jackson}
import scala.concurrent.duration._
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success}
/**
* Created by PM on 25/01/2016.
*/
// @formatter:off
case class JsonRPCBody(jsonrpc: String = "1.0", id: String = "scala-client", method: String, params: Seq[JValue])
case class Error(code: Int, message: String)
case class JsonRPCRes(result: AnyRef, error: Option[Error], id: String)
case class Status(node_id: String)
// @formatter:on
//TODO : use Json4sSupport ?
trait Service extends Logging {
implicit def ec: ExecutionContext = ExecutionContext.Implicits.global
implicit val serialization = jackson.Serialization
implicit val formats = org.json4s.DefaultFormats + new BinaryDataSerializer + new StateSerializer + new Sha256Serializer + new ShaChainSerializer
implicit val timeout = Timeout(30 seconds)
implicit val shouldWritePretty: ShouldWritePretty = ShouldWritePretty.True
import Json4sSupport.{json4sMarshaller, json4sUnmarshaller}
def connect(host: String, port: Int, amount: Satoshi): Unit
@ -53,62 +57,59 @@ trait Service extends Logging {
def paymentHandler: ActorRef
val customHeaders = RawHeader("Access-Control-Allow-Origin", "*") ::
RawHeader("Access-Control-Allow-Headers", "Content-Type") ::
RawHeader("Access-Control-Allow-Methods", "PUT, GET, POST, DELETE, OPTIONS") ::
RawHeader("Cache-control", "public, no-store, max-age=0") ::
RawHeader("Access-Control-Allow-Headers", "x-requested-with") :: Nil
val customHeaders = `Access-Control-Allow-Origin`(*) ::
`Access-Control-Allow-Headers`("Content-Type, Authorization") ::
`Access-Control-Allow-Methods`(PUT, GET, POST, DELETE, OPTIONS) ::
`Cache-Control`(public, `no-store`, `max-age`(0)) ::
`Access-Control-Allow-Headers`("x-requested-with") :: Nil
val route =
pathSingleSlash {
post {
entity(as[String]) {
body =>
val json = parse(body).extract[JsonRPCBody]
val f_res: Future[AnyRef] = json match {
case JsonRPCBody(_, _, "connect", JString(host) :: JInt(port) :: JInt(anchor_amount) :: Nil) =>
connect(host, port.toInt, Satoshi(anchor_amount.toLong))
Future.successful("ok")
case JsonRPCBody(_, _, "info", _) =>
Future.successful(Status(Globals.Node.id))
case JsonRPCBody(_, _, "list", _) =>
(register ? ListChannels).mapTo[Iterable[ActorRef]]
.flatMap(l => Future.sequence(l.map(c => c ? CMD_GETINFO)))
case JsonRPCBody(_, _, "network", _) =>
(router ? 'network).mapTo[Iterable[ChannelDesc]]
case JsonRPCBody(_, _, "addhtlc", JInt(amount) :: JString(rhash) :: JString(nodeId) :: Nil) =>
(paymentInitiator ? CreatePayment(amount.toInt, BinaryData(rhash), BinaryData(nodeId))).mapTo[ChannelEvent]
case JsonRPCBody(_, _, "genh", _) =>
(paymentHandler ? 'genh).mapTo[BinaryData]
case JsonRPCBody(_, _, "sign", JString(channel) :: Nil) =>
(register ? SendCommand(channel, CMD_SIGN)).mapTo[ActorRef].map(_ => "ok")
case JsonRPCBody(_, _, "fulfillhtlc", JString(channel) :: JDouble(id) :: JString(r) :: Nil) =>
(register ? SendCommand(channel, CMD_FULFILL_HTLC(id.toLong, BinaryData(r), commit = true))).mapTo[ActorRef].map(_ => "ok")
case JsonRPCBody(_, _, "close", JString(channel) :: JString(scriptPubKey) :: Nil) =>
(register ? SendCommand(channel, CMD_CLOSE(Some(scriptPubKey)))).mapTo[ActorRef].map(_ => "ok")
case JsonRPCBody(_, _, "close", JString(channel) :: Nil) =>
(register ? SendCommand(channel, CMD_CLOSE(None))).mapTo[ActorRef].map(_ => "ok")
case JsonRPCBody(_, _, "help", _) =>
Future.successful(List(
"info: display basic node information",
"connect (host, port, anchor_amount): open a channel with another eclair or lightningd instance",
"list: list existing channels",
"addhtlc (amount, rhash, nodeId): send an htlc",
"sign (channel_id): update the commitment transaction",
"fulfillhtlc (channel_id, htlc_id, r): fulfill an htlc",
"close (channel_id): close a channel",
"help: display this message"))
case _ => Future.failed(new RuntimeException("method not found"))
}
respondWithDefaultHeaders(customHeaders) {
pathSingleSlash {
post {
entity(as[JsonRPCBody]) {
req =>
val f_res: Future[AnyRef] = req match {
case JsonRPCBody(_, _, "connect", JString(host) :: JInt(port) :: JInt(anchor_amount) :: Nil) =>
connect(host, port.toInt, Satoshi(anchor_amount.toLong))
Future.successful("ok")
case JsonRPCBody(_, _, "info", _) =>
Future.successful(Status(Globals.Node.id))
case JsonRPCBody(_, _, "list", _) =>
(register ? ListChannels).mapTo[Iterable[ActorRef]]
.flatMap(l => Future.sequence(l.map(c => c ? CMD_GETINFO)))
case JsonRPCBody(_, _, "network", _) =>
(router ? 'network).mapTo[Iterable[ChannelDesc]]
case JsonRPCBody(_, _, "addhtlc", JInt(amount) :: JString(rhash) :: JString(nodeId) :: Nil) =>
(paymentInitiator ? CreatePayment(amount.toInt, BinaryData(rhash), BinaryData(nodeId))).mapTo[ChannelEvent]
case JsonRPCBody(_, _, "genh", _) =>
(paymentHandler ? 'genh).mapTo[BinaryData]
case JsonRPCBody(_, _, "sign", JString(channel) :: Nil) =>
(register ? SendCommand(channel, CMD_SIGN)).mapTo[ActorRef].map(_ => "ok")
case JsonRPCBody(_, _, "fulfillhtlc", JString(channel) :: JDouble(id) :: JString(r) :: Nil) =>
(register ? SendCommand(channel, CMD_FULFILL_HTLC(id.toLong, BinaryData(r), commit = true))).mapTo[ActorRef].map(_ => "ok")
case JsonRPCBody(_, _, "close", JString(channel) :: JString(scriptPubKey) :: Nil) =>
(register ? SendCommand(channel, CMD_CLOSE(Some(scriptPubKey)))).mapTo[ActorRef].map(_ => "ok")
case JsonRPCBody(_, _, "close", JString(channel) :: Nil) =>
(register ? SendCommand(channel, CMD_CLOSE(None))).mapTo[ActorRef].map(_ => "ok")
case JsonRPCBody(_, _, "help", _) =>
Future.successful(List(
"info: display basic node information",
"connect (host, port, anchor_amount): open a channel with another eclair or lightningd instance",
"list: list existing channels",
"addhtlc (amount, rhash, nodeId): send an htlc",
"sign (channel_id): update the commitment transaction",
"fulfillhtlc (channel_id, htlc_id, r): fulfill an htlc",
"close (channel_id): close a channel",
"help: display this message"))
case _ => Future.failed(new RuntimeException("method not found"))
}
onComplete(f_res) {
case Success(res) => complete(HttpResponse(StatusCodes.OK, headers = customHeaders, entity = HttpEntity(ContentTypes.`application/json`, Serialization.writePretty(
JsonRPCRes(res, None, json.id)
))))
case Failure(t) => complete(HttpResponse(StatusCodes.InternalServerError, headers = customHeaders, entity = HttpEntity(ContentTypes.`application/json`, Serialization.writePretty(
JsonRPCRes(null, Some(Error(-1, t.getMessage)), json.id))
)))
}
onComplete(f_res) {
case Success(res) => complete(JsonRPCRes(res, None, req.id))
case Failure(t) => complete(StatusCodes.InternalServerError, JsonRPCRes(null, Some(Error(-1, t.getMessage)), req.id))
}
}
}
}
}

View file

@ -1,13 +1,13 @@
package fr.acinq.eclair.blockchain
import fr.acinq.bitcoin._
import fr.acinq.eclair.blockchain.rpc.{BitcoinJsonRPCClient, JsonRPCError}
import fr.acinq.eclair.channel
import fr.acinq.eclair.channel.Scripts
import org.bouncycastle.util.encoders.Hex
import org.json4s.JsonAST._
import scala.concurrent.duration._
import scala.concurrent.{Await, ExecutionContext, Future}
import scala.concurrent.{ExecutionContext, Future}
/**
* Created by PM on 26/04/2016.
@ -27,7 +27,7 @@ class ExtendedBitcoinClient(val client: BitcoinJsonRPCClient) {
client.invoke("getrawtransaction", txId, 1) // we choose verbose output to get the number of confirmations
.map(json => Some((json \ "confirmations").extract[Int]))
.recover {
case t: JsonRPCError if t.code == -5 => None
case t: JsonRPCError if t.error.code == -5 => None
}
/**

View file

@ -0,0 +1,45 @@
package fr.acinq.eclair.blockchain.rpc
import java.io.IOException
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.marshalling.Marshal
import akka.http.scaladsl.model.headers.{Authorization, BasicHttpCredentials}
import akka.http.scaladsl.model.{HttpMethods, HttpRequest, RequestEntity, Uri}
import akka.http.scaladsl.unmarshalling.Unmarshal
import akka.stream.ActorMaterializer
import de.heikoseeberger.akkahttpjson4s.Json4sSupport._
import org.json4s.JsonAST.JValue
import org.json4s.{DefaultFormats, jackson}
import scala.concurrent.{ExecutionContext, Future}
// @formatter:off
case class JsonRPCRequest(jsonrpc: String = "1.0", id: String = "scala-client", method: String, params: Seq[Any])
case class Error(code: Int, message: String)
case class JsonRPCResponse(result: JValue, error: Option[Error], id: String)
case class JsonRPCError(error: Error) extends IOException(s"${error.message} (code: ${error.code})")
// @formatter:on
class BitcoinJsonRPCClient(user: String, password: String, host: String = "127.0.0.1", port: Int = 8332, ssl: Boolean = false)(implicit system: ActorSystem) {
val scheme = if (ssl) "https" else "http"
val uri = Uri(s"$scheme://$host:$port")
implicit val materializer = ActorMaterializer()
val httpClient = Http(system)
implicit val serialization = jackson.Serialization
implicit val formats = DefaultFormats
def invoke(method: String, params: Any*)(implicit ec: ExecutionContext): Future[JValue] =
for {
entity <- Marshal(JsonRPCRequest(method = method, params = params)).to[RequestEntity]
httpRes <- httpClient.singleRequest(HttpRequest(uri = uri, method = HttpMethods.POST).addHeader(Authorization(BasicHttpCredentials(user, password))).withEntity(entity))
jsonRpcRes <- Unmarshal(httpRes).to[JsonRPCResponse].map {
case JsonRPCResponse(_, Some(error), _) => throw JsonRPCError(error)
case o => o
}
} yield jsonRpcRes.result
}

View file

@ -1,9 +1,10 @@
package fr.acinq.eclair
import akka.actor.ActorSystem
import fr.acinq.bitcoin.{BinaryData, BitcoinJsonRPCClient, Satoshi, Transaction, TxIn, TxOut}
import fr.acinq.bitcoin.{BinaryData, Satoshi, Transaction, TxIn, TxOut}
import fr.acinq.eclair.blockchain.ExtendedBitcoinClient
import fr.acinq.eclair.blockchain.peer.{NewBlock, NewTransaction}
import fr.acinq.eclair.blockchain.rpc.BitcoinJsonRPCClient
import fr.acinq.eclair.channel.Scripts
import scala.concurrent.{ExecutionContext, Future}
@ -14,8 +15,6 @@ import scala.concurrent.duration._
*/
class TestBitcoinClient()(implicit system: ActorSystem) extends ExtendedBitcoinClient(new BitcoinJsonRPCClient("", "", "", 0)) {
client.client.close()
import scala.concurrent.ExecutionContext.Implicits.global
system.scheduler.schedule(100 milliseconds, 100 milliseconds, new Runnable {
override def run(): Unit = system.eventStream.publish(NewBlock(null)) // blocks are not actually interpreted

View file

@ -4,19 +4,19 @@ import java.nio.file.{Files, Paths}
import akka.actor.{ActorRef, ActorSystem}
import akka.pattern.ask
import akka.testkit.TestKit
import akka.util.Timeout
import com.typesafe.config.ConfigFactory
import fr.acinq.bitcoin.{BinaryData, BitcoinJsonRPCClient}
import fr.acinq.bitcoin.BinaryData
import fr.acinq.eclair._
import fr.acinq.eclair.blockchain.ExtendedBitcoinClient
import fr.acinq.eclair.blockchain.rpc.BitcoinJsonRPCClient
import fr.acinq.eclair.channel.Register.ListChannels
import fr.acinq.eclair.channel.{CLOSED, CLOSING, CMD_ADD_HTLC, _}
import lightning.locktime
import lightning.locktime.Locktime.Blocks
import org.json4s.JsonAST.JString
import org.json4s.jackson.JsonMethods._
import org.scalatest.{BeforeAndAfterAll, FunSuite, FunSuiteLike}
import org.scalatest.{BeforeAndAfterAll, FunSuite}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent._
@ -60,7 +60,7 @@ class InteroperabilitySpec extends FunSuite with BeforeAndAfterAll {
Thread.sleep(5000)
assert(!bitcoindf.isCompleted)
val bitcoinClient = new BitcoinJsonRPCClient(user = "foo", password = "bar", host = "localhost", port = 18332)
val bitcoinClient = new BitcoinJsonRPCClient(user = "foo", password = "bar", host = "localhost", port = 18332)(ActorSystem())
val btccli = new ExtendedBitcoinClient(bitcoinClient)
Await.result(btccli.client.invoke("getblockchaininfo"), 3 seconds)

View file

@ -95,6 +95,7 @@
<dependency>
<groupId>com.trueaccord.scalapb</groupId>
<artifactId>scalapb-runtime_${scala.version.short}</artifactId>
<version>${scalapb.version}</version>
</dependency>
</dependencies>

33
pom.xml
View file

@ -45,9 +45,8 @@
<maven.compiler.target>1.7</maven.compiler.target>
<scala.version>2.11.8</scala.version>
<scala.version.short>2.11</scala.version.short>
<akka.version>2.4.9</akka.version>
<akka.version>2.4.12</akka.version>
<bitcoinlib.version>0.9.6</bitcoinlib.version>
<acinqtools.version>1.3</acinqtools.version>
<scalapb.version>0.4.21</scalapb.version>
</properties>
@ -158,36 +157,6 @@
</repository>
</repositories>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>fr.acinq</groupId>
<artifactId>bitcoin-lib_${scala.version.short}</artifactId>
<version>${bitcoinlib.version}</version>
</dependency>
<dependency>
<groupId>com.trueaccord.scalapb</groupId>
<artifactId>scalapb-runtime_${scala.version.short}</artifactId>
<version>${scalapb.version}</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>com.trueaccord.lenses</groupId>
<artifactId>lenses_${scala.version.short}</artifactId>
<version>0.4</version>
</dependency>
<dependency>
<groupId>org.json4s</groupId>
<artifactId>json4s-jackson_${scala.version.short}</artifactId>
<version>3.2.11</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.scala-lang</groupId>