mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-01-19 05:43:51 +01:00
Eclair RPC 0.4.1 (#1627)
* Eclair RPC 0.4.1 * channelstats * remove the launch script editing code * getinfo * sendonchain, onchainbalance, onchaintransactions * cleanup * repond to the comments
This commit is contained in:
parent
194370622d
commit
43b6349758
@ -5,11 +5,12 @@ import java.time.Instant
|
|||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
import org.bitcoins.commons.serializers.JsonReaders._
|
import org.bitcoins.commons.serializers.JsonReaders._
|
||||||
|
import org.bitcoins.core.config.BitcoinNetwork
|
||||||
import org.bitcoins.core.currency.Satoshis
|
import org.bitcoins.core.currency.Satoshis
|
||||||
import org.bitcoins.core.protocol.ln.channel.{ChannelState, FundedChannelId}
|
import org.bitcoins.core.protocol.ln.channel.{ChannelState, FundedChannelId}
|
||||||
import org.bitcoins.core.protocol.ln.currency.MilliSatoshis
|
import org.bitcoins.core.protocol.ln.currency.MilliSatoshis
|
||||||
import org.bitcoins.core.protocol.ln.fee.FeeProportionalMillionths
|
import org.bitcoins.core.protocol.ln.fee.FeeProportionalMillionths
|
||||||
import org.bitcoins.core.protocol.ln.node.NodeId
|
import org.bitcoins.core.protocol.ln.node.{Feature, FeatureSupport, NodeId}
|
||||||
import org.bitcoins.core.protocol.ln.{
|
import org.bitcoins.core.protocol.ln.{
|
||||||
LnHumanReadablePart,
|
LnHumanReadablePart,
|
||||||
PaymentPreimage,
|
PaymentPreimage,
|
||||||
@ -28,11 +29,15 @@ import scala.concurrent.duration.FiniteDuration
|
|||||||
sealed abstract class EclairModels
|
sealed abstract class EclairModels
|
||||||
|
|
||||||
case class GetInfoResult(
|
case class GetInfoResult(
|
||||||
|
version: String,
|
||||||
nodeId: NodeId,
|
nodeId: NodeId,
|
||||||
alias: String,
|
alias: String,
|
||||||
|
features: Features,
|
||||||
chainHash: DoubleSha256Digest,
|
chainHash: DoubleSha256Digest,
|
||||||
|
network: BitcoinNetwork,
|
||||||
blockHeight: Long,
|
blockHeight: Long,
|
||||||
publicAddresses: Seq[InetSocketAddress])
|
publicAddresses: Seq[InetSocketAddress],
|
||||||
|
instanceId: UUID)
|
||||||
|
|
||||||
case class PeerInfo(
|
case class PeerInfo(
|
||||||
nodeId: NodeId,
|
nodeId: NodeId,
|
||||||
@ -40,6 +45,31 @@ case class PeerInfo(
|
|||||||
address: Option[String],
|
address: Option[String],
|
||||||
channels: Int)
|
channels: Int)
|
||||||
|
|
||||||
|
case class ChannelCommandResult(
|
||||||
|
results: scala.collection.Map[
|
||||||
|
Either[ShortChannelId, FundedChannelId],
|
||||||
|
ChannelCommandResult.State]
|
||||||
|
)
|
||||||
|
|
||||||
|
object ChannelCommandResult {
|
||||||
|
sealed trait State
|
||||||
|
case object OK extends State
|
||||||
|
case object ChannelOpened extends State
|
||||||
|
case object ChannelClosed extends State
|
||||||
|
case class Error(message: String) extends State
|
||||||
|
|
||||||
|
def fromString(s: String): State =
|
||||||
|
if (s == "ok") {
|
||||||
|
ChannelCommandResult.OK
|
||||||
|
} else if (s.startsWith("created channel ")) {
|
||||||
|
ChannelCommandResult.ChannelOpened
|
||||||
|
} else if (s.startsWith("closed channel ")) {
|
||||||
|
ChannelCommandResult.ChannelClosed
|
||||||
|
} else {
|
||||||
|
ChannelCommandResult.Error(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the data model returned by the RPC call
|
* This is the data model returned by the RPC call
|
||||||
* `channels nodeId`. The content of the objects
|
* `channels nodeId`. The content of the objects
|
||||||
@ -82,14 +112,23 @@ case class OpenChannelInfo(
|
|||||||
state: ChannelState.NORMAL.type
|
state: ChannelState.NORMAL.type
|
||||||
) extends ChannelInfo
|
) extends ChannelInfo
|
||||||
|
|
||||||
|
case class ActivatedFeature(feature: Feature, support: FeatureSupport)
|
||||||
|
|
||||||
|
case class UnknownFeature(bitIndex: Int)
|
||||||
|
|
||||||
|
case class Features(
|
||||||
|
activated: Set[ActivatedFeature],
|
||||||
|
unknown: Set[UnknownFeature])
|
||||||
|
|
||||||
case class NodeInfo(
|
case class NodeInfo(
|
||||||
signature: ECDigitalSignature,
|
signature: ECDigitalSignature,
|
||||||
features: String,
|
features: Features,
|
||||||
timestamp: Instant,
|
timestamp: Instant,
|
||||||
nodeId: NodeId,
|
nodeId: NodeId,
|
||||||
rgbColor: String,
|
rgbColor: String,
|
||||||
alias: String,
|
alias: String,
|
||||||
addresses: Vector[InetSocketAddress])
|
addresses: Vector[InetSocketAddress],
|
||||||
|
unknownFields: String)
|
||||||
|
|
||||||
case class ChannelDesc(shortChannelId: ShortChannelId, a: NodeId, b: NodeId)
|
case class ChannelDesc(shortChannelId: ShortChannelId, a: NodeId, b: NodeId)
|
||||||
|
|
||||||
@ -110,12 +149,31 @@ case class NetworkFeesResult(
|
|||||||
|
|
||||||
case class ChannelStats(
|
case class ChannelStats(
|
||||||
channelId: FundedChannelId,
|
channelId: FundedChannelId,
|
||||||
|
direction: ChannelStats.Direction,
|
||||||
avgPaymentAmount: Satoshis,
|
avgPaymentAmount: Satoshis,
|
||||||
paymentCount: Long,
|
paymentCount: Long,
|
||||||
relayFee: Satoshis,
|
relayFee: Satoshis,
|
||||||
networkFee: Satoshis
|
networkFee: Satoshis
|
||||||
)
|
)
|
||||||
|
|
||||||
|
object ChannelStats {
|
||||||
|
sealed trait Direction
|
||||||
|
case object In extends Direction
|
||||||
|
case object Out extends Direction
|
||||||
|
|
||||||
|
object Direction {
|
||||||
|
|
||||||
|
def fromString(s: String): Direction =
|
||||||
|
if (s.toUpperCase == "IN") {
|
||||||
|
ChannelStats.In
|
||||||
|
} else if (s.toUpperCase == "OUT") {
|
||||||
|
ChannelStats.Out
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException(s"Unknown payment direction: `$s`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case class UsableBalancesResult(
|
case class UsableBalancesResult(
|
||||||
remoteNodeId: NodeId,
|
remoteNodeId: NodeId,
|
||||||
shortChannelId: ShortChannelId,
|
shortChannelId: ShortChannelId,
|
||||||
@ -362,3 +420,14 @@ object WebSocketEvent {
|
|||||||
) extends WebSocketEvent
|
) extends WebSocketEvent
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case class OnChainBalance(confirmed: Satoshis, unconfirmed: Satoshis)
|
||||||
|
|
||||||
|
case class WalletTransaction(
|
||||||
|
address: String,
|
||||||
|
amount: Satoshis,
|
||||||
|
fees: Satoshis,
|
||||||
|
blockHash: DoubleSha256DigestBE,
|
||||||
|
confirmations: Long,
|
||||||
|
txid: DoubleSha256DigestBE,
|
||||||
|
timestamp: Long)
|
||||||
|
@ -19,7 +19,7 @@ import org.bitcoins.core.protocol.ln._
|
|||||||
import org.bitcoins.core.protocol.ln.channel._
|
import org.bitcoins.core.protocol.ln.channel._
|
||||||
import org.bitcoins.core.protocol.ln.currency._
|
import org.bitcoins.core.protocol.ln.currency._
|
||||||
import org.bitcoins.core.protocol.ln.fee.FeeProportionalMillionths
|
import org.bitcoins.core.protocol.ln.fee.FeeProportionalMillionths
|
||||||
import org.bitcoins.core.protocol.ln.node.NodeId
|
import org.bitcoins.core.protocol.ln.node.{Feature, FeatureSupport, NodeId}
|
||||||
import org.bitcoins.core.protocol.script.{
|
import org.bitcoins.core.protocol.script.{
|
||||||
ScriptPubKey,
|
ScriptPubKey,
|
||||||
ScriptSignature,
|
ScriptSignature,
|
||||||
@ -36,16 +36,7 @@ import org.bitcoins.core.protocol.{
|
|||||||
import org.bitcoins.core.script.ScriptType
|
import org.bitcoins.core.script.ScriptType
|
||||||
import org.bitcoins.core.script.crypto.HashType
|
import org.bitcoins.core.script.crypto.HashType
|
||||||
import org.bitcoins.core.wallet.fee.{BitcoinFeeUnit, SatoshisPerByte}
|
import org.bitcoins.core.wallet.fee.{BitcoinFeeUnit, SatoshisPerByte}
|
||||||
import org.bitcoins.crypto.{
|
import org.bitcoins.crypto._
|
||||||
DoubleSha256Digest,
|
|
||||||
DoubleSha256DigestBE,
|
|
||||||
ECDigitalSignature,
|
|
||||||
ECPublicKey,
|
|
||||||
RipeMd160Digest,
|
|
||||||
RipeMd160DigestBE,
|
|
||||||
Sha256Digest,
|
|
||||||
Sha256Hash160Digest
|
|
||||||
}
|
|
||||||
import play.api.libs.json._
|
import play.api.libs.json._
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
@ -257,6 +248,13 @@ object JsonReaders {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
implicit object BitcoinNetworkReads extends Reads[BitcoinNetwork] {
|
||||||
|
|
||||||
|
override def reads(json: JsValue): JsResult[BitcoinNetwork] =
|
||||||
|
SerializerUtil.processJsString(BitcoinNetworks.fromString)(json)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Errors for Unit return types are caught in RpcClient::checkUnit
|
// Errors for Unit return types are caught in RpcClient::checkUnit
|
||||||
implicit object UnitReads extends Reads[Unit] {
|
implicit object UnitReads extends Reads[Unit] {
|
||||||
override def reads(json: JsValue): JsResult[Unit] = JsSuccess(())
|
override def reads(json: JsValue): JsResult[Unit] = JsSuccess(())
|
||||||
@ -718,6 +716,41 @@ object JsonReaders {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
implicit val featureSupportReads: Reads[FeatureSupport] =
|
||||||
|
Reads { jsValue =>
|
||||||
|
SerializerUtil.processJsString {
|
||||||
|
case "mandatory" => FeatureSupport.Mandatory
|
||||||
|
case "optional" => FeatureSupport.Optional
|
||||||
|
case err: String =>
|
||||||
|
throw new RuntimeException(s"Invalid feature support value: `$err`")
|
||||||
|
}(jsValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy val featuresByName: Map[String, Feature] =
|
||||||
|
Feature.knownFeatures.map(f => (f.rfcName, f)).toMap
|
||||||
|
|
||||||
|
implicit val featureReads: Reads[Feature] =
|
||||||
|
Reads { jsValue =>
|
||||||
|
SerializerUtil.processJsString(featuresByName)(jsValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit val unknownFeatureReads: Reads[UnknownFeature] =
|
||||||
|
Reads { jsValue =>
|
||||||
|
SerializerUtil.processJsString(s => UnknownFeature(s.toInt))(jsValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit val activatedFeatureReads: Reads[ActivatedFeature] =
|
||||||
|
Reads { jsValue =>
|
||||||
|
for {
|
||||||
|
feature <- (jsValue \ "name").validate[Feature]
|
||||||
|
support <- (jsValue \ "support").validate[FeatureSupport]
|
||||||
|
} yield ActivatedFeature(feature, support)
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit val featuresReads: Reads[Features] = {
|
||||||
|
Json.reads[Features]
|
||||||
|
}
|
||||||
|
|
||||||
implicit val getInfoResultReads: Reads[GetInfoResult] = {
|
implicit val getInfoResultReads: Reads[GetInfoResult] = {
|
||||||
Json.reads[GetInfoResult]
|
Json.reads[GetInfoResult]
|
||||||
}
|
}
|
||||||
@ -737,20 +770,22 @@ object JsonReaders {
|
|||||||
Reads { jsValue =>
|
Reads { jsValue =>
|
||||||
for {
|
for {
|
||||||
signature <- (jsValue \ "signature").validate[ECDigitalSignature]
|
signature <- (jsValue \ "signature").validate[ECDigitalSignature]
|
||||||
features <- (jsValue \ "features").validate[String]
|
features <- (jsValue \ "features").validate[Features]
|
||||||
timestamp <- (jsValue \ "timestamp")
|
timestamp <- (jsValue \ "timestamp")
|
||||||
.validate[Instant](instantReadsSeconds)
|
.validate[Instant](instantReadsSeconds)
|
||||||
nodeId <- (jsValue \ "nodeId").validate[NodeId]
|
nodeId <- (jsValue \ "nodeId").validate[NodeId]
|
||||||
rgbColor <- (jsValue \ "rgbColor").validate[String]
|
rgbColor <- (jsValue \ "rgbColor").validate[String]
|
||||||
alias <- (jsValue \ "alias").validate[String]
|
alias <- (jsValue \ "alias").validate[String]
|
||||||
addresses <- (jsValue \ "addresses").validate[Vector[InetSocketAddress]]
|
addresses <- (jsValue \ "addresses").validate[Vector[InetSocketAddress]]
|
||||||
|
unknownFields <- (jsValue \ "unknownFields").validate[String]
|
||||||
} yield NodeInfo(signature,
|
} yield NodeInfo(signature,
|
||||||
features,
|
features,
|
||||||
timestamp,
|
timestamp,
|
||||||
nodeId,
|
nodeId,
|
||||||
rgbColor,
|
rgbColor,
|
||||||
alias,
|
alias,
|
||||||
addresses)
|
addresses,
|
||||||
|
unknownFields)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -843,6 +878,25 @@ object JsonReaders {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
implicit val channelCommandResultStateReads: Reads[
|
||||||
|
ChannelCommandResult.State] = Reads { jsValue =>
|
||||||
|
SerializerUtil.processJsString(ChannelCommandResult.fromString)(jsValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit val channelCommandResultReads: Reads[ChannelCommandResult] = Reads {
|
||||||
|
case obj: JsObject =>
|
||||||
|
JsSuccess(ChannelCommandResult(obj.value.map { x =>
|
||||||
|
val channelId = Try(FundedChannelId.fromHex(x._1)) match {
|
||||||
|
case Success(id) => Right(id)
|
||||||
|
case Failure(_) => Left(ShortChannelId.fromHumanReadableString(x._1))
|
||||||
|
}
|
||||||
|
(channelId, x._2.validate[ChannelCommandResult.State].get)
|
||||||
|
}))
|
||||||
|
case err @ (JsNull | _: JsBoolean | _: JsString | _: JsArray |
|
||||||
|
_: JsNumber) =>
|
||||||
|
SerializerUtil.buildJsErrorMsg("jsobject", err)
|
||||||
|
}
|
||||||
|
|
||||||
implicit val channelUpdateReads: Reads[ChannelUpdate] = {
|
implicit val channelUpdateReads: Reads[ChannelUpdate] = {
|
||||||
Reads { jsValue =>
|
Reads { jsValue =>
|
||||||
for {
|
for {
|
||||||
@ -1129,6 +1183,11 @@ object JsonReaders {
|
|||||||
timestamp)
|
timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
implicit val channelStatsDirectionReads: Reads[ChannelStats.Direction] =
|
||||||
|
Reads { json =>
|
||||||
|
SerializerUtil.processJsString(ChannelStats.Direction.fromString)(json)
|
||||||
|
}
|
||||||
|
|
||||||
implicit val channelStatsReads: Reads[ChannelStats] =
|
implicit val channelStatsReads: Reads[ChannelStats] =
|
||||||
Json.reads[ChannelStats]
|
Json.reads[ChannelStats]
|
||||||
|
|
||||||
@ -1243,4 +1302,10 @@ object JsonReaders {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
implicit val onChainBalanceReads: Reads[OnChainBalance] =
|
||||||
|
Json.reads[OnChainBalance]
|
||||||
|
|
||||||
|
implicit val walletTransactionReads: Reads[WalletTransaction] =
|
||||||
|
Json.reads[WalletTransaction]
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,95 @@
|
|||||||
|
package org.bitcoins.core.protocol.ln.node
|
||||||
|
|
||||||
|
sealed trait FeatureSupport
|
||||||
|
|
||||||
|
object FeatureSupport {
|
||||||
|
|
||||||
|
case object Mandatory extends FeatureSupport {
|
||||||
|
override def toString: String = "mandatory"
|
||||||
|
}
|
||||||
|
|
||||||
|
case object Optional extends FeatureSupport {
|
||||||
|
override def toString: String = "optional"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed trait Feature {
|
||||||
|
def rfcName: String
|
||||||
|
def mandatory: Int
|
||||||
|
def optional: Int = mandatory + 1
|
||||||
|
|
||||||
|
def supportBit(support: FeatureSupport): Int =
|
||||||
|
support match {
|
||||||
|
case FeatureSupport.Mandatory => mandatory
|
||||||
|
case FeatureSupport.Optional => optional
|
||||||
|
}
|
||||||
|
|
||||||
|
override def toString = rfcName
|
||||||
|
}
|
||||||
|
|
||||||
|
object Feature {
|
||||||
|
|
||||||
|
case object OptionDataLossProtect extends Feature {
|
||||||
|
val rfcName = "option_data_loss_protect"
|
||||||
|
val mandatory = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
case object InitialRoutingSync extends Feature {
|
||||||
|
val rfcName = "initial_routing_sync"
|
||||||
|
// reserved but not used as per lightningnetwork/lightning-rfc/pull/178
|
||||||
|
val mandatory = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
case object ChannelRangeQueries extends Feature {
|
||||||
|
val rfcName = "gossip_queries"
|
||||||
|
val mandatory = 6
|
||||||
|
}
|
||||||
|
|
||||||
|
case object VariableLengthOnion extends Feature {
|
||||||
|
val rfcName = "var_onion_optin"
|
||||||
|
val mandatory = 8
|
||||||
|
}
|
||||||
|
|
||||||
|
case object ChannelRangeQueriesExtended extends Feature {
|
||||||
|
val rfcName = "gossip_queries_ex"
|
||||||
|
val mandatory = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
case object StaticRemoteKey extends Feature {
|
||||||
|
val rfcName = "option_static_remotekey"
|
||||||
|
val mandatory = 12
|
||||||
|
}
|
||||||
|
|
||||||
|
case object PaymentSecret extends Feature {
|
||||||
|
val rfcName = "payment_secret"
|
||||||
|
val mandatory = 14
|
||||||
|
}
|
||||||
|
|
||||||
|
case object BasicMultiPartPayment extends Feature {
|
||||||
|
val rfcName = "basic_mpp"
|
||||||
|
val mandatory = 16
|
||||||
|
}
|
||||||
|
|
||||||
|
case object Wumbo extends Feature {
|
||||||
|
val rfcName = "option_support_large_channel"
|
||||||
|
val mandatory = 18
|
||||||
|
}
|
||||||
|
|
||||||
|
case object TrampolinePayment extends Feature {
|
||||||
|
val rfcName = "trampoline_payment"
|
||||||
|
val mandatory = 50
|
||||||
|
}
|
||||||
|
|
||||||
|
val knownFeatures: Set[Feature] = Set(
|
||||||
|
OptionDataLossProtect,
|
||||||
|
InitialRoutingSync,
|
||||||
|
ChannelRangeQueries,
|
||||||
|
VariableLengthOnion,
|
||||||
|
ChannelRangeQueriesExtended,
|
||||||
|
PaymentSecret,
|
||||||
|
BasicMultiPartPayment,
|
||||||
|
Wumbo,
|
||||||
|
TrampolinePayment,
|
||||||
|
StaticRemoteKey
|
||||||
|
)
|
||||||
|
}
|
@ -3,16 +3,9 @@ package org.bitcoins.eclair.rpc
|
|||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
import org.bitcoins.commons.jsonmodels.eclair.{
|
import org.bitcoins.commons.jsonmodels.eclair._
|
||||||
ChannelResult,
|
import org.bitcoins.core.config.RegTest
|
||||||
ChannelUpdate,
|
import org.bitcoins.core.currency.{CurrencyUnits, Satoshis}
|
||||||
IncomingPaymentStatus,
|
|
||||||
InvoiceResult,
|
|
||||||
OpenChannelInfo,
|
|
||||||
OutgoingPaymentStatus,
|
|
||||||
WebSocketEvent
|
|
||||||
}
|
|
||||||
import org.bitcoins.core.currency.{CurrencyUnit, CurrencyUnits, Satoshis}
|
|
||||||
import org.bitcoins.core.number.UInt64
|
import org.bitcoins.core.number.UInt64
|
||||||
import org.bitcoins.core.protocol.BitcoinAddress
|
import org.bitcoins.core.protocol.BitcoinAddress
|
||||||
import org.bitcoins.core.protocol.ln.LnParams.LnBitcoinRegTest
|
import org.bitcoins.core.protocol.ln.LnParams.LnBitcoinRegTest
|
||||||
@ -149,6 +142,23 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
|
|||||||
executeWithClientOtherClient(f)
|
executeWithClientOtherClient(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
it should "perform on-chain operations" in {
|
||||||
|
for {
|
||||||
|
c <- clientF
|
||||||
|
address <- c.getNewAddress()
|
||||||
|
balance <- c.onChainBalance()
|
||||||
|
txid <- c.sendOnChain(address, Satoshis(5000), 1)
|
||||||
|
balance1 <- c.onChainBalance()
|
||||||
|
transactions <- c.onChainTransactions()
|
||||||
|
} yield {
|
||||||
|
assert(balance.confirmed > Satoshis(0))
|
||||||
|
assert(balance.unconfirmed == Satoshis(0))
|
||||||
|
// we sent 5000 sats to ourselves and paid some sats in fee
|
||||||
|
assert(balance1.confirmed < balance.confirmed)
|
||||||
|
assert(transactions.exists(_.txid == txid))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Please keep this test the very first. All other tests rely on the propagated gossip messages.
|
* Please keep this test the very first. All other tests rely on the propagated gossip messages.
|
||||||
*/
|
*/
|
||||||
@ -1189,15 +1199,6 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
it should "get new address" in {
|
|
||||||
for {
|
|
||||||
c <- clientF
|
|
||||||
res <- c.getNewAddress()
|
|
||||||
} yield {
|
|
||||||
assert(res.toString.nonEmpty)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
it should "disconnect node" in {
|
it should "disconnect node" in {
|
||||||
for {
|
for {
|
||||||
c1 <- clientF
|
c1 <- clientF
|
||||||
@ -1205,6 +1206,8 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
|
|||||||
nodeInfo2 <- c2.getInfo
|
nodeInfo2 <- c2.getInfo
|
||||||
_ <- c1.disconnect(nodeInfo2.nodeId)
|
_ <- c1.disconnect(nodeInfo2.nodeId)
|
||||||
} yield {
|
} yield {
|
||||||
|
assert(nodeInfo2.features.activated.nonEmpty)
|
||||||
|
assert(nodeInfo2.network == RegTest)
|
||||||
succeed
|
succeed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,8 +21,8 @@ TaskKeys.downloadEclair := {
|
|||||||
Files.createDirectories(binaryDir)
|
Files.createDirectories(binaryDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
val version = "0.4"
|
val version = "0.4.1"
|
||||||
val commit = "69c538e"
|
val commit = "e5fb281"
|
||||||
|
|
||||||
logger.debug(s"(Maybe) downloading Eclair binaries for version: $version")
|
logger.debug(s"(Maybe) downloading Eclair binaries for version: $version")
|
||||||
|
|
||||||
@ -49,36 +49,6 @@ TaskKeys.downloadEclair := {
|
|||||||
logger.info(s"Deleting archive")
|
logger.info(s"Deleting archive")
|
||||||
Files.delete(archiveLocation)
|
Files.delete(archiveLocation)
|
||||||
|
|
||||||
fixShebang(
|
|
||||||
versionDir resolve s"eclair-node-$version-$commit" resolve "bin" resolve "eclair-node.sh")
|
|
||||||
|
|
||||||
logger.info(s"Download complete")
|
logger.info(s"Download complete")
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove me when https://github.com/ACINQ/eclair/issues/1421
|
|
||||||
// and https://github.com/ACINQ/eclair/issues/1422 are fixed
|
|
||||||
def fixShebang(scriptPath: Path): Unit = {
|
|
||||||
import java.nio.file.attribute.PosixFilePermissions
|
|
||||||
import scala.io.Source
|
|
||||||
import scala.collection.JavaConverters._
|
|
||||||
|
|
||||||
val tempPath = scriptPath.getParent resolve scriptPath.getFileName.toString + ".tmp"
|
|
||||||
Files.createFile(tempPath,
|
|
||||||
PosixFilePermissions.asFileAttribute(
|
|
||||||
PosixFilePermissions.fromString("rwxr-xr-x")))
|
|
||||||
val source = Source
|
|
||||||
.fromFile(scriptPath.toUri)
|
|
||||||
|
|
||||||
val lines = (Vector("#!/usr/bin/env bash") ++ source.getLines()).map(
|
|
||||||
line =>
|
|
||||||
if (line == "declare -r lib_dir=\"$(realpath \"${app_home::-4}/lib\")\" # {app_home::-4} transforms ../bin in ../")
|
|
||||||
"declare -r lib_dir=\"$(realpath \"${app_home:0:${#app_home}-4}/lib\")\" # {app_home:0:${#app_home}-4} transforms ../bin in ../"
|
|
||||||
else line)
|
|
||||||
|
|
||||||
source.close()
|
|
||||||
|
|
||||||
Files.write(tempPath, lines.asJava, StandardOpenOption.WRITE)
|
|
||||||
|
|
||||||
tempPath.toFile.renameTo(scriptPath.toFile)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -3,25 +3,7 @@ package org.bitcoins.eclair.rpc.api
|
|||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
import org.bitcoins.commons.jsonmodels.eclair.{
|
import org.bitcoins.commons.jsonmodels.eclair._
|
||||||
AuditResult,
|
|
||||||
ChannelDesc,
|
|
||||||
ChannelInfo,
|
|
||||||
ChannelResult,
|
|
||||||
ChannelStats,
|
|
||||||
ChannelUpdate,
|
|
||||||
GetInfoResult,
|
|
||||||
IncomingPayment,
|
|
||||||
InvoiceResult,
|
|
||||||
NetworkFeesResult,
|
|
||||||
NodeInfo,
|
|
||||||
OutgoingPayment,
|
|
||||||
PaymentId,
|
|
||||||
PeerInfo,
|
|
||||||
SendToRouteResult,
|
|
||||||
UsableBalancesResult,
|
|
||||||
WebSocketEvent
|
|
||||||
}
|
|
||||||
import org.bitcoins.core.currency.{CurrencyUnit, Satoshis}
|
import org.bitcoins.core.currency.{CurrencyUnit, Satoshis}
|
||||||
import org.bitcoins.core.protocol.ln.channel.{ChannelId, FundedChannelId}
|
import org.bitcoins.core.protocol.ln.channel.{ChannelId, FundedChannelId}
|
||||||
import org.bitcoins.core.protocol.ln.currency.MilliSatoshis
|
import org.bitcoins.core.protocol.ln.currency.MilliSatoshis
|
||||||
@ -35,7 +17,7 @@ import org.bitcoins.core.protocol.ln.{
|
|||||||
import org.bitcoins.core.protocol.script.ScriptPubKey
|
import org.bitcoins.core.protocol.script.ScriptPubKey
|
||||||
import org.bitcoins.core.protocol.{Address, BitcoinAddress}
|
import org.bitcoins.core.protocol.{Address, BitcoinAddress}
|
||||||
import org.bitcoins.core.wallet.fee.SatoshisPerByte
|
import org.bitcoins.core.wallet.fee.SatoshisPerByte
|
||||||
import org.bitcoins.crypto.Sha256Digest
|
import org.bitcoins.crypto.{DoubleSha256DigestBE, Sha256Digest}
|
||||||
import org.bitcoins.eclair.rpc.network.NodeUri
|
import org.bitcoins.eclair.rpc.network.NodeUri
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
@ -93,7 +75,7 @@ trait EclairApi {
|
|||||||
|
|
||||||
def disconnect(nodeId: NodeId): Future[Unit]
|
def disconnect(nodeId: NodeId): Future[Unit]
|
||||||
|
|
||||||
def close(id: ChannelId, spk: ScriptPubKey): Future[Unit]
|
def close(id: ChannelId, spk: ScriptPubKey): Future[ChannelCommandResult]
|
||||||
|
|
||||||
def findRoute(
|
def findRoute(
|
||||||
nodeId: NodeId,
|
nodeId: NodeId,
|
||||||
@ -105,9 +87,9 @@ trait EclairApi {
|
|||||||
invoice: LnInvoice,
|
invoice: LnInvoice,
|
||||||
amountMsat: MilliSatoshis): Future[Vector[NodeId]]
|
amountMsat: MilliSatoshis): Future[Vector[NodeId]]
|
||||||
|
|
||||||
def forceClose(channelId: ChannelId): Future[Unit]
|
def forceClose(channelId: ChannelId): Future[ChannelCommandResult]
|
||||||
|
|
||||||
def forceClose(shortChannelId: ShortChannelId): Future[Unit]
|
def forceClose(shortChannelId: ShortChannelId): Future[ChannelCommandResult]
|
||||||
|
|
||||||
def getInfo: Future[GetInfoResult]
|
def getInfo: Future[GetInfoResult]
|
||||||
|
|
||||||
@ -120,13 +102,13 @@ trait EclairApi {
|
|||||||
def updateRelayFee(
|
def updateRelayFee(
|
||||||
channelId: ChannelId,
|
channelId: ChannelId,
|
||||||
feeBaseMsat: MilliSatoshis,
|
feeBaseMsat: MilliSatoshis,
|
||||||
feePropertionalMillionths: Long): Future[Unit]
|
feePropertionalMillionths: Long): Future[ChannelCommandResult]
|
||||||
|
|
||||||
def updateRelayFee(
|
def updateRelayFee(
|
||||||
shortChannelId: ShortChannelId,
|
shortChannelId: ShortChannelId,
|
||||||
feeBaseMsat: MilliSatoshis,
|
feeBaseMsat: MilliSatoshis,
|
||||||
feePropertionalMillionths: Long
|
feePropertionalMillionths: Long
|
||||||
): Future[Unit]
|
): Future[ChannelCommandResult]
|
||||||
|
|
||||||
def open(
|
def open(
|
||||||
nodeId: NodeId,
|
nodeId: NodeId,
|
||||||
@ -296,4 +278,13 @@ trait EclairApi {
|
|||||||
def connectToWebSocket(eventHandler: WebSocketEvent => Unit): Future[Unit]
|
def connectToWebSocket(eventHandler: WebSocketEvent => Unit): Future[Unit]
|
||||||
|
|
||||||
def getNewAddress(): Future[BitcoinAddress]
|
def getNewAddress(): Future[BitcoinAddress]
|
||||||
|
|
||||||
|
def onChainBalance(): Future[OnChainBalance]
|
||||||
|
|
||||||
|
def onChainTransactions(): Future[Vector[WalletTransaction]]
|
||||||
|
|
||||||
|
def sendOnChain(
|
||||||
|
address: BitcoinAddress,
|
||||||
|
amount: Satoshis,
|
||||||
|
confirmationTarget: Int): Future[DoubleSha256DigestBE]
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ import org.bitcoins.core.protocol.script.ScriptPubKey
|
|||||||
import org.bitcoins.core.protocol.{Address, BitcoinAddress}
|
import org.bitcoins.core.protocol.{Address, BitcoinAddress}
|
||||||
import org.bitcoins.core.util.{BytesUtil, FutureUtil, StartStop}
|
import org.bitcoins.core.util.{BytesUtil, FutureUtil, StartStop}
|
||||||
import org.bitcoins.core.wallet.fee.SatoshisPerByte
|
import org.bitcoins.core.wallet.fee.SatoshisPerByte
|
||||||
import org.bitcoins.crypto.Sha256Digest
|
import org.bitcoins.crypto.{DoubleSha256DigestBE, Sha256Digest}
|
||||||
import org.bitcoins.eclair.rpc.api._
|
import org.bitcoins.eclair.rpc.api._
|
||||||
import org.bitcoins.eclair.rpc.config.EclairInstance
|
import org.bitcoins.eclair.rpc.config.EclairInstance
|
||||||
import org.bitcoins.eclair.rpc.network.NodeUri
|
import org.bitcoins.eclair.rpc.network.NodeUri
|
||||||
@ -65,7 +65,7 @@ class EclairRpcClient(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override def allNodes(): Future[Vector[NodeInfo]] = {
|
override def allNodes(): Future[Vector[NodeInfo]] = {
|
||||||
eclairCall[Vector[NodeInfo]]("allnodes")
|
eclairCall[Vector[NodeInfo]]("nodes")
|
||||||
}
|
}
|
||||||
|
|
||||||
override def allUpdates(): Future[Vector[ChannelUpdate]] =
|
override def allUpdates(): Future[Vector[ChannelUpdate]] =
|
||||||
@ -108,22 +108,22 @@ class EclairRpcClient(
|
|||||||
private def close(
|
private def close(
|
||||||
channelId: ChannelId,
|
channelId: ChannelId,
|
||||||
shortChannelId: Option[ShortChannelId],
|
shortChannelId: Option[ShortChannelId],
|
||||||
scriptPubKey: Option[ScriptPubKey]): Future[Unit] = {
|
scriptPubKey: Option[ScriptPubKey]): Future[ChannelCommandResult] = {
|
||||||
val params =
|
val params =
|
||||||
Seq("channelId" -> channelId.hex) ++ Seq(
|
Seq("channelId" -> channelId.hex) ++ Seq(
|
||||||
shortChannelId.map(x => "shortChannelId" -> x.toString),
|
shortChannelId.map(x => "shortChannelId" -> x.toString),
|
||||||
scriptPubKey.map(x => "scriptPubKey" -> BytesUtil.encodeHex(x.asmBytes))
|
scriptPubKey.map(x => "scriptPubKey" -> BytesUtil.encodeHex(x.asmBytes))
|
||||||
).flatten
|
).flatten
|
||||||
|
|
||||||
eclairCall[String]("close", params: _*).map(_ => ())
|
eclairCall[ChannelCommandResult]("close", params: _*)
|
||||||
}
|
}
|
||||||
|
|
||||||
def close(channelId: ChannelId): Future[Unit] =
|
def close(channelId: ChannelId): Future[ChannelCommandResult] =
|
||||||
close(channelId, scriptPubKey = None, shortChannelId = None)
|
close(channelId, scriptPubKey = None, shortChannelId = None)
|
||||||
|
|
||||||
override def close(
|
override def close(
|
||||||
channelId: ChannelId,
|
channelId: ChannelId,
|
||||||
scriptPubKey: ScriptPubKey): Future[Unit] = {
|
scriptPubKey: ScriptPubKey): Future[ChannelCommandResult] = {
|
||||||
close(channelId, scriptPubKey = Some(scriptPubKey), shortChannelId = None)
|
close(channelId, scriptPubKey = Some(scriptPubKey), shortChannelId = None)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,13 +173,16 @@ class EclairRpcClient(
|
|||||||
eclairCall[Vector[NodeId]]("findroute", params: _*)
|
eclairCall[Vector[NodeId]]("findroute", params: _*)
|
||||||
}
|
}
|
||||||
|
|
||||||
override def forceClose(channelId: ChannelId): Future[Unit] = {
|
override def forceClose(
|
||||||
eclairCall[String]("forceclose", "channelId" -> channelId.hex).map(_ => ())
|
channelId: ChannelId): Future[ChannelCommandResult] = {
|
||||||
|
eclairCall[ChannelCommandResult]("forceclose", "channelId" -> channelId.hex)
|
||||||
}
|
}
|
||||||
|
|
||||||
override def forceClose(shortChannelId: ShortChannelId): Future[Unit] = {
|
override def forceClose(
|
||||||
eclairCall[String]("forceclose",
|
shortChannelId: ShortChannelId): Future[ChannelCommandResult] = {
|
||||||
"shortChannelId" -> shortChannelId.toString).map(_ => ())
|
eclairCall[ChannelCommandResult](
|
||||||
|
"forceclose",
|
||||||
|
"shortChannelId" -> shortChannelId.toString)
|
||||||
}
|
}
|
||||||
|
|
||||||
override def getInfo: Future[GetInfoResult] = {
|
override def getInfo: Future[GetInfoResult] = {
|
||||||
@ -530,8 +533,8 @@ class EclairRpcClient(
|
|||||||
override def updateRelayFee(
|
override def updateRelayFee(
|
||||||
channelId: ChannelId,
|
channelId: ChannelId,
|
||||||
feeBaseMsat: MilliSatoshis,
|
feeBaseMsat: MilliSatoshis,
|
||||||
feeProportionalMillionths: Long): Future[Unit] = {
|
feeProportionalMillionths: Long): Future[ChannelCommandResult] = {
|
||||||
eclairCall[Unit](
|
eclairCall[ChannelCommandResult](
|
||||||
"updaterelayfee",
|
"updaterelayfee",
|
||||||
"channelId" -> channelId.hex,
|
"channelId" -> channelId.hex,
|
||||||
"feeBaseMsat" -> feeBaseMsat.toLong.toString,
|
"feeBaseMsat" -> feeBaseMsat.toLong.toString,
|
||||||
@ -542,8 +545,8 @@ class EclairRpcClient(
|
|||||||
override def updateRelayFee(
|
override def updateRelayFee(
|
||||||
shortChannelId: ShortChannelId,
|
shortChannelId: ShortChannelId,
|
||||||
feeBaseMsat: MilliSatoshis,
|
feeBaseMsat: MilliSatoshis,
|
||||||
feeProportionalMillionths: Long): Future[Unit] = {
|
feeProportionalMillionths: Long): Future[ChannelCommandResult] = {
|
||||||
eclairCall[Unit](
|
eclairCall[ChannelCommandResult](
|
||||||
"updaterelayfee",
|
"updaterelayfee",
|
||||||
"shortChannelId" -> shortChannelId.toHumanReadableString,
|
"shortChannelId" -> shortChannelId.toHumanReadableString,
|
||||||
"feeBaseMsat" -> feeBaseMsat.toLong.toString,
|
"feeBaseMsat" -> feeBaseMsat.toLong.toString,
|
||||||
@ -609,6 +612,25 @@ class EclairRpcClient(
|
|||||||
eclairCall[BitcoinAddress]("getnewaddress")
|
eclairCall[BitcoinAddress]("getnewaddress")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override def onChainBalance(): Future[OnChainBalance] = {
|
||||||
|
eclairCall[OnChainBalance]("onchainbalance")
|
||||||
|
}
|
||||||
|
|
||||||
|
override def onChainTransactions(): Future[Vector[WalletTransaction]] = {
|
||||||
|
eclairCall[Vector[WalletTransaction]]("onchaintransactions")
|
||||||
|
}
|
||||||
|
|
||||||
|
override def sendOnChain(
|
||||||
|
address: BitcoinAddress,
|
||||||
|
amount: Satoshis,
|
||||||
|
confirmationTarget: Int): Future[DoubleSha256DigestBE] = {
|
||||||
|
eclairCall[DoubleSha256DigestBE](
|
||||||
|
"sendonchain",
|
||||||
|
"address" -> address.toString,
|
||||||
|
"amountSatoshis" -> amount.toLong.toString,
|
||||||
|
"confirmationTarget" -> confirmationTarget.toString)
|
||||||
|
}
|
||||||
|
|
||||||
private def eclairCall[T](command: String, parameters: (String, String)*)(
|
private def eclairCall[T](command: String, parameters: (String, String)*)(
|
||||||
implicit reader: Reads[T]): Future[T] = {
|
implicit reader: Reads[T]): Future[T] = {
|
||||||
val request = buildRequest(getDaemon, command, parameters: _*)
|
val request = buildRequest(getDaemon, command, parameters: _*)
|
||||||
@ -946,8 +968,8 @@ object EclairRpcClient {
|
|||||||
implicit system: ActorSystem) = new EclairRpcClient(instance, binary)
|
implicit system: ActorSystem) = new EclairRpcClient(instance, binary)
|
||||||
|
|
||||||
/** The current commit we support of Eclair */
|
/** The current commit we support of Eclair */
|
||||||
private[bitcoins] val commit = "69c538e"
|
private[bitcoins] val commit = "e5fb281"
|
||||||
|
|
||||||
/** The current version we support of Eclair */
|
/** The current version we support of Eclair */
|
||||||
private[bitcoins] val version = "0.4"
|
private[bitcoins] val version = "0.4.1"
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user