commonMain.fr.acinq.lightning.wire.FailureMessage.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of lightning-kmp-jvm Show documentation
Show all versions of lightning-kmp-jvm Show documentation
A Kotlin Multiplatform implementation of the Lightning Network
package fr.acinq.lightning.wire
import fr.acinq.bitcoin.ByteVector32
import fr.acinq.bitcoin.io.ByteArrayInput
import fr.acinq.bitcoin.io.ByteArrayOutput
import fr.acinq.bitcoin.io.Output
import fr.acinq.lightning.CltvExpiry
import fr.acinq.lightning.MilliSatoshi
import fr.acinq.lightning.utils.toByteVector32
sealed class FailureMessage {
abstract val message: String
abstract val code: Int
companion object {
const val BADONION = 0x8000
const val PERM = 0x4000
const val NODE = 0x2000
const val UPDATE = 0x1000
private fun readChannelUpdate(stream: ByteArrayInput): ChannelUpdate {
val len = LightningCodecs.u16(stream)
val tag = LightningCodecs.u16(stream)
require(tag == ChannelUpdate.type.toInt()) { "channel update should be prefixed with its lightning message type" }
return ChannelUpdate.read(LightningCodecs.bytes(stream, len - 2))
}
fun decode(input: ByteArray): FailureMessage {
val stream = ByteArrayInput(input)
return when (val code = LightningCodecs.u16(stream)) {
InvalidRealm.code -> InvalidRealm
TemporaryNodeFailure.code -> TemporaryNodeFailure
PermanentNodeFailure.code -> PermanentNodeFailure
RequiredNodeFeatureMissing.code -> RequiredNodeFeatureMissing
InvalidOnionVersion.code -> InvalidOnionVersion(LightningCodecs.bytes(stream, 32).toByteVector32())
InvalidOnionHmac.code -> InvalidOnionHmac(LightningCodecs.bytes(stream, 32).toByteVector32())
InvalidOnionKey.code -> InvalidOnionKey(LightningCodecs.bytes(stream, 32).toByteVector32())
TemporaryChannelFailure.code -> TemporaryChannelFailure(readChannelUpdate(stream))
PermanentChannelFailure.code -> PermanentChannelFailure
RequiredChannelFeatureMissing.code -> RequiredChannelFeatureMissing
UnknownNextPeer.code -> UnknownNextPeer
AmountBelowMinimum.code -> AmountBelowMinimum(MilliSatoshi(LightningCodecs.u64(stream)), readChannelUpdate(stream))
FeeInsufficient.code -> FeeInsufficient(MilliSatoshi(LightningCodecs.u64(stream)), readChannelUpdate(stream))
TrampolineFeeInsufficient.code -> TrampolineFeeInsufficient
IncorrectCltvExpiry.code -> IncorrectCltvExpiry(CltvExpiry(LightningCodecs.u32(stream).toLong()), readChannelUpdate(stream))
ExpiryTooSoon.code -> ExpiryTooSoon(readChannelUpdate(stream))
TrampolineExpiryTooSoon.code -> TrampolineExpiryTooSoon
IncorrectOrUnknownPaymentDetails.code -> {
val amount = if (stream.availableBytes > 0) MilliSatoshi(LightningCodecs.u64(stream)) else MilliSatoshi(0)
val blockHeight = if (stream.availableBytes > 0) LightningCodecs.u32(stream).toLong() else 0L
IncorrectOrUnknownPaymentDetails(amount, blockHeight)
}
FinalIncorrectCltvExpiry.code -> FinalIncorrectCltvExpiry(CltvExpiry(LightningCodecs.u32(stream).toLong()))
FinalIncorrectHtlcAmount.code -> FinalIncorrectHtlcAmount(MilliSatoshi(LightningCodecs.u64(stream)))
ChannelDisabled.code -> ChannelDisabled(LightningCodecs.byte(stream).toByte(), LightningCodecs.byte(stream).toByte(), readChannelUpdate(stream))
ExpiryTooFar.code -> ExpiryTooFar
InvalidOnionPayload.code -> InvalidOnionPayload(LightningCodecs.bigSize(stream), LightningCodecs.u16(stream))
PaymentTimeout.code -> PaymentTimeout
else -> UnknownFailureMessage(code)
}
}
private fun writeChannelUpdate(channelUpdate: ChannelUpdate, out: Output) {
val bin = channelUpdate.write()
LightningCodecs.writeU16(bin.size + 2, out)
LightningCodecs.writeU16(ChannelUpdate.type.toInt(), out)
LightningCodecs.writeBytes(bin, out)
}
fun encode(input: FailureMessage, out: Output) {
LightningCodecs.writeU16(input.code, out)
when (input) {
InvalidRealm -> {}
TemporaryNodeFailure -> {}
PermanentNodeFailure -> {}
RequiredNodeFeatureMissing -> {}
is InvalidOnionVersion -> LightningCodecs.writeBytes(input.onionHash, out)
is InvalidOnionHmac -> LightningCodecs.writeBytes(input.onionHash, out)
is InvalidOnionKey -> LightningCodecs.writeBytes(input.onionHash, out)
is InvalidOnionBlinding -> LightningCodecs.writeBytes(input.onionHash, out)
is TemporaryChannelFailure -> writeChannelUpdate(input.update, out)
PermanentChannelFailure -> {}
RequiredChannelFeatureMissing -> {}
UnknownNextPeer -> {}
is AmountBelowMinimum -> {
LightningCodecs.writeU64(input.amount.toLong(), out)
writeChannelUpdate(input.update, out)
}
is FeeInsufficient -> {
LightningCodecs.writeU64(input.amount.toLong(), out)
writeChannelUpdate(input.update, out)
}
TrampolineFeeInsufficient -> {}
is IncorrectCltvExpiry -> {
LightningCodecs.writeU32(input.expiry.toLong().toInt(), out)
writeChannelUpdate(input.update, out)
}
is ExpiryTooSoon -> writeChannelUpdate(input.update, out)
TrampolineExpiryTooSoon -> {}
is IncorrectOrUnknownPaymentDetails -> {
LightningCodecs.writeU64(input.amount.toLong(), out)
LightningCodecs.writeU32(input.height.toInt(), out)
}
is FinalIncorrectCltvExpiry -> LightningCodecs.writeU32(input.expiry.toLong().toInt(), out)
is FinalIncorrectHtlcAmount -> LightningCodecs.writeU64(input.amount.toLong(), out)
is ChannelDisabled -> {
LightningCodecs.writeByte(input.messageFlags.toInt(), out)
LightningCodecs.writeByte(input.channelFlags.toInt(), out)
writeChannelUpdate(input.update, out)
}
ExpiryTooFar -> {}
is InvalidOnionPayload -> {
LightningCodecs.writeBigSize(input.tag, out)
LightningCodecs.writeU16(input.offset, out)
}
PaymentTimeout -> {}
is UnknownFailureMessage -> {}
}
}
fun encode(input: FailureMessage): ByteArray {
val out = ByteArrayOutput()
encode(input, out)
return out.toByteArray()
}
}
}
// @formatter:off
interface BadOnion { val onionHash: ByteVector32 }
interface Perm
interface Node
interface Update { val update: ChannelUpdate }
object InvalidRealm : FailureMessage(), Perm {
override val code get() = PERM or 1
override val message get() = "realm was not understood by the processing node"
}
object TemporaryNodeFailure : FailureMessage(), Node {
override val code get() = NODE or 2
override val message get() = "general temporary failure of the processing node"
}
object PermanentNodeFailure : FailureMessage(), Perm, Node {
override val code get() = PERM or NODE or 2
override val message get() = "general permanent failure of the processing node"
}
object RequiredNodeFeatureMissing : FailureMessage(), Perm, Node {
override val code get() = PERM or NODE or 3
override val message get() = "processing node requires features that are missing from this onion"
}
data class InvalidOnionVersion(override val onionHash: ByteVector32) : FailureMessage(), BadOnion, Perm {
override val code get() = InvalidOnionVersion.code
override val message get() = "onion version was not understood by the processing node"
companion object { const val code = BADONION or PERM or 4 }
}
data class InvalidOnionHmac(override val onionHash: ByteVector32) : FailureMessage(), BadOnion, Perm {
override val code get() = InvalidOnionHmac.code
override val message get() = "onion HMAC was incorrect when it reached the processing node"
companion object { const val code = BADONION or PERM or 5 }
}
data class InvalidOnionKey(override val onionHash: ByteVector32) : FailureMessage(), BadOnion, Perm {
override val code get() = InvalidOnionKey.code
override val message get() = "ephemeral key was unparsable by the processing node"
companion object { const val code = BADONION or PERM or 6 }
}
data class InvalidOnionBlinding(override val onionHash: ByteVector32) : FailureMessage(), BadOnion, Perm {
override val code get() = InvalidOnionBlinding.code
override val message get() = "the blinded onion didn't match the processing node's requirements"
companion object { const val code = BADONION or PERM or 24 }
}
data class TemporaryChannelFailure(override val update: ChannelUpdate) : FailureMessage(), Update {
override val code get() = TemporaryChannelFailure.code
override val message get() = "channel ${update.shortChannelId} is currently unavailable"
companion object { const val code = UPDATE or 7 }
}
object PermanentChannelFailure : FailureMessage(), Perm {
override val code get() = PERM or 8
override val message get() = "channel is permanently unavailable"
}
object RequiredChannelFeatureMissing : FailureMessage(), Perm {
override val code get() = PERM or 9
override val message get() = "channel requires features not present in the onion"
}
object UnknownNextPeer : FailureMessage(), Perm {
override val code get() = PERM or 10
override val message get() = "processing node does not know the next peer in the route"
}
data class AmountBelowMinimum(val amount: MilliSatoshi, override val update: ChannelUpdate) : FailureMessage(), Update {
override val code get() = AmountBelowMinimum.code
override val message get() = "payment amount was below the minimum required by the channel"
companion object { const val code = UPDATE or 11 }
}
data class FeeInsufficient(val amount: MilliSatoshi, override val update: ChannelUpdate) : FailureMessage(), Update {
override val code get() = FeeInsufficient.code
override val message get() = "payment fee was below the minimum required by the channel"
companion object { const val code = UPDATE or 12 }
}
object TrampolineFeeInsufficient : FailureMessage(), Node {
override val code get() = NODE or 51
override val message get() = "payment fee was below the minimum required by the trampoline node"
}
data class IncorrectCltvExpiry(val expiry: CltvExpiry, override val update: ChannelUpdate) : FailureMessage(), Update {
override val code get() = IncorrectCltvExpiry.code
override val message get() = "payment expiry doesn't match the value in the onion"
companion object { const val code = UPDATE or 13 }
}
data class ExpiryTooSoon(override val update: ChannelUpdate) : FailureMessage(), Update {
override val code get() = ExpiryTooSoon.code
override val message get() = "payment expiry is too close to the current block height for safe handling by the relaying node"
companion object { const val code = UPDATE or 14 }
}
object TrampolineExpiryTooSoon : FailureMessage(), Node {
override val code get() = NODE or 52
override val message get() = "payment expiry is too close to the current block height for safe handling by the relaying node"
}
data class IncorrectOrUnknownPaymentDetails(val amount: MilliSatoshi, val height: Long) : FailureMessage(), Perm {
override val code get() = IncorrectOrUnknownPaymentDetails.code
override val message get() = "incorrect payment details or unknown payment hash"
companion object { const val code = PERM or 15 }
}
data class FinalIncorrectCltvExpiry(val expiry: CltvExpiry) : FailureMessage() {
override val code get() = FinalIncorrectCltvExpiry.code
override val message get() = "payment expiry doesn't match the value in the onion"
companion object { const val code = 18 }
}
data class FinalIncorrectHtlcAmount(val amount: MilliSatoshi) : FailureMessage() {
override val code get() = FinalIncorrectHtlcAmount.code
override val message get() = "payment amount is incorrect in the final htlc"
companion object { const val code = 19 }
}
data class ChannelDisabled(val messageFlags: Byte, val channelFlags: Byte, override val update: ChannelUpdate) : FailureMessage(), Update {
override val code get() = ChannelDisabled.code
override val message get() = "channel is currently disabled"
companion object { const val code = UPDATE or 20 }
}
data object ExpiryTooFar : FailureMessage() {
override val code get() = 21
override val message get() = "payment expiry is too far in the future"
}
data class InvalidOnionPayload(val tag: Long, val offset: Int) : FailureMessage(), Perm {
override val code get() = InvalidOnionPayload.code
override val message get() = "onion per-hop payload is invalid"
companion object { const val code = PERM or 22 }
}
data object PaymentTimeout : FailureMessage() {
override val code get() = 23
override val message get() = "the complete payment amount was not received within a reasonable time"
}
/**
* We allow remote nodes to send us unknown failure codes (e.g. deprecated failure codes).
* By reading the PERM and NODE bits of the failure code we can still extract useful information for payment retry even
* without knowing how to decode the failure payload (but we can't extract a channel update or onion hash).
*/
data class UnknownFailureMessage(override val code: Int) : FailureMessage() {
override val message get() = "unknown failure message"
}
// @formatter:on
© 2015 - 2024 Weber Informatics LLC | Privacy Policy