Skip to content

Commit

Permalink
Implement missing codecs
Browse files Browse the repository at this point in the history
Fixes #220
  • Loading branch information
t-bast committed Mar 11, 2021
1 parent 3dcc579 commit 6729302
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 28 deletions.
15 changes: 13 additions & 2 deletions src/commonMain/kotlin/fr/acinq/eclair/wire/ChannelTlv.kt
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,25 @@ sealed class ChannelTlv : Tlv {
override val tag: Long get() = ChannelOriginTlv.tag

override fun write(out: Output) {
TODO("Not implemented (not needed)")
when (channelOrigin) {
is ChannelOrigin.PayToOpenOrigin -> {
LightningCodecs.writeU16(1, out)
LightningCodecs.writeBytes(channelOrigin.paymentHash, out)
}
is ChannelOrigin.SwapInOrigin -> {
LightningCodecs.writeU16(2, out)
val addressBytes = channelOrigin.bitcoinAddress.encodeToByteArray()
LightningCodecs.writeBigSize(addressBytes.size.toLong(), out)
LightningCodecs.writeBytes(addressBytes, out)
}
}
}

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

override fun read(input: Input): ChannelOriginTlv {
val origin = when(LightningCodecs.u16(input)) {
val origin = when (LightningCodecs.u16(input)) {
1 -> ChannelOrigin.PayToOpenOrigin(ByteVector32(LightningCodecs.bytes(input, 32)))
2 -> {
val len = LightningCodecs.bigSize(input)
Expand Down
85 changes: 75 additions & 10 deletions src/commonMain/kotlin/fr/acinq/eclair/wire/LightningMessages.kt
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,15 @@ interface LightningMessage {
UpdateFulfillHtlc.type -> UpdateFulfillHtlc.read(stream)
UpdateFee.type -> UpdateFee.read(stream)
AnnouncementSignatures.type -> AnnouncementSignatures.read(stream)
ChannelAnnouncement.type -> ChannelAnnouncement.read(stream)
ChannelUpdate.type -> ChannelUpdate.read(stream)
Shutdown.type -> Shutdown.read(stream)
ClosingSigned.type -> ClosingSigned.read(stream)
PayToOpenRequest.type -> PayToOpenRequest.read(stream)
PayToOpenResponse.type -> PayToOpenResponse.read(stream)
FCMToken.type -> FCMToken.read(stream)
UnsetFCMToken.type -> UnsetFCMToken
SwapInRequest.type -> SwapInRequest.read(stream)
SwapInResponse.type -> SwapInResponse.read(stream)
SwapInPending.type -> SwapInPending.read(stream)
SwapInConfirmed.type -> SwapInConfirmed.read(stream)
Expand Down Expand Up @@ -800,14 +803,52 @@ data class ChannelAnnouncement(
override val type: Long get() = ChannelAnnouncement.type

override fun write(out: Output) {
TODO()
LightningCodecs.writeBytes(nodeSignature1, out)
LightningCodecs.writeBytes(nodeSignature2, out)
LightningCodecs.writeBytes(bitcoinSignature1, out)
LightningCodecs.writeBytes(bitcoinSignature2, out)
val featureBytes = features.toByteArray()
LightningCodecs.writeU16(featureBytes.size, out)
LightningCodecs.writeBytes(featureBytes, out)
LightningCodecs.writeBytes(chainHash, out)
LightningCodecs.writeU64(shortChannelId.toLong(), out)
LightningCodecs.writeBytes(nodeId1.value, out)
LightningCodecs.writeBytes(nodeId2.value, out)
LightningCodecs.writeBytes(bitcoinKey1.value, out)
LightningCodecs.writeBytes(bitcoinKey2.value, out)
LightningCodecs.writeBytes(unknownFields, out)
}

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

override fun read(input: Input): ChannelAnnouncement {
TODO()
val nodeSignature1 = LightningCodecs.bytes(input, 64).toByteVector64()
val nodeSignature2 = LightningCodecs.bytes(input, 64).toByteVector64()
val bitcoinSignature1 = LightningCodecs.bytes(input, 64).toByteVector64()
val bitcoinSignature2 = LightningCodecs.bytes(input, 64).toByteVector64()
val featureBytes = LightningCodecs.bytes(input, LightningCodecs.u16(input))
val chainHash = LightningCodecs.bytes(input, 32).toByteVector32()
val shortChannelId = ShortChannelId(LightningCodecs.u64(input))
val nodeId1 = PublicKey(LightningCodecs.bytes(input, 33))
val nodeId2 = PublicKey(LightningCodecs.bytes(input, 33))
val bitcoinKey1 = PublicKey(LightningCodecs.bytes(input, 33))
val bitcoinKey2 = PublicKey(LightningCodecs.bytes(input, 33))
val unknownBytes = if (input.availableBytes > 0) LightningCodecs.bytes(input, input.availableBytes).toByteVector() else ByteVector.empty
return ChannelAnnouncement(
nodeSignature1,
nodeSignature2,
bitcoinSignature1,
bitcoinSignature2,
Features(featureBytes),
chainHash,
shortChannelId,
nodeId1,
nodeId2,
bitcoinKey1,
bitcoinKey2,
unknownBytes
)
}
}
}
Expand Down Expand Up @@ -981,7 +1022,15 @@ data class PayToOpenRequest(
override val type: Long get() = PayToOpenRequest.type

override fun write(out: Output) {
TODO("Not implemented (not needed)")
LightningCodecs.writeBytes(chainHash, out)
LightningCodecs.writeU64(fundingSatoshis.toLong(), out)
LightningCodecs.writeU64(amountMsat.toLong(), out)
LightningCodecs.writeU64(payToOpenMinAmountMsat.toLong(), out)
LightningCodecs.writeU64(payToOpenFeeSatoshis.toLong(), out)
LightningCodecs.writeBytes(paymentHash, out)
LightningCodecs.writeU32(expireAt.toInt(), out)
LightningCodecs.writeU16(finalPacket.payload.size(), out)
OnionRoutingPacketSerializer(finalPacket.payload.size()).write(finalPacket, out)
}

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

override fun read(input: Input): PayToOpenResponse {
TODO("Not yet implemented")
val chainHash = LightningCodecs.bytes(input, 32).toByteVector32()
val paymentHash = LightningCodecs.bytes(input, 32).toByteVector32()
return when (val preimage = LightningCodecs.bytes(input, 32).toByteVector32()) {
ByteVector32.Zeroes -> {
val failure = if (input.availableBytes > 0) LightningCodecs.bytes(input, LightningCodecs.u16(input)).toByteVector() else null
PayToOpenResponse(chainHash, paymentHash, Result.Failure(failure))
}
else -> PayToOpenResponse(chainHash, paymentHash, Result.Success(preimage))
}
}
}
}
Expand Down Expand Up @@ -1079,11 +1136,10 @@ data class FCMToken(@Serializable(with = ByteVectorKSerializer::class) val token

@Serializable
object UnsetFCMToken : LightningMessage {

override val type: Long get() = 35019

override fun write(out: Output) {}
}

@OptIn(ExperimentalUnsignedTypes::class)
data class SwapInRequest(
override val chainHash: ByteVector32
Expand All @@ -1098,7 +1154,7 @@ data class SwapInRequest(
const val type: Long = 35007

override fun read(input: Input): SwapInRequest {
TODO("Not implemented (not needed)")
return SwapInRequest(LightningCodecs.bytes(input, 32).toByteVector32())
}
}
}
Expand All @@ -1111,7 +1167,10 @@ data class SwapInResponse(
override val type: Long get() = SwapInResponse.type

override fun write(out: Output) {
TODO("Not implemented (not needed)")
LightningCodecs.writeBytes(chainHash, out)
val addressBytes = bitcoinAddress.encodeToByteArray()
LightningCodecs.writeU16(addressBytes.size, out)
LightningCodecs.writeBytes(addressBytes, out)
}

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

override fun write(out: Output) {
TODO("Not implemented (not needed)")
val addressBytes = bitcoinAddress.encodeToByteArray()
LightningCodecs.writeU16(addressBytes.size, out)
LightningCodecs.writeBytes(addressBytes, out)
LightningCodecs.writeU64(amount.toLong(), out)
}

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

override fun write(out: Output) {
TODO("Not implemented (not needed)")
val addressBytes = bitcoinAddress.encodeToByteArray()
LightningCodecs.writeU16(addressBytes.size, out)
LightningCodecs.writeBytes(addressBytes, out)
LightningCodecs.writeU64(amount.toLong(), out)
}

companion object : LightningMessageReader<SwapInConfirmed> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import fr.acinq.eclair.Eclair.randomBytes
import fr.acinq.eclair.Eclair.randomBytes32
import fr.acinq.eclair.Eclair.randomBytes64
import fr.acinq.eclair.Eclair.randomKey
import fr.acinq.eclair.Features
import fr.acinq.eclair.MilliSatoshi
import fr.acinq.eclair.ShortChannelId
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
Expand All @@ -24,6 +25,7 @@ import kotlinx.serialization.json.jsonPrimitive
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFails
import kotlin.test.assertNotNull

@OptIn(ExperimentalUnsignedTypes::class)
class LightningCodecsTestsCommon : EclairTestSuite() {
Expand Down Expand Up @@ -465,6 +467,21 @@ class LightningCodecsTestsCommon : EclairTestSuite() {
assertEquals(channelUpdate, decoded)
}

@Test
fun `encode - decode channel_announcement`() {
val testCases = listOf(
ChannelAnnouncement(randomBytes64(), randomBytes64(), randomBytes64(), randomBytes64(), Features(Hex.decode("09004200")), randomBytes32(), ShortChannelId(42), randomKey().publicKey(), randomKey().publicKey(), randomKey().publicKey(), randomKey().publicKey()),
ChannelAnnouncement(randomBytes64(), randomBytes64(), randomBytes64(), randomBytes64(), Features(emptySet()), randomBytes32(), ShortChannelId(42), randomKey().publicKey(), randomKey().publicKey(), randomKey().publicKey(), randomKey().publicKey(), ByteVector("01020304")),
)

testCases.forEach {
val encoded = LightningMessage.encode(it)
val decoded = LightningMessage.decode(encoded)
assertNotNull(decoded)
assertEquals(it, decoded)
}
}

@Test
fun `nonreg backup channel data`() {
val channelId = randomBytes32()
Expand Down Expand Up @@ -527,26 +544,37 @@ class LightningCodecsTestsCommon : EclairTestSuite() {
}

@Test
fun `encode - decode swap-in messages`() {
assertArrayEquals(
a = Hex.decode("88bf000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"),
b = LightningMessage.encode(SwapInRequest(Block.LivenetGenesisBlock.blockId))
)

assertEquals(
expected = SwapInResponse(Block.LivenetGenesisBlock.blockId, "bc1qms2el02t3fv8ecln0j74auassqwcg3ejekmypv"),
actual = LightningMessage.decode(Hex.decode("88c1000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f002a626331716d7332656c3032743366763865636c6e306a373461756173737177636733656a656b6d797076"))
fun `encode - decode pay-to-open messages`() {
val testCases = listOf(
PayToOpenRequest(randomBytes32(), 10_000.sat, 5_000.msat, 100.msat, 10.sat, randomBytes32(), 100, OnionRoutingPacket(0, randomKey().publicKey().value, ByteVector("0102030405"), randomBytes32())),
PayToOpenResponse(randomBytes32(), randomBytes32(), PayToOpenResponse.Result.Success(randomBytes32())),
PayToOpenResponse(randomBytes32(), randomBytes32(), PayToOpenResponse.Result.Failure(null)),
PayToOpenResponse(randomBytes32(), randomBytes32(), PayToOpenResponse.Result.Failure(ByteVector("deadbeef"))),
)

assertEquals(
expected = SwapInPending("bc1qms2el02t3fv8ecln0j74auassqwcg3ejekmypv", Satoshi(123456)),
actual = LightningMessage.decode(Hex.decode("88bd002a626331716d7332656c3032743366763865636c6e306a373461756173737177636733656a656b6d797076000000000001e240"))
)
testCases.forEach {
val encoded = LightningMessage.encode(it)
val decoded = LightningMessage.decode(encoded)
assertNotNull(decoded)
assertEquals(it, decoded)
}
}

assertEquals(
expected = SwapInConfirmed("39gzznpTuzhtjdN5R2LZu8GgWLR9NovLdi", MilliSatoshi(42_000_000)),
actual = LightningMessage.decode(Hex.decode("88c700223339677a7a6e7054757a68746a644e3552324c5a75384767574c52394e6f764c6469000000000280de80"))
@Test
fun `encode - decode swap-in messages`() {
val testCases = listOf(
Pair(SwapInRequest(Block.LivenetGenesisBlock.blockId), Hex.decode("88bf000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")),
Pair(SwapInResponse(Block.LivenetGenesisBlock.blockId, "bc1qms2el02t3fv8ecln0j74auassqwcg3ejekmypv"), Hex.decode("88c1000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f002a626331716d7332656c3032743366763865636c6e306a373461756173737177636733656a656b6d797076")),
Pair(SwapInPending("bc1qms2el02t3fv8ecln0j74auassqwcg3ejekmypv", Satoshi(123456)), Hex.decode("88bd002a626331716d7332656c3032743366763865636c6e306a373461756173737177636733656a656b6d797076000000000001e240")),
Pair(SwapInConfirmed("39gzznpTuzhtjdN5R2LZu8GgWLR9NovLdi", MilliSatoshi(42_000_000)), Hex.decode("88c700223339677a7a6e7054757a68746a644e3552324c5a75384767574c52394e6f764c6469000000000280de80"))
)

testCases.forEach {
val decoded = LightningMessage.decode(it.second)
assertNotNull(decoded)
assertEquals(it.first, decoded)
val encoded = LightningMessage.encode(decoded)
assertArrayEquals(it.second, encoded)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package fr.acinq.eclair.wire
import fr.acinq.bitcoin.ByteVector32
import fr.acinq.eclair.channel.ChannelOrigin
import fr.acinq.eclair.channel.ChannelVersion
import fr.acinq.eclair.crypto.assertArrayEquals
import fr.acinq.eclair.tests.utils.EclairTestSuite
import fr.acinq.eclair.utils.toByteVector
import fr.acinq.secp256k1.Hex
import kotlin.test.Test
import kotlin.test.assertEquals
Expand Down Expand Up @@ -49,6 +51,8 @@ class OpenTlvTestsCommon : EclairTestSuite() {

testCases.forEach {
val decoded = tlvStreamSerializer.read(it.second)
val encoded = tlvStreamSerializer.write(decoded)
assertArrayEquals(it.second, encoded)
val channelOrigin = decoded.records.mapNotNull { record ->
when (record) {
is ChannelTlv.ChannelOriginTlv -> record.channelOrigin
Expand Down

0 comments on commit 6729302

Please sign in to comment.