Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,8 @@ object Transactions {
case class HtlcTimeoutTx(input: InputInfo, tx: Transaction, htlcId: Long) extends HtlcTx { override def desc: String = "htlc-timeout" }
case class HtlcDelayedTx(input: InputInfo, tx: Transaction) extends TransactionWithInputInfo { override def desc: String = "htlc-delayed" }
sealed trait ClaimHtlcTx extends TransactionWithInputInfo { def htlcId: Long }
case class ClaimHtlcSuccessTx(input: InputInfo, tx: Transaction, htlcId: Long) extends ClaimHtlcTx { override def desc: String = "claim-htlc-success" }
case class LegacyClaimHtlcSuccessTx(input: InputInfo, tx: Transaction, htlcId: Long) extends ClaimHtlcTx { override def desc: String = "claim-htlc-success" }
case class ClaimHtlcSuccessTx(input: InputInfo, tx: Transaction, paymentHash: ByteVector32, htlcId: Long) extends ClaimHtlcTx { override def desc: String = "claim-htlc-success" }
case class ClaimHtlcTimeoutTx(input: InputInfo, tx: Transaction, htlcId: Long) extends ClaimHtlcTx { override def desc: String = "claim-htlc-timeout" }
sealed trait ClaimAnchorOutputTx extends TransactionWithInputInfo
case class ClaimLocalAnchorOutputTx(input: InputInfo, tx: Transaction) extends ClaimAnchorOutputTx with ReplaceableTransactionWithInputInfo { override def desc: String = "local-anchor" }
Expand Down Expand Up @@ -528,14 +529,14 @@ object Transactions {
txIn = TxIn(input.outPoint, ByteVector.empty, sequence) :: Nil,
txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil,
lockTime = 0)
val weight = addSigs(ClaimHtlcSuccessTx(input, tx, htlc.id), PlaceHolderSig, ByteVector32.Zeroes).tx.weight()
val weight = addSigs(ClaimHtlcSuccessTx(input, tx, htlc.paymentHash, htlc.id), PlaceHolderSig, ByteVector32.Zeroes).tx.weight()
val fee = weight2fee(feeratePerKw, weight)
val amount = input.txOut.amount - fee
if (amount < localDustLimit) {
Left(AmountBelowDustLimit)
} else {
val tx1 = tx.copy(txOut = tx.txOut.head.copy(amount = amount) :: Nil)
Right(ClaimHtlcSuccessTx(input, tx1, htlc.id))
Right(ClaimHtlcSuccessTx(input, tx1, htlc.paymentHash, htlc.id))
}
case None => Left(OutputNotFound)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import scodec.bits.{BitVector, ByteVector}
import scodec.codecs._

import java.util.UUID
import scala.concurrent.duration._

/**
* Those codecs are here solely for backward compatibility reasons.
Expand Down Expand Up @@ -137,7 +136,7 @@ private[channel] object ChannelCodecs0 {
.typecase(0x01, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[CommitTx])
.typecase(0x02, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | provide(0L))).as[HtlcSuccessTx])
.typecase(0x03, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L))).as[HtlcTimeoutTx])
.typecase(0x04, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L))).as[ClaimHtlcSuccessTx])
.typecase(0x04, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L))).as[LegacyClaimHtlcSuccessTx])
.typecase(0x05, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L))).as[ClaimHtlcTimeoutTx])
.typecase(0x06, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimP2WPKHOutputTx])
.typecase(0x07, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimLocalDelayedOutputTx])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import fr.acinq.eclair.channel._
import fr.acinq.eclair.crypto.ShaChain
import fr.acinq.eclair.transactions.CommitmentSpec
import fr.acinq.eclair.transactions.Transactions._
import fr.acinq.eclair.{Feature, FeatureSupport, Features, channel}
import fr.acinq.eclair.{Feature, Features, channel}
import scodec.bits.BitVector

private[channel] object ChannelTypes0 {
Expand Down Expand Up @@ -77,7 +77,7 @@ private[channel] object ChannelTypes0 {
// the channel will put a watch at start-up which will make us fetch the spending transaction.
val irrevocablySpentNew = irrevocablySpent.collect { case (outpoint, txid) if knownTxs.contains(txid) => (outpoint, knownTxs(txid)) }
val claimMainOutputTxNew = claimMainOutputTx.map(tx => ClaimP2WPKHOutputTx(getPartialInputInfo(commitTx, tx), tx))
val claimHtlcSuccessTxsNew = claimHtlcSuccessTxs.map(tx => ClaimHtlcSuccessTx(getPartialInputInfo(commitTx, tx), tx, 0))
val claimHtlcSuccessTxsNew = claimHtlcSuccessTxs.map(tx => LegacyClaimHtlcSuccessTx(getPartialInputInfo(commitTx, tx), tx, 0))
val claimHtlcTimeoutTxsNew = claimHtlcTimeoutTxs.map(tx => ClaimHtlcTimeoutTx(getPartialInputInfo(commitTx, tx), tx, 0))
val claimHtlcTxsNew = (claimHtlcSuccessTxsNew ++ claimHtlcTimeoutTxsNew).map(tx => tx.input.outPoint -> Some(tx)).toMap
channel.RemoteCommitPublished(commitTx, claimMainOutputTxNew, claimHtlcTxsNew, Nil, irrevocablySpentNew)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ private[channel] object ChannelCodecs1 {
.typecase(0x01, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[CommitTx])
.typecase(0x02, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | provide(0L))).as[HtlcSuccessTx])
.typecase(0x03, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L))).as[HtlcTimeoutTx])
.typecase(0x04, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L))).as[ClaimHtlcSuccessTx])
.typecase(0x04, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L))).as[LegacyClaimHtlcSuccessTx])
.typecase(0x05, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L))).as[ClaimHtlcTimeoutTx])
.typecase(0x06, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimP2WPKHOutputTx])
.typecase(0x07, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimLocalDelayedOutputTx])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ private[channel] object ChannelCodecs2 {
val htlcSuccessTxCodec: Codec[HtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow)).as[HtlcSuccessTx]
val htlcTimeoutTxCodec: Codec[HtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow)).as[HtlcTimeoutTx]
val htlcDelayedTxCodec: Codec[HtlcDelayedTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[HtlcDelayedTx]
val claimHtlcSuccessTxCodec: Codec[ClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow)).as[ClaimHtlcSuccessTx]
val claimHtlcSuccessTxCodec: Codec[LegacyClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow)).as[LegacyClaimHtlcSuccessTx]
val claimHtlcTimeoutTxCodec: Codec[ClaimHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow)).as[ClaimHtlcTimeoutTx]
val claimLocalDelayedOutputTxCodec: Codec[ClaimLocalDelayedOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimLocalDelayedOutputTx]
val claimP2WPKHOutputTxCodec: Codec[ClaimP2WPKHOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimP2WPKHOutputTx]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
package fr.acinq.eclair.wire.internal.channel.version3

import fr.acinq.bitcoin.DeterministicWallet.{ExtendedPrivateKey, KeyPath}
import fr.acinq.bitcoin.{OutPoint, Transaction, TxOut}
import fr.acinq.bitcoin.{ByteVector32, OutPoint, Transaction, TxOut}
import fr.acinq.eclair.channel._
import fr.acinq.eclair.crypto.ShaChain
import fr.acinq.eclair.transactions.Transactions._
Expand Down Expand Up @@ -128,7 +128,8 @@ private[channel] object ChannelCodecs3 {
val htlcSuccessTxCodec: Codec[HtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow)).as[HtlcSuccessTx]
val htlcTimeoutTxCodec: Codec[HtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow)).as[HtlcTimeoutTx]
val htlcDelayedTxCodec: Codec[HtlcDelayedTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[HtlcDelayedTx]
val claimHtlcSuccessTxCodec: Codec[ClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow)).as[ClaimHtlcSuccessTx]
private val legacyClaimHtlcSuccessTxCodec: Codec[LegacyClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow)).as[LegacyClaimHtlcSuccessTx]
val claimHtlcSuccessTxCodec: Codec[ClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow)).as[ClaimHtlcSuccessTx]
val claimHtlcTimeoutTxCodec: Codec[ClaimHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow)).as[ClaimHtlcTimeoutTx]
val claimLocalDelayedOutputTxCodec: Codec[ClaimLocalDelayedOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimLocalDelayedOutputTx]
val claimP2WPKHOutputTxCodec: Codec[ClaimP2WPKHOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimP2WPKHOutputTx]
Expand All @@ -144,7 +145,7 @@ private[channel] object ChannelCodecs3 {
.typecase(0x01, commitTxCodec)
.typecase(0x02, htlcSuccessTxCodec)
.typecase(0x03, htlcTimeoutTxCodec)
.typecase(0x04, claimHtlcSuccessTxCodec)
.typecase(0x04, legacyClaimHtlcSuccessTxCodec)
.typecase(0x05, claimHtlcTimeoutTxCodec)
.typecase(0x06, claimP2WPKHOutputTxCodec)
.typecase(0x07, claimLocalDelayedOutputTxCodec)
Expand All @@ -156,6 +157,7 @@ private[channel] object ChannelCodecs3 {
.typecase(0x13, claimRemoteDelayedOutputTxCodec)
.typecase(0x14, claimHtlcDelayedOutputPenaltyTxCodec)
.typecase(0x15, htlcDelayedTxCodec)
.typecase(0x16, claimHtlcSuccessTxCodec)

val claimRemoteCommitMainOutputTxCodec: Codec[ClaimRemoteCommitMainOutputTx] = discriminated[ClaimRemoteCommitMainOutputTx].by(uint8)
.typecase(0x01, claimP2WPKHOutputTxCodec)
Expand All @@ -170,8 +172,9 @@ private[channel] object ChannelCodecs3 {
.typecase(0x02, htlcTimeoutTxCodec)

val claimHtlcTxCodec: Codec[ClaimHtlcTx] = discriminated[ClaimHtlcTx].by(uint8)
.typecase(0x01, claimHtlcSuccessTxCodec)
.typecase(0x01, legacyClaimHtlcSuccessTxCodec)
.typecase(0x02, claimHtlcTimeoutTxCodec)
.typecase(0x03, claimHtlcSuccessTxCodec)

val htlcTxsAndRemoteSigsCodec: Codec[HtlcTxAndRemoteSig] = (
("txinfo" | htlcTxCodec) ::
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat
}

private def removeHtlcId(claimHtlcTx: ClaimHtlcTx): ClaimHtlcTx = claimHtlcTx match {
case claimHtlcTx: LegacyClaimHtlcSuccessTx => claimHtlcTx.copy(htlcId = 0)
case claimHtlcTx: ClaimHtlcSuccessTx => claimHtlcTx.copy(htlcId = 0)
case claimHtlcTx: ClaimHtlcTimeoutTx => claimHtlcTx.copy(htlcId = 0)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@
*/
package fr.acinq.eclair.wire.internal.channel.version3

import fr.acinq.bitcoin.Satoshi
import fr.acinq.bitcoin.{ByteVector32, Satoshi}
import fr.acinq.eclair.FeatureSupport.{Mandatory, Optional}
import fr.acinq.eclair.Features.{ChannelRangeQueries, PaymentSecret, VariableLengthOnion}
import fr.acinq.eclair.channel._
import fr.acinq.eclair.transactions.Transactions.{ClaimHtlcSuccessTx, LegacyClaimHtlcSuccessTx}
import fr.acinq.eclair.wire.internal.channel.ChannelCodecsSpec.normal
import fr.acinq.eclair.wire.internal.channel.version3.ChannelCodecs3.Codecs.{DATA_NORMAL_Codec, channelConfigCodec, remoteParamsCodec}
import fr.acinq.eclair.wire.internal.channel.version3.ChannelCodecs3.Codecs._
import fr.acinq.eclair.wire.internal.channel.version3.ChannelCodecs3.stateDataCodec
import fr.acinq.eclair.{CltvExpiryDelta, Features, MilliSatoshi, UInt64, randomKey}
import org.scalatest.funsuite.AnyFunSuite
Expand Down Expand Up @@ -124,4 +125,30 @@ class ChannelCodecs3Spec extends AnyFunSuite {
assert(decoded1 === decoded2)
}

test("backwards compatibility with legacy claim-htlc-success transactions") {
// We can decode old data that didn't contain a payment hash.
val oldTxWithInputInfoCodecBin = hex"0004 24020bc3ab58d19af4aac858786c2cbec2e8f9c43a3bd1ce4a51f6b0e1e1b17ffd000000002bb0ad010000000000220020e63b4729b67c90212244953d1c9eb15da73dffb035eaef21a5fd883c5968145b8576a914a0fc54aee923e51ceb5d37283b6f263a571cae428763ac672103ea61bef3c6ef05f1e65aaea2020c786e6fc923102c8fe0e53a6fe0315da2e62e7c820120876475527c21023b468606d008f702a9ad940c5fb8539f5b66cf2a3d0a7baa4960377dda9a61d152ae67a91452d220bcd80633ae134ec71b1bd8e79dc1490b9388ac6868fd014302000000000101020bc3ab58d19af4aac858786c2cbec2e8f9c43a3bd1ce4a51f6b0e1e1b17ffd0000000000ffffffff016297010000000000160014b95a632df5806773f328bb583f973fe5cb05e7a303463043022045676e573cc8314baad6038f2655f106c5cff1d968cd2373ea1a746cb764ffd5021f4c373a06f917f4ed606c9abb3cb9e770c215bc77480c95c6d36e90edea31680120f992c96b4fe64f43cd031c52be0bc39d6c9bf6fc2712a5a9c310dbd58872a9fc8576a914a0fc54aee923e51ceb5d37283b6f263a571cae428763ac672103ea61bef3c6ef05f1e65aaea2020c786e6fc923102c8fe0e53a6fe0315da2e62e7c820120876475527c21023b468606d008f702a9ad940c5fb8539f5b66cf2a3d0a7baa4960377dda9a61d152ae67a91452d220bcd80633ae134ec71b1bd8e79dc1490b9388ac6868000000000000000000000000"
val oldClaimHtlcTxBin = hex"01 24020bc3ab58d19af4aac858786c2cbec2e8f9c43a3bd1ce4a51f6b0e1e1b17ffd000000002bb0ad010000000000220020e63b4729b67c90212244953d1c9eb15da73dffb035eaef21a5fd883c5968145b8576a914a0fc54aee923e51ceb5d37283b6f263a571cae428763ac672103ea61bef3c6ef05f1e65aaea2020c786e6fc923102c8fe0e53a6fe0315da2e62e7c820120876475527c21023b468606d008f702a9ad940c5fb8539f5b66cf2a3d0a7baa4960377dda9a61d152ae67a91452d220bcd80633ae134ec71b1bd8e79dc1490b9388ac6868fd014302000000000101020bc3ab58d19af4aac858786c2cbec2e8f9c43a3bd1ce4a51f6b0e1e1b17ffd0000000000ffffffff016297010000000000160014b95a632df5806773f328bb583f973fe5cb05e7a303463043022045676e573cc8314baad6038f2655f106c5cff1d968cd2373ea1a746cb764ffd5021f4c373a06f917f4ed606c9abb3cb9e770c215bc77480c95c6d36e90edea31680120f992c96b4fe64f43cd031c52be0bc39d6c9bf6fc2712a5a9c310dbd58872a9fc8576a914a0fc54aee923e51ceb5d37283b6f263a571cae428763ac672103ea61bef3c6ef05f1e65aaea2020c786e6fc923102c8fe0e53a6fe0315da2e62e7c820120876475527c21023b468606d008f702a9ad940c5fb8539f5b66cf2a3d0a7baa4960377dda9a61d152ae67a91452d220bcd80633ae134ec71b1bd8e79dc1490b9388ac6868000000000000000000000000"
val legacyClaimHtlcSuccess = {
val claimHtlcSuccessTx1 = txWithInputInfoCodec.decode(oldTxWithInputInfoCodecBin.bits).require.value
val claimHtlcSuccessTx2 = claimHtlcTxCodec.decode(oldClaimHtlcTxBin.bits).require.value
assert(claimHtlcSuccessTx1 === claimHtlcSuccessTx2)
assert(claimHtlcSuccessTx1.isInstanceOf[LegacyClaimHtlcSuccessTx])
claimHtlcSuccessTx1.asInstanceOf[LegacyClaimHtlcSuccessTx]
}

// We can encode data that contains a payment hash.
val claimHtlcSuccess = ClaimHtlcSuccessTx(legacyClaimHtlcSuccess.input, legacyClaimHtlcSuccess.tx, ByteVector32(hex"0101010101010101010101010101010101010101010101010101010101010101"), legacyClaimHtlcSuccess.htlcId)
val txWithInputInfoCodecBin = txWithInputInfoCodec.encode(claimHtlcSuccess).require.bytes
assert(txWithInputInfoCodecBin !== oldTxWithInputInfoCodecBin)
val claimHtlcTxBin = claimHtlcTxCodec.encode(claimHtlcSuccess).require.bytes
assert(claimHtlcTxBin !== oldClaimHtlcTxBin)

// And decode new data that contains a payment hash.
val decoded1 = txWithInputInfoCodec.decode(txWithInputInfoCodecBin.bits).require.value
assert(claimHtlcSuccess === decoded1)
val decoded2 = claimHtlcTxCodec.decode(claimHtlcTxBin.bits).require.value
assert(claimHtlcSuccess === decoded2)
}

}