1
0
Fork 0
mirror of https://github.com/ACINQ/eclair.git synced 2025-02-20 13:34:35 +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:
Bastien Teinturier 2020-09-28 09:51:42 +02:00 committed by GitHub
parent 483cce4ae2
commit 42481c66e6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 62 additions and 4 deletions

View file

@ -25,7 +25,7 @@ import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
import fr.acinq.bitcoin.{ByteVector32, ByteVector64, OutPoint, Satoshi, Transaction}
import fr.acinq.eclair.ApiTypes.ChannelIdentifier
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.db.{IncomingPaymentStatus, OutgoingPaymentStatus}
import fr.acinq.eclair.payment._
@ -42,6 +42,7 @@ import scodec.bits.ByteVector
* JSON Serializers.
* Note: in general, deserialization does not need to be implemented.
*/
class ByteVectorSerializer extends CustomSerializer[ByteVector](_ => ( {
null
}, {
@ -278,6 +279,30 @@ class JavaUUIDSerializer extends CustomSerializer[UUID](_ => ( {
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 {
val reverse: Map[String, Class[_]] = custom.map(_.swap)
@ -321,6 +346,7 @@ object JsonSupport extends Json4sSupport {
new ByteVectorSerializer +
new ByteVector32Serializer +
new ByteVector64Serializer +
new ChannelEventSerializer +
new UInt64Serializer +
new SatoshiSerializer +
new MilliSatoshiSerializer +
@ -360,10 +386,12 @@ object JsonSupport extends Json4sSupport {
JObject(
JField("name", JString(a.feature.rfcName)),
JField("support", JString(a.support.toString))
)}.toList)),
)
}.toList)),
JField("unknown", JArray(features.unknown.map { i =>
JObject(
JField("featureBit", JInt(i.bitIndex))
)}.toList))
)
}.toList))
)
}

View file

@ -35,6 +35,7 @@ import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.bitcoin.{ByteVector32, Satoshi}
import fr.acinq.eclair.api.FormParamExtractors._
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.payment.{PaymentEvent, PaymentRequest}
import fr.acinq.eclair.{CltvExpiryDelta, Eclair, MilliSatoshi}
@ -91,10 +92,19 @@ trait Service extends ExtraDirectives with Logging {
override def preStart: Unit = {
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 = {
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))
}
}))

View file

@ -33,7 +33,9 @@ import fr.acinq.eclair.ApiTypes.ChannelIdentifier
import fr.acinq.eclair.FeatureSupport.{Mandatory, Optional}
import fr.acinq.eclair.Features.{ChannelRangeQueriesExtended, OptionDataLossProtect}
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.io.NodeURI
import fr.acinq.eclair.io.Peer.PeerInfo
@ -530,6 +532,24 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM
assert(serialization.write(pset) === expectedSerializedPset)
system.eventStream.publish(pset)
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)
}
}