2024 04 29 eclair upgrade v0.9.0 (#5556)

* Rework EclairRpcTestUtil.shutdown() to shutdown eclair/bitcoind separately

* Fix keysend test

* Add new eclair websocket events

* Empty commit to re-run CI

* First bug where we weren't shutting down a bitcoind in eclairRpcTest
This commit is contained in:
Chris Stewart 2024-04-30 07:36:36 -05:00 committed by GitHub
parent 819a047d1e
commit 7ee749adcb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 125 additions and 35 deletions

View File

@ -7,12 +7,14 @@ import org.bitcoins.core.protocol.ln.channel.{
ChannelId,
ChannelState,
FundedChannelId,
ShortChannelId
ShortChannelId,
TempChannelId
}
import org.bitcoins.core.protocol.ln.currency.MilliSatoshis
import org.bitcoins.core.protocol.ln.fee.FeeProportionalMillionths
import org.bitcoins.core.protocol.ln.node.{Feature, FeatureSupport, NodeId}
import org.bitcoins.core.protocol.ln.{LnHumanReadablePart, PaymentPreimage}
import org.bitcoins.core.wallet.fee.SatoshisPerKW
import org.bitcoins.crypto._
import play.api.libs.json.JsObject
@ -459,6 +461,26 @@ object WebSocketEvent {
timestamp: Instant // milliseconds
) extends WebSocketEvent
case class ChannelStateChange(
channelId: FundedChannelId,
remoteNodeId: NodeId,
previousState: ChannelState,
currentState: ChannelState)
extends WebSocketEvent
case class ChannelCreated(
remoteNodeId: NodeId,
isInitiator: Boolean,
temporaryChannelId: TempChannelId,
commitTxFeeratePerKw: SatoshisPerKW,
fundingTxFeeratePerKw: Option[SatoshisPerKW])
extends WebSocketEvent
case class ChannelOpened(remoteNodeId: NodeId, channelId: FundedChannelId)
extends WebSocketEvent
case class ChannelClosed(channelId: FundedChannelId, closingType: String)
extends WebSocketEvent
}
case class OnChainBalance(confirmed: Satoshis, unconfirmed: Satoshis)

View File

