mirror of
https://github.com/ACINQ/eclair.git
synced 2025-02-22 06:21:42 +01:00
Add some channel events to websocket (#1536)
Send basic channel events to websockets listeners: * Channel open initiated * Channel state change * Channel closed We only send basic, high-level data about these events. If the listener is interested in details, it should call the `channelInfo` API to get all of the channel's data. Fixes #1509
This commit is contained in:
parent
483cce4ae2
commit
42481c66e6
3 changed files with 62 additions and 4 deletions
|
@ -25,7 +25,7 @@ import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
|
||||||
import fr.acinq.bitcoin.{ByteVector32, ByteVector64, OutPoint, Satoshi, Transaction}
|
import fr.acinq.bitcoin.{ByteVector32, ByteVector64, OutPoint, Satoshi, Transaction}
|
||||||
import fr.acinq.eclair.ApiTypes.ChannelIdentifier
|
import fr.acinq.eclair.ApiTypes.ChannelIdentifier
|
||||||
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
|
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
|
||||||
import fr.acinq.eclair.channel.{ChannelOpenResponse, ChannelVersion, CloseCommand, Command, CommandResponse, RES_SUCCESS, State}
|
import fr.acinq.eclair.channel._
|
||||||
import fr.acinq.eclair.crypto.ShaChain
|
import fr.acinq.eclair.crypto.ShaChain
|
||||||
import fr.acinq.eclair.db.{IncomingPaymentStatus, OutgoingPaymentStatus}
|
import fr.acinq.eclair.db.{IncomingPaymentStatus, OutgoingPaymentStatus}
|
||||||
import fr.acinq.eclair.payment._
|
import fr.acinq.eclair.payment._
|
||||||
|
@ -42,6 +42,7 @@ import scodec.bits.ByteVector
|
||||||
* JSON Serializers.
|
* JSON Serializers.
|
||||||
* Note: in general, deserialization does not need to be implemented.
|
* Note: in general, deserialization does not need to be implemented.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class ByteVectorSerializer extends CustomSerializer[ByteVector](_ => ( {
|
class ByteVectorSerializer extends CustomSerializer[ByteVector](_ => ( {
|
||||||
null
|
null
|
||||||
}, {
|
}, {
|
||||||
|
@ -278,6 +279,30 @@ class JavaUUIDSerializer extends CustomSerializer[UUID](_ => ( {
|
||||||
case id: UUID => JString(id.toString)
|
case id: UUID => JString(id.toString)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
class ChannelEventSerializer extends CustomSerializer[ChannelEvent](_ => ( {
|
||||||
|
null
|
||||||
|
}, {
|
||||||
|
case e: ChannelCreated => JObject(
|
||||||
|
JField("type", JString("channel-opened")),
|
||||||
|
JField("remoteNodeId", JString(e.remoteNodeId.toString())),
|
||||||
|
JField("isFunder", JBool(e.isFunder)),
|
||||||
|
JField("temporaryChannelId", JString(e.temporaryChannelId.toHex)),
|
||||||
|
JField("initialFeeratePerKw", JLong(e.initialFeeratePerKw.toLong)),
|
||||||
|
JField("fundingTxFeeratePerKw", e.fundingTxFeeratePerKw.map(f => JLong(f.toLong)).getOrElse(JNothing))
|
||||||
|
)
|
||||||
|
case e: ChannelStateChanged => JObject(
|
||||||
|
JField("type", JString("channel-state-changed")),
|
||||||
|
JField("remoteNodeId", JString(e.remoteNodeId.toString())),
|
||||||
|
JField("previousState", JString(e.previousState.toString)),
|
||||||
|
JField("currentState", JString(e.currentState.toString))
|
||||||
|
)
|
||||||
|
case e: ChannelClosed => JObject(
|
||||||
|
JField("type", JString("channel-closed")),
|
||||||
|
JField("channelId", JString(e.channelId.toHex)),
|
||||||
|
JField("closingType", JString(e.closingType.getClass.getSimpleName))
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
|
||||||
case class CustomTypeHints(custom: Map[Class[_], String]) extends TypeHints {
|
case class CustomTypeHints(custom: Map[Class[_], String]) extends TypeHints {
|
||||||
val reverse: Map[String, Class[_]] = custom.map(_.swap)
|
val reverse: Map[String, Class[_]] = custom.map(_.swap)
|
||||||
|
|
||||||
|
@ -321,6 +346,7 @@ object JsonSupport extends Json4sSupport {
|
||||||
new ByteVectorSerializer +
|
new ByteVectorSerializer +
|
||||||
new ByteVector32Serializer +
|
new ByteVector32Serializer +
|
||||||
new ByteVector64Serializer +
|
new ByteVector64Serializer +
|
||||||
|
new ChannelEventSerializer +
|
||||||
new UInt64Serializer +
|
new UInt64Serializer +
|
||||||
new SatoshiSerializer +
|
new SatoshiSerializer +
|
||||||
new MilliSatoshiSerializer +
|
new MilliSatoshiSerializer +
|
||||||
|
@ -360,10 +386,12 @@ object JsonSupport extends Json4sSupport {
|
||||||
JObject(
|
JObject(
|
||||||
JField("name", JString(a.feature.rfcName)),
|
JField("name", JString(a.feature.rfcName)),
|
||||||
JField("support", JString(a.support.toString))
|
JField("support", JString(a.support.toString))
|
||||||
)}.toList)),
|
)
|
||||||
|
}.toList)),
|
||||||
JField("unknown", JArray(features.unknown.map { i =>
|
JField("unknown", JArray(features.unknown.map { i =>
|
||||||
JObject(
|
JObject(
|
||||||
JField("featureBit", JInt(i.bitIndex))
|
JField("featureBit", JInt(i.bitIndex))
|
||||||
)}.toList))
|
)
|
||||||
|
}.toList))
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -35,6 +35,7 @@ import fr.acinq.bitcoin.Crypto.PublicKey
|
||||||
import fr.acinq.bitcoin.{ByteVector32, Satoshi}
|
import fr.acinq.bitcoin.{ByteVector32, Satoshi}
|
||||||
import fr.acinq.eclair.api.FormParamExtractors._
|
import fr.acinq.eclair.api.FormParamExtractors._
|
||||||
import fr.acinq.eclair.blockchain.fee.FeeratePerByte
|
import fr.acinq.eclair.blockchain.fee.FeeratePerByte
|
||||||
|
import fr.acinq.eclair.channel.{ChannelClosed, ChannelCreated, ChannelEvent, ChannelStateChanged, WAIT_FOR_INIT_INTERNAL}
|
||||||
import fr.acinq.eclair.io.NodeURI
|
import fr.acinq.eclair.io.NodeURI
|
||||||
import fr.acinq.eclair.payment.{PaymentEvent, PaymentRequest}
|
import fr.acinq.eclair.payment.{PaymentEvent, PaymentRequest}
|
||||||
import fr.acinq.eclair.{CltvExpiryDelta, Eclair, MilliSatoshi}
|
import fr.acinq.eclair.{CltvExpiryDelta, Eclair, MilliSatoshi}
|
||||||
|
@ -91,10 +92,19 @@ trait Service extends ExtraDirectives with Logging {
|
||||||
|
|
||||||
override def preStart: Unit = {
|
override def preStart: Unit = {
|
||||||
context.system.eventStream.subscribe(self, classOf[PaymentEvent])
|
context.system.eventStream.subscribe(self, classOf[PaymentEvent])
|
||||||
|
context.system.eventStream.subscribe(self, classOf[ChannelCreated])
|
||||||
|
context.system.eventStream.subscribe(self, classOf[ChannelStateChanged])
|
||||||
|
context.system.eventStream.subscribe(self, classOf[ChannelClosed])
|
||||||
}
|
}
|
||||||
|
|
||||||
def receive: Receive = {
|
def receive: Receive = {
|
||||||
case message: PaymentEvent => flowInput.offer(serialization.write(message))
|
case message: PaymentEvent => flowInput.offer(serialization.write(message))
|
||||||
|
case message: ChannelCreated => flowInput.offer(serialization.write(message))
|
||||||
|
case message: ChannelStateChanged =>
|
||||||
|
if (message.previousState != WAIT_FOR_INIT_INTERNAL) {
|
||||||
|
flowInput.offer(serialization.write(message))
|
||||||
|
}
|
||||||
|
case message: ChannelClosed => flowInput.offer(serialization.write(message))
|
||||||
}
|
}
|
||||||
|
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -33,7 +33,9 @@ import fr.acinq.eclair.ApiTypes.ChannelIdentifier
|
||||||
import fr.acinq.eclair.FeatureSupport.{Mandatory, Optional}
|
import fr.acinq.eclair.FeatureSupport.{Mandatory, Optional}
|
||||||
import fr.acinq.eclair.Features.{ChannelRangeQueriesExtended, OptionDataLossProtect}
|
import fr.acinq.eclair.Features.{ChannelRangeQueriesExtended, OptionDataLossProtect}
|
||||||
import fr.acinq.eclair._
|
import fr.acinq.eclair._
|
||||||
import fr.acinq.eclair.channel.{CMD_CLOSE, ChannelOpenResponse, CommandResponse, RES_SUCCESS}
|
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
|
||||||
|
import fr.acinq.eclair.channel.Helpers.Closing
|
||||||
|
import fr.acinq.eclair.channel._
|
||||||
import fr.acinq.eclair.db._
|
import fr.acinq.eclair.db._
|
||||||
import fr.acinq.eclair.io.NodeURI
|
import fr.acinq.eclair.io.NodeURI
|
||||||
import fr.acinq.eclair.io.Peer.PeerInfo
|
import fr.acinq.eclair.io.Peer.PeerInfo
|
||||||
|
@ -530,6 +532,24 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM
|
||||||
assert(serialization.write(pset) === expectedSerializedPset)
|
assert(serialization.write(pset) === expectedSerializedPset)
|
||||||
system.eventStream.publish(pset)
|
system.eventStream.publish(pset)
|
||||||
wsClient.expectMessage(expectedSerializedPset)
|
wsClient.expectMessage(expectedSerializedPset)
|
||||||
|
|
||||||
|
val chcr = ChannelCreated(system.deadLetters, system.deadLetters, bobNodeId, isFunder = true, ByteVector32.One, FeeratePerKw(25 sat), Some(FeeratePerKw(20 sat)))
|
||||||
|
val expectedSerializedChcr = """{"type":"channel-opened","remoteNodeId":"039dc0e0b1d25905e44fdf6f8e89755a5e219685840d0bc1d28d3308f9628a3585","isFunder":true,"temporaryChannelId":"0100000000000000000000000000000000000000000000000000000000000000","initialFeeratePerKw":25,"fundingTxFeeratePerKw":20}"""
|
||||||
|
assert(serialization.write(chcr) === expectedSerializedChcr)
|
||||||
|
system.eventStream.publish(chcr)
|
||||||
|
wsClient.expectMessage(expectedSerializedChcr)
|
||||||
|
|
||||||
|
val chsc = ChannelStateChanged(system.deadLetters, system.deadLetters, bobNodeId, OFFLINE, NORMAL, null)
|
||||||
|
val expectedSerializedChsc = """{"type":"channel-state-changed","remoteNodeId":"039dc0e0b1d25905e44fdf6f8e89755a5e219685840d0bc1d28d3308f9628a3585","previousState":"OFFLINE","currentState":"NORMAL"}"""
|
||||||
|
assert(serialization.write(chsc) === expectedSerializedChsc)
|
||||||
|
system.eventStream.publish(chsc)
|
||||||
|
wsClient.expectMessage(expectedSerializedChsc)
|
||||||
|
|
||||||
|
val chcl = ChannelClosed(system.deadLetters, ByteVector32.One, Closing.NextRemoteClose(null, null), null)
|
||||||
|
val expectedSerializedChcl = """{"type":"channel-closed","channelId":"0100000000000000000000000000000000000000000000000000000000000000","closingType":"NextRemoteClose"}"""
|
||||||
|
assert(serialization.write(chcl) === expectedSerializedChcl)
|
||||||
|
system.eventStream.publish(chcl)
|
||||||
|
wsClient.expectMessage(expectedSerializedChcl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue