Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement missing codecs #224

Merged
merged 1 commit into from
Mar 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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