@ -7,6 +7,12 @@ import org.bitcoins.commons.jsonmodels.bitcoind.RpcOpts.{
}
import org.bitcoins.commons.jsonmodels.bitcoind._
import org.bitcoins.commons.jsonmodels.clightning.CLightningJsonModels._
import org.bitcoins.commons.jsonmodels.eclair.WebSocketEvent.{
ChannelClosed,
ChannelCreated,
ChannelOpened,
ChannelStateChange
}
import org.bitcoins.commons.jsonmodels.eclair._
import org.bitcoins.commons.serializers.JsonSerializers._
import org.bitcoins.core.config._
@ -51,6 +57,12 @@ import scala.util.{Failure, Success, Try}
object JsonReaders {
implicit val byteVectorReads: Reads[ByteVector] = {
Reads { jsValue =>
SerializerUtil.processJsStringOpt(str => ByteVector.fromHex(str))(jsValue)
}
}
/** Tries to prase the provided JSON into a map with keys of type `K` and
* values of type `V`
*/
@ -733,6 +745,13 @@ object JsonReaders {
SatoshisPerVirtualByte.fromLong(num.toLong))(json)
}
implicit object SatoshisPerKWReads extends Reads[SatoshisPerKW] {
override def reads(json: JsValue): JsResult[SatoshisPerKW] = {
SerializerUtil.processJsNumberBigInt(num =>
SatoshisPerKW(Satoshis(num.toLong)))(json)
}
}
implicit object FileReads extends Reads[File] {
override def reads(json: JsValue): JsResult[File] =
@ -967,6 +986,15 @@ object JsonReaders {
implicit val realChannelIdReads: Reads[RealChannelId] =
Json.reads[RealChannelId]
implicit val tempChannelIdReads: Reads[TempChannelId] =
Reads {
case string: JsString =>
byteVectorReads.reads(string).map(TempChannelId(_))
case x @ (_: JsBoolean | _: JsNumber | _: JsArray | _: JsObject |
JsNull) =>
JsError(s"Invalid json type for tempChannelId, got=$x")
}
implicit val shortIdsReads: Reads[ShortIds] =
Json.reads[ShortIds]
@ -1045,11 +1073,13 @@ object JsonReaders {
.validate[ShortChannelId]
channelId <- (jsValue \ "channelId").validate[FundedChannelId]
state <- (jsValue \ "state").validate[ChannelState.NORMAL.type]
active <- (jsValue \ "data" \ "commitments" \ "active")
.validate[Vector[JsObject]]
remoteMsat <-
(jsValue \ "data" \ "commitments" \ "localCommit" \ "spec" \ "toRemote")
(active.head \ "localCommit" \ "spec" \ "toRemote")
.validate[MilliSatoshis]
localMsat <-
(jsValue \ "data" \ "commitments" \ "localCommit" \ "spec" \ "toLocal")
(active.head \ "localCommit" \ "spec" \ "toLocal")
.validate[MilliSatoshis]
} yield OpenChannelInfo(
@ -1067,11 +1097,13 @@ object JsonReaders {
nodeId <- (jsValue \ "nodeId").validate[NodeId]
channelId <- (jsValue \ "channelId").validate[FundedChannelId]
state <- (jsValue \ "state").validate[ChannelState]
active <- (jsValue \ "data" \ "commitments" \ "active")
.validate[Vector[JsObject]]
remoteMsat <-
(jsValue \ "data" \ "commitments" \ "localCommit" \ "spec" \ "toRemote")
(active.head \ "localCommit" \ "spec" \ "toRemote")
.validate[MilliSatoshis]
localMsat <-
(jsValue \ "data" \ "commitments" \ "localCommit" \ "spec" \ "toLocal")
(active.head \ "localCommit" \ "spec" \ "toLocal")
.validate[MilliSatoshis]
} yield BaseChannelInfo(
@ -1576,6 +1608,22 @@ object JsonReaders {
)
}
implicit val channelStateChangeReads: Reads[ChannelStateChange] = {
Json.reads[ChannelStateChange]
}
implicit val channelCreatedReads: Reads[ChannelCreated] = {
Json.reads[ChannelCreated]
}
implicit val channelClosedReads: Reads[ChannelClosed] = {
Json.reads[ChannelClosed]
}
implicit val channelOpenReads: Reads[ChannelOpened] = {
Json.reads[ChannelOpened]
}
implicit val webSocketEventReads: Reads[WebSocketEvent] =
Reads { js =>
(js \ "type")
@ -1589,6 +1637,14 @@ object JsonReaders {
js.validate[WebSocketEvent.PaymentSent]
case "payment-settling-onchain" =>
js.validate[WebSocketEvent.PaymentSettlingOnchain]
case "channel-state-changed" =>
js.validate[WebSocketEvent.ChannelStateChange]
case "channel-created" =>
js.validate[WebSocketEvent.ChannelCreated]
case "channel-opened" =>
js.validate[WebSocketEvent.ChannelOpened]
case "channel-closed" =>
js.validate[WebSocketEvent.ChannelClosed]
}
}
@ -1608,12 +1664,6 @@ object JsonReaders {
throw Value.InvalidData(js, "Expected value in Satoshis")
}
implicit val byteVectorReads: Reads[ByteVector] = {
Reads { jsValue =>
SerializerUtil.processJsStringOpt(str => ByteVector.fromHex(str))(jsValue)
}
}
implicit val outputStatusReads: Reads[OutputStatus] = {
Reads { jsValue =>
SerializerUtil.processJsStringOpt(OutputStatus.fromStringOpt)(jsValue)

View File

@ -29,6 +29,7 @@ import org.bitcoins.eclair.rpc.config.{
import org.bitcoins.rpc.client.common.BitcoindRpcClient
import org.bitcoins.testkit.async.TestAsyncUtil
import org.bitcoins.testkit.eclair.rpc.{EclairNodes4, EclairRpcTestUtil}
import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil
import org.bitcoins.testkit.util.{BitcoinSAsyncTest, EclairRpcTestClient}
import org.scalatest.Assertion
@ -425,14 +426,17 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
}
it should "be able to start and shutdown a node" in {
val eclairTestClient =
val eclairTestClientF = for {
bitcoind <- bitcoindRpcClientF
} yield {
EclairRpcTestClient.fromSbtDownload(
eclairVersionOpt = None,
eclairCommitOpt = None,
bitcoindRpcClientOpt = None
bitcoindRpcClientOpt = Some(bitcoind)
)
}
for {
eclair <- eclairTestClient.start()
eclair <- eclairTestClientF.flatMap(_.start())
_ <- TestAsyncUtil.retryUntilSatisfiedF(
conditionF = () => eclair.isStarted(),
interval = 1.second,
@ -1326,7 +1330,12 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
}
override def afterAll(): Unit = {
clients.result().foreach(EclairRpcTestUtil.shutdown)
val resultF = for {
_ <- Future.traverse(clients.result())(EclairRpcTestUtil.shutdown)
bitcoind <- bitcoindRpcClientF
_ <- BitcoindRpcTestUtil.stopServer(bitcoind)
} yield ()
val _ = Await.result(resultF, 30.second)
super.afterAll()
}
}

View File

@ -3,8 +3,12 @@ package org.bitcoins.eclair.rpc
import org.apache.pekko.actor.ActorSystem
import org.bitcoins.eclair.rpc.client.EclairRpcClient
import org.bitcoins.testkit.eclair.rpc.EclairRpcTestUtil
import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil
import org.bitcoins.testkit.util.BitcoinSAsyncTest
import scala.concurrent.duration.DurationInt
import scala.concurrent.{Await, Future}
class EclairRpcTestUtilTest extends BitcoinSAsyncTest {
implicit private val actorSystem: ActorSystem =
@ -21,7 +25,12 @@ class EclairRpcTestUtilTest extends BitcoinSAsyncTest {
Vector.newBuilder[EclairRpcClient]
override def afterAll(): Unit = {
clients.result().foreach(EclairRpcTestUtil.shutdown)
val resultF = for {
_ <- Future.traverse(clients.result())(EclairRpcTestUtil.shutdown)
bitcoind <- bitcoindRpcF
_ <- BitcoindRpcTestUtil.stopServer(bitcoind)
} yield ()
val _ = Await.result(resultF, 30.second)
super.afterAll()
}

View File

@ -19,8 +19,8 @@ TaskKeys.downloadEclair := {
Files.createDirectories(binaryDir)
}
val version = "0.8.0"
val commit = "0077471"
val version = "0.9.0"
val commit = "623f7e4"
logger.debug(s"(Maybe) downloading Eclair binaries for version: $version")
@ -48,7 +48,7 @@ TaskKeys.downloadEclair := {
.mkString
val expectedHash =
"d279317de25ba86b275183160d83acd064647371c446a35601397ae87ee04abb"
"249604de45c54dc48f02c7335b49ff2896334fd44541dbb175e56aff66054cdc"
val success = hash.equalsIgnoreCase(expectedHash)
if (success) {

View File

@ -263,9 +263,10 @@ class EclairRpcClient(
// this is unfortunately returned in this format
// created channel 30bdf849eb9f72c9b41a09e38a6d83138c2edf332cb116dd7cf0f0dfb66be395
val call = eclairCall[String]("open", params: _*)
// let's just return the chanId
val chanIdF = call.map(_.split(" ").last)
// format:
// created channel 19e11470b0dd96ed15c56ea8f32e9a3277dcbd570e7392c1c34709adc7ebfdc3 with fundingTxId=c2fdebc7ad0947c3c192730e57bddc77329a2ef3a86ec515ed96ddb07014e119 and fees=24750 sat
val chanIdF = call.map(_.split(" ")(2))
chanIdF.map(FundedChannelId.fromHex)
}
@ -1080,10 +1081,10 @@ object EclairRpcClient {
) = new EclairRpcClient(instance, binary)
/** The current commit we support of Eclair */
private[bitcoins] val commit = "0077471"
private[bitcoins] val commit = "623f7e4"
/** The current version we support of Eclair */
private[bitcoins] val version = "0.8.0"
private[bitcoins] val version = "0.9.0"
/** The bitcoind version that eclair is officially tested & supported with by
* ACINQ

View File

@ -20,7 +20,7 @@
</encoder>
</appender>
<root level="OFF">
<root level="INFO">
<appender-ref ref="FILE"/>
<appender-ref ref="STDOUT"/>
</root>

View File

@ -793,33 +793,29 @@ trait EclairRpcTestUtil extends BitcoinSLogger {
info <- client.getInfo
} yield info.blockHeight == blockCount
/** Shuts down an eclair daemon and the bitcoind daemon it is associated with
*/
/** Shuts down an eclair daemon */
def shutdown(
eclairRpcClient: EclairRpcClient
)(implicit system: ActorSystem): Future[Unit] = {
import system.dispatcher
val bitcoindRpc = getBitcoindRpc(eclairRpcClient)
logger.debug(s"shutting down eclair")
val stopEclairF = eclairRpcClient.stop()
val killBitcoindF = BitcoindRpcTestUtil.stopServer(bitcoindRpc)
val iskilled = eclairRpcClient.isStopped
val shutdownF = for {
_ <- killBitcoindF
_ <- stopEclairF
_ <- iskilled
} yield {
logger.debug(
"Successfully shutdown eclair and it's corresponding bitcoind"
"Successfully shutdown eclair"
)
}
shutdownF.failed.foreach { err: Throwable =>
logger.info(
s"Killed a bitcoind instance, but could not find an eclair process to kill"
logger.error(
s"Could kill eclair process",
err
)
throw err
}
shutdownF
}

View File

@ -352,7 +352,7 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
servers: Vector[BitcoindRpcClient]
)(implicit system: ActorSystem): Future[Unit] = {
implicit val ec: ExecutionContextExecutor = system.getDispatcher
logger.info(s"Shutting down ${servers.length} bitcoinds")
val serverStopsF = Future.traverse(servers) { s =>
val stopF = s.stop()
stopF.onComplete {
@ -366,7 +366,10 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
_ <- removeDataDirectory(s)
} yield ()
}
serverStopsF.map(_ => ())
serverStopsF.map { _ =>
logger.info(s"Done shutting down ${servers.length} bitcoinds")
()
}
}
/** Stops the given server and deletes its data directory