Skip to content

Commit 4343838

Browse files
authored
Implement missing codecs (#224)
Fixes #220
1 parent 2530895 commit 4343838

File tree

4 files changed

+136
-28
lines changed

4 files changed

+136
-28
lines changed

src/commonMain/kotlin/fr/acinq/eclair/wire/ChannelTlv.kt

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,25 @@ sealed class ChannelTlv : Tlv {
5959
override val tag: Long get() = ChannelOriginTlv.tag
6060

6161
override fun write(out: Output) {
62-
TODO("Not implemented (not needed)")
62+
when (channelOrigin) {
63+
is ChannelOrigin.PayToOpenOrigin -> {
64+
LightningCodecs.writeU16(1, out)
65+
LightningCodecs.writeBytes(channelOrigin.paymentHash, out)
66+
}
67+
is ChannelOrigin.SwapInOrigin -> {
68+
LightningCodecs.writeU16(2, out)
69+
val addressBytes = channelOrigin.bitcoinAddress.encodeToByteArray()
70+
LightningCodecs.writeBigSize(addressBytes.size.toLong(), out)
71+
LightningCodecs.writeBytes(addressBytes, out)
72+
}
73+
}
6374
}
6475

6576
companion object : TlvValueReader<ChannelOriginTlv> {
6677
const val tag: Long = 0x47000003
6778

6879
override fun read(input: Input): ChannelOriginTlv {
69-
val origin = when(LightningCodecs.u16(input)) {
80+
val origin = when (LightningCodecs.u16(input)) {
7081
1 -> ChannelOrigin.PayToOpenOrigin(ByteVector32(LightningCodecs.bytes(input, 32)))
7182
2 -> {
7283
val len = LightningCodecs.bigSize(input)

src/commonMain/kotlin/fr/acinq/eclair/wire/LightningMessages.kt

Lines changed: 75 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,15 @@ interface LightningMessage {
6565
UpdateFulfillHtlc.type -> UpdateFulfillHtlc.read(stream)
6666
UpdateFee.type -> UpdateFee.read(stream)
6767
AnnouncementSignatures.type -> AnnouncementSignatures.read(stream)
68+
ChannelAnnouncement.type -> ChannelAnnouncement.read(stream)
6869
ChannelUpdate.type -> ChannelUpdate.read(stream)
6970
Shutdown.type -> Shutdown.read(stream)
7071
ClosingSigned.type -> ClosingSigned.read(stream)
7172
PayToOpenRequest.type -> PayToOpenRequest.read(stream)
73+
PayToOpenResponse.type -> PayToOpenResponse.read(stream)
7274
FCMToken.type -> FCMToken.read(stream)
7375
UnsetFCMToken.type -> UnsetFCMToken
76+
SwapInRequest.type -> SwapInRequest.read(stream)
7477
SwapInResponse.type -> SwapInResponse.read(stream)
7578
SwapInPending.type -> SwapInPending.read(stream)
7679
SwapInConfirmed.type -> SwapInConfirmed.read(stream)
@@ -800,14 +803,52 @@ data class ChannelAnnouncement(
800803
override val type: Long get() = ChannelAnnouncement.type
801804

802805
override fun write(out: Output) {
803-
TODO()
806+
LightningCodecs.writeBytes(nodeSignature1, out)
807+
LightningCodecs.writeBytes(nodeSignature2, out)
808+
LightningCodecs.writeBytes(bitcoinSignature1, out)
809+
LightningCodecs.writeBytes(bitcoinSignature2, out)
810+
val featureBytes = features.toByteArray()
811+
LightningCodecs.writeU16(featureBytes.size, out)
812+
LightningCodecs.writeBytes(featureBytes, out)
813+
LightningCodecs.writeBytes(chainHash, out)
814+
LightningCodecs.writeU64(shortChannelId.toLong(), out)
815+
LightningCodecs.writeBytes(nodeId1.value, out)
816+
LightningCodecs.writeBytes(nodeId2.value, out)
817+
LightningCodecs.writeBytes(bitcoinKey1.value, out)
818+
LightningCodecs.writeBytes(bitcoinKey2.value, out)
819+
LightningCodecs.writeBytes(unknownFields, out)
804820
}
805821

806822
companion object : LightningMessageReader<ChannelAnnouncement> {
807823
const val type: Long = 256
808824

809825
override fun read(input: Input): ChannelAnnouncement {
810-
TODO()
826+
val nodeSignature1 = LightningCodecs.bytes(input, 64).toByteVector64()
827+
val nodeSignature2 = LightningCodecs.bytes(input, 64).toByteVector64()
828+
val bitcoinSignature1 = LightningCodecs.bytes(input, 64).toByteVector64()
829+
val bitcoinSignature2 = LightningCodecs.bytes(input, 64).toByteVector64()
830+
val featureBytes = LightningCodecs.bytes(input, LightningCodecs.u16(input))
831+
val chainHash = LightningCodecs.bytes(input, 32).toByteVector32()
832+
val shortChannelId = ShortChannelId(LightningCodecs.u64(input))
833+
val nodeId1 = PublicKey(LightningCodecs.bytes(input, 33))
834+
val nodeId2 = PublicKey(LightningCodecs.bytes(input, 33))
835+
val bitcoinKey1 = PublicKey(LightningCodecs.bytes(input, 33))
836+
val bitcoinKey2 = PublicKey(LightningCodecs.bytes(input, 33))
837+
val unknownBytes = if (input.availableBytes > 0) LightningCodecs.bytes(input, input.availableBytes).toByteVector() else ByteVector.empty
838+
return ChannelAnnouncement(
839+
nodeSignature1,
840+
nodeSignature2,
841+
bitcoinSignature1,
842+
bitcoinSignature2,
843+
Features(featureBytes),
844+
chainHash,
845+
shortChannelId,
846+
nodeId1,
847+
nodeId2,
848+
bitcoinKey1,
849+
bitcoinKey2,
850+
unknownBytes
851+
)
811852
}
812853
}
813854
}
@@ -981,7 +1022,15 @@ data class PayToOpenRequest(
9811022
override val type: Long get() = PayToOpenRequest.type
9821023

9831024
override fun write(out: Output) {
984-
TODO("Not implemented (not needed)")
1025+
LightningCodecs.writeBytes(chainHash, out)
1026+
LightningCodecs.writeU64(fundingSatoshis.toLong(), out)
1027+
LightningCodecs.writeU64(amountMsat.toLong(), out)
1028+
LightningCodecs.writeU64(payToOpenMinAmountMsat.toLong(), out)
1029+
LightningCodecs.writeU64(payToOpenFeeSatoshis.toLong(), out)
1030+
LightningCodecs.writeBytes(paymentHash, out)
1031+
LightningCodecs.writeU32(expireAt.toInt(), out)
1032+
LightningCodecs.writeU16(finalPacket.payload.size(), out)
1033+
OnionRoutingPacketSerializer(finalPacket.payload.size()).write(finalPacket, out)
9851034
}
9861035

9871036
companion object : LightningMessageReader<PayToOpenRequest> {
@@ -1050,7 +1099,15 @@ data class PayToOpenResponse(override val chainHash: ByteVector32, val paymentHa
10501099
const val type: Long = 35003
10511100

10521101
override fun read(input: Input): PayToOpenResponse {
1053-
TODO("Not yet implemented")
1102+
val chainHash = LightningCodecs.bytes(input, 32).toByteVector32()
1103+
val paymentHash = LightningCodecs.bytes(input, 32).toByteVector32()
1104+
return when (val preimage = LightningCodecs.bytes(input, 32).toByteVector32()) {
1105+
ByteVector32.Zeroes -> {
1106+
val failure = if (input.availableBytes > 0) LightningCodecs.bytes(input, LightningCodecs.u16(input)).toByteVector() else null
1107+
PayToOpenResponse(chainHash, paymentHash, Result.Failure(failure))
1108+
}
1109+
else -> PayToOpenResponse(chainHash, paymentHash, Result.Success(preimage))
1110+
}
10541111
}
10551112
}
10561113
}
@@ -1079,11 +1136,10 @@ data class FCMToken(@Serializable(with = ByteVectorKSerializer::class) val token
10791136

10801137
@Serializable
10811138
object UnsetFCMToken : LightningMessage {
1082-
10831139
override val type: Long get() = 35019
1084-
10851140
override fun write(out: Output) {}
10861141
}
1142+
10871143
@OptIn(ExperimentalUnsignedTypes::class)
10881144
data class SwapInRequest(
10891145
override val chainHash: ByteVector32
@@ -1098,7 +1154,7 @@ data class SwapInRequest(
10981154
const val type: Long = 35007
10991155

11001156
override fun read(input: Input): SwapInRequest {
1101-
TODO("Not implemented (not needed)")
1157+
return SwapInRequest(LightningCodecs.bytes(input, 32).toByteVector32())
11021158
}
11031159
}
11041160
}
@@ -1111,7 +1167,10 @@ data class SwapInResponse(
11111167
override val type: Long get() = SwapInResponse.type
11121168

11131169
override fun write(out: Output) {
1114-
TODO("Not implemented (not needed)")
1170+
LightningCodecs.writeBytes(chainHash, out)
1171+
val addressBytes = bitcoinAddress.encodeToByteArray()
1172+
LightningCodecs.writeU16(addressBytes.size, out)
1173+
LightningCodecs.writeBytes(addressBytes, out)
11151174
}
11161175

11171176
companion object : LightningMessageReader<SwapInResponse> {
@@ -1134,7 +1193,10 @@ data class SwapInPending(
11341193
override val type: Long get() = SwapInPending.type
11351194

11361195
override fun write(out: Output) {
1137-
TODO("Not implemented (not needed)")
1196+
val addressBytes = bitcoinAddress.encodeToByteArray()
1197+
LightningCodecs.writeU16(addressBytes.size, out)
1198+
LightningCodecs.writeBytes(addressBytes, out)
1199+
LightningCodecs.writeU64(amount.toLong(), out)
11381200
}
11391201

11401202
companion object : LightningMessageReader<SwapInPending> {
@@ -1157,7 +1219,10 @@ data class SwapInConfirmed(
11571219
override val type: Long get() = SwapInConfirmed.type
11581220

11591221
override fun write(out: Output) {
1160-
TODO("Not implemented (not needed)")
1222+
val addressBytes = bitcoinAddress.encodeToByteArray()
1223+
LightningCodecs.writeU16(addressBytes.size, out)
1224+
LightningCodecs.writeBytes(addressBytes, out)
1225+
LightningCodecs.writeU64(amount.toLong(), out)
11611226
}
11621227

11631228
companion object : LightningMessageReader<SwapInConfirmed> {

src/commonTest/kotlin/fr/acinq/eclair/wire/LightningCodecsTestsCommon.kt

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import fr.acinq.eclair.Eclair.randomBytes
88
import fr.acinq.eclair.Eclair.randomBytes32
99
import fr.acinq.eclair.Eclair.randomBytes64
1010
import fr.acinq.eclair.Eclair.randomKey
11+
import fr.acinq.eclair.Features
1112
import fr.acinq.eclair.MilliSatoshi
1213
import fr.acinq.eclair.ShortChannelId
1314
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
@@ -24,6 +25,7 @@ import kotlinx.serialization.json.jsonPrimitive
2425
import kotlin.test.Test
2526
import kotlin.test.assertEquals
2627
import kotlin.test.assertFails
28+
import kotlin.test.assertNotNull
2729

2830
@OptIn(ExperimentalUnsignedTypes::class)
2931
class LightningCodecsTestsCommon : EclairTestSuite() {
@@ -465,6 +467,21 @@ class LightningCodecsTestsCommon : EclairTestSuite() {
465467
assertEquals(channelUpdate, decoded)
466468
}
467469

470+
@Test
471+
fun `encode - decode channel_announcement`() {
472+
val testCases = listOf(
473+
ChannelAnnouncement(randomBytes64(), randomBytes64(), randomBytes64(), randomBytes64(), Features(Hex.decode("09004200")), randomBytes32(), ShortChannelId(42), randomKey().publicKey(), randomKey().publicKey(), randomKey().publicKey(), randomKey().publicKey()),
474+
ChannelAnnouncement(randomBytes64(), randomBytes64(), randomBytes64(), randomBytes64(), Features(emptySet()), randomBytes32(), ShortChannelId(42), randomKey().publicKey(), randomKey().publicKey(), randomKey().publicKey(), randomKey().publicKey(), ByteVector("01020304")),
475+
)
476+
477+
testCases.forEach {
478+
val encoded = LightningMessage.encode(it)
479+
val decoded = LightningMessage.decode(encoded)
480+
assertNotNull(decoded)
481+
assertEquals(it, decoded)
482+
}
483+
}
484+
468485
@Test
469486
fun `nonreg backup channel data`() {
470487
val channelId = randomBytes32()
@@ -527,26 +544,37 @@ class LightningCodecsTestsCommon : EclairTestSuite() {
527544
}
528545

529546
@Test
530-
fun `encode - decode swap-in messages`() {
531-
assertArrayEquals(
532-
a = Hex.decode("88bf000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"),
533-
b = LightningMessage.encode(SwapInRequest(Block.LivenetGenesisBlock.blockId))
534-
)
535-
536-
assertEquals(
537-
expected = SwapInResponse(Block.LivenetGenesisBlock.blockId, "bc1qms2el02t3fv8ecln0j74auassqwcg3ejekmypv"),
538-
actual = LightningMessage.decode(Hex.decode("88c1000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f002a626331716d7332656c3032743366763865636c6e306a373461756173737177636733656a656b6d797076"))
547+
fun `encode - decode pay-to-open messages`() {
548+
val testCases = listOf(
549+
PayToOpenRequest(randomBytes32(), 10_000.sat, 5_000.msat, 100.msat, 10.sat, randomBytes32(), 100, OnionRoutingPacket(0, randomKey().publicKey().value, ByteVector("0102030405"), randomBytes32())),
550+
PayToOpenResponse(randomBytes32(), randomBytes32(), PayToOpenResponse.Result.Success(randomBytes32())),
551+
PayToOpenResponse(randomBytes32(), randomBytes32(), PayToOpenResponse.Result.Failure(null)),
552+
PayToOpenResponse(randomBytes32(), randomBytes32(), PayToOpenResponse.Result.Failure(ByteVector("deadbeef"))),
539553
)
540554

541-
assertEquals(
542-
expected = SwapInPending("bc1qms2el02t3fv8ecln0j74auassqwcg3ejekmypv", Satoshi(123456)),
543-
actual = LightningMessage.decode(Hex.decode("88bd002a626331716d7332656c3032743366763865636c6e306a373461756173737177636733656a656b6d797076000000000001e240"))
544-
)
555+
testCases.forEach {
556+
val encoded = LightningMessage.encode(it)
557+
val decoded = LightningMessage.decode(encoded)
558+
assertNotNull(decoded)
559+
assertEquals(it, decoded)
560+
}
561+
}
545562

546-
assertEquals(
547-
expected = SwapInConfirmed("39gzznpTuzhtjdN5R2LZu8GgWLR9NovLdi", MilliSatoshi(42_000_000)),
548-
actual = LightningMessage.decode(Hex.decode("88c700223339677a7a6e7054757a68746a644e3552324c5a75384767574c52394e6f764c6469000000000280de80"))
563+
@Test
564+
fun `encode - decode swap-in messages`() {
565+
val testCases = listOf(
566+
Pair(SwapInRequest(Block.LivenetGenesisBlock.blockId), Hex.decode("88bf000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")),
567+
Pair(SwapInResponse(Block.LivenetGenesisBlock.blockId, "bc1qms2el02t3fv8ecln0j74auassqwcg3ejekmypv"), Hex.decode("88c1000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f002a626331716d7332656c3032743366763865636c6e306a373461756173737177636733656a656b6d797076")),
568+
Pair(SwapInPending("bc1qms2el02t3fv8ecln0j74auassqwcg3ejekmypv", Satoshi(123456)), Hex.decode("88bd002a626331716d7332656c3032743366763865636c6e306a373461756173737177636733656a656b6d797076000000000001e240")),
569+
Pair(SwapInConfirmed("39gzznpTuzhtjdN5R2LZu8GgWLR9NovLdi", MilliSatoshi(42_000_000)), Hex.decode("88c700223339677a7a6e7054757a68746a644e3552324c5a75384767574c52394e6f764c6469000000000280de80"))
549570
)
550571

572+
testCases.forEach {
573+
val decoded = LightningMessage.decode(it.second)
574+
assertNotNull(decoded)
575+
assertEquals(it.first, decoded)
576+
val encoded = LightningMessage.encode(decoded)
577+
assertArrayEquals(it.second, encoded)
578+
}
551579
}
552580
}

src/commonTest/kotlin/fr/acinq/eclair/wire/OpenTlvTestsCommon.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package fr.acinq.eclair.wire
33
import fr.acinq.bitcoin.ByteVector32
44
import fr.acinq.eclair.channel.ChannelOrigin
55
import fr.acinq.eclair.channel.ChannelVersion
6+
import fr.acinq.eclair.crypto.assertArrayEquals
67
import fr.acinq.eclair.tests.utils.EclairTestSuite
8+
import fr.acinq.eclair.utils.toByteVector
79
import fr.acinq.secp256k1.Hex
810
import kotlin.test.Test
911
import kotlin.test.assertEquals
@@ -49,6 +51,8 @@ class OpenTlvTestsCommon : EclairTestSuite() {
4951

5052
testCases.forEach {
5153
val decoded = tlvStreamSerializer.read(it.second)
54+
val encoded = tlvStreamSerializer.write(decoded)
55+
assertArrayEquals(it.second, encoded)
5256
val channelOrigin = decoded.records.mapNotNull { record ->
5357
when (record) {
5458
is ChannelTlv.ChannelOriginTlv -> record.channelOrigin

0 commit comments

Comments
 (0)