From 72a7d7e2c753b64143b7bb44361dd31264c3af07 Mon Sep 17 00:00:00 2001 From: t-bast Date: Fri, 13 Jun 2025 15:40:37 +0200 Subject: [PATCH 1/6] Add more channel codecs v4 tests Before introducing channel codecs v5, we add more test data encoded with the v4 codecs, especially for the fields we intend to change. --- .../channel/version4/ChannelCodecs4Spec.scala | 104 +++++++++++++++++- 1 file changed, 99 insertions(+), 5 deletions(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4Spec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4Spec.scala index 3ea77fc4fd..3c7851e030 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4Spec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4Spec.scala @@ -1,13 +1,12 @@ package fr.acinq.eclair.wire.internal.channel.version4 import com.softwaremill.quicklens.ModifyPimp -import fr.acinq.bitcoin.scalacompat.Crypto.PrivateKey -import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, DeterministicWallet, OutPoint, Satoshi, SatoshiLong, Script, TxId, TxOut} +import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} +import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, DeterministicWallet, OutPoint, Satoshi, SatoshiLong, Script, Transaction, TxId, TxOut} import fr.acinq.eclair.FeatureSupport.{Mandatory, Optional} import fr.acinq.eclair.Features.{ChannelRangeQueries, PaymentSecret, VariableLengthOnion} import fr.acinq.eclair.TestUtils.randomTxId import fr.acinq.eclair.blockchain.fee.{FeeratePerByte, FeeratePerKw} -import fr.acinq.eclair.channel.LocalFundingStatus.DualFundedUnconfirmedFundingTx import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.fund.InteractiveTxBuilder.{InteractiveTxParams, PartiallySignedSharedTransaction, RequireConfirmedInputs, SharedTransaction} import fr.acinq.eclair.channel.fund.InteractiveTxSigningSession.UnsignedLocalCommit @@ -163,7 +162,7 @@ class ChannelCodecs4Spec extends AnyFunSuite { // check that our codec will set the pubkeyscript using the one from the funding params val channelId = ByteVector32.Zeroes val script = Scripts.multiSig2of2(PrivateKey(ByteVector.fromValidHex("01" * 32)).publicKey, PrivateKey(ByteVector.fromValidHex("02" * 32)).publicKey) - val dualFundedUnconfirmedFundingTx = DualFundedUnconfirmedFundingTx( + val dualFundedUnconfirmedFundingTx = LocalFundingStatus.DualFundedUnconfirmedFundingTx( PartiallySignedSharedTransaction( SharedTransaction( // we include the correct pubkey script here @@ -195,6 +194,25 @@ class ChannelCodecs4Spec extends AnyFunSuite { test("decode dual-funded unsigned local commit") { val bin = ByteVector.fromValidHex("00130d411da7b37693960ef0a495ffa8c0fcbf82c1fd8034ab3bf37f8a184242b7230101041000100002aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa0009f22f74ef88d63eb6ece1076957feb94a7cf7f20b07f3ee710a9f2ac024d3872180000001000000000000044c000000001dcd6500000000000000000000900064c0000392a15254566846b06ea693dbeedb1a6ded24b02d53ee40f091f628dc117a1a5d0000186b02000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000001808220a598202bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e6300000000000003e8000000003b9aca0000000000000003e80090001e021ef6e2ee1a5d15aba3c663f1b608ed66315fa8a855b91f53eefa8debec663b5e0392a15254566846b06ea693dbeedb1a6ded24b02d53ee40f091f628dc117a1a5d0289365d47702d068083d9444c9167be1addfe353d9466a9805215c89361aaf85903e60c20e9744d7a3ff07a72c0170569a9841ea2fb6e0dac7d90ee550a491e931b0000001408000000000000000000000000001008228a598200000262866d2d58787368382a8fc145e34c08f5fbfd54d582b582e088de5e32fd8736000000000000000000000000000000000d411da7b37693960ef0a495ffa8c0fcbf82c1fd8034ab3bf37f8a184242b723ff00000000000f4240000000000007a120000230d9b88fad260907c43f97b88bb9992bc334ef29015cae8d7a8aa5063539931d000000061a80000000000000044c000027100000000000000002000000000000000422002059106401877b19606b711f9980431fd80e830d2a30c59103d7f12e16a7e141d3000000003b9aca00000000001dcd6500000000000000000000010100000000000000005202000000014a12a0174ed233d1e65150f14ccb948a0508dbeaaa49236dce4c4fde7b69d31201000000000000000001e0c81000000000001600149e0bb5d89fbd10f3054bdde0fa3e22d42d1e7abc000000000000000000000000000101000000000000000124e3b1062400aaf012a8e25bb0932a3de9ab16a1accf7302b333f6020dfea5a22b000000002bc0270900000000002251202ee2991cf9a98cb8be01ed4ebc8279bcd84536bed6f73f4dd0012d1927d5d8250000000000010100000000000000020000000000016ec21600149e0bb5d89fbd10f3054bdde0fa3e22d42d1e7abc000101000000000000000300000000000176d82251202ee2991cf9a98cb8be01ed4ebc8279bcd84536bed6f73f4dd0012d1927d5d82500061a80af0d411da7b37693960ef0a495ffa8c0fcbf82c1fd8034ab3bf37f8a184242b723b0e1b56831c5dd8ef32c85df9896c057c36a7e8d3c95aeba8c9a4da79b0aa1430001006b0247304402203f4fdd467e2d27c89946d2851004c01988294b0071748c4acd315c90b9ab0b1902202c86d68228cf322d4b676ece89a5106d2518565cb859c436b9284d78f81b4d1201210392a15254566846b06ea693dbeedb1a6ded24b02d53ee40f091f628dc117a1a5d000000000000000000000000002710000000003b9aca00000000001dcd650024b0e1b56831c5dd8ef32c85df9896c057c36a7e8d3c95aeba8c9a4da79b0aa143020000002b60e316000000000022002059106401877b19606b711f9980431fd80e830d2a30c59103d7f12e16a7e141d3007d0200000001b0e1b56831c5dd8ef32c85df9896c057c36a7e8d3c95aeba8c9a4da79b0aa1430200000000a9d907800220a10700000000001600149e0bb5d89fbd10f3054bdde0fa3e22d42d1e7abcf8250f00000000002200203737db5c8aa01d33fd415cabd8b8680a16527c8d9c6fe32ab5cedbe610484aa187c9772000000000000000000000000000002710000000001dcd6500000000003b9aca007c6fd871c702806ae018d8609df56a1705d275b99994bb3563878d554fed62f4039fa3697fcaf99e44e3a5b601600667e00d22e940cba46708e3bf0beae9fda8e20000") val decoded = channelDataCodec.decode(bin.bits).require.value.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_SIGNED] + assert(decoded.channelParams.commitmentFormat == DefaultCommitmentFormat) + assert(decoded.channelParams.channelFeatures.features == Set(Features.DualFunding, Features.StaticRemoteKey)) + // Local params. + assert(decoded.channelParams.localParams.isChannelOpener) + assert(decoded.channelParams.localParams.toSelfDelay == CltvExpiryDelta(144)) + assert(decoded.channelParams.localParams.dustLimit == 1100.sat) + assert(decoded.channelParams.localParams.htlcMinimum == 0.msat) + assert(decoded.channelParams.localParams.maxAcceptedHtlcs == 100) + assert(decoded.channelParams.localParams.maxHtlcValueInFlightMsat == 500_000_000.msat) + // Remote params. + assert(decoded.channelParams.remoteParams.toSelfDelay == CltvExpiryDelta(144)) + assert(decoded.channelParams.remoteParams.dustLimit == 1000.sat) + assert(decoded.channelParams.remoteParams.htlcMinimum == 1000.msat) + assert(decoded.channelParams.remoteParams.maxAcceptedHtlcs == 30) + assert(decoded.channelParams.remoteParams.maxHtlcValueInFlightMsat == UInt64(1_000_000_000)) + // Signing session. + assert(decoded.signingSession.fundingTxIndex == 0) + assert(decoded.signingSession.commitInput.outPoint == OutPoint(TxId.fromValidHex("43a10a9ba74d9a8cbaae953c8d7e6ac357c09698df852cf38eddc53168b5e1b0"), 2)) + assert(decoded.signingSession.commitInput.txOut == TxOut(1_500_000 sat, hex"002059106401877b19606b711f9980431fd80e830d2a30c59103d7f12e16a7e141d3")) assert(decoded.signingSession.localCommit.isLeft) val unsignedLocalCommit = decoded.signingSession.localCommit.left.toOption.get assert(unsignedLocalCommit.index == 0) @@ -205,8 +223,27 @@ class ChannelCodecs4Spec extends AnyFunSuite { test("decode splice unsigned local commit") { val bin = ByteVector.fromValidHex("001801d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d230101041000100002bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e630009439468f0eab4a6375ce444fc12eaf57df47aba7e2644be598e5249349f8172218000000000000000000003e8000000003b9aca0000000000000003e80090001e000003fcfbfa87a789958625832770826a2e391b87880a5698e601033942c75173460a0000001408000000000000000000000000001008228a598202aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa000000000000044c000000001dcd6500000000000000000000900064027800eedc36e641dff2b5cd5041895a8dbff6b52bf1d8fe58511fe0b2ae60c19503fcfbfa87a789958625832770826a2e391b87880a5698e601033942c75173460a03c6804903002dbb70e488fb53009ea0a024f87acafda1d96f8a0f1fd2f98b3e1302a26a217b4263be92199009db2b503e48272c0600d753a97ae34bd9827251c6700000186b02000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000001808220a59820000000000000000000000000000000000000000000200000000000000020004fffd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000000000000000e4e1c0342bb8ca8412a52cbaa04072e0e285ff414a4bf36be1828482705a6bd13501a200061b100003e368b6d07bc35ff7e3475feddeb313912771046d96fa86ec9a5ba2e9024f5df44c2609468b1f86640de3863c1e3ff60042667392d43809462bc3d18d454ad70de3d85e738909aaa1930559a093e363449e7a00c85cb62562298a108131f6f88635d990d4a6de980d076bfee5281d62828c19da4db7843c304c6d2fe742a8421ffb46c26b5e06249fb8fb4ff0b4d1f08d44d8d7d87953db3da6154f8d31d5af9780f6e45f4867fc7d77a1e438dd87172758779414784275dd05b88eafca27ed4424f1249c14d0894202377b2a394e3cc063badd9bd57f66122bb391fff3718520fc9dffa0b225b1eba27be4f0a386aeda91f85b126771f356afa9d1c9aeed249ac100ece76fc8b32903eaa9caeb2c0e3e6d05f69ad025d7ba625d536e6ad06d8b77aceb6d7a01dbb17adee7e17eca234b251e353e26de28849876b8cf88bd57f4a04745d6a88d32f00b35afdf416efd8f84826840e9e974cbd8aa4a102507f0c7e441ad9e1c1275ccc89266bde55f4a3582b78fa8aafa1994ef36b35d3943d3cd326bc70a3ddd377e895b498d79cbc857b5bb2e61f30ac8ca48fe186e40326fd9fef0215e2ef31844bcaec6334490a89dcd42219d64bfeacec7978294ac964b66b49958130a77f7569b083b1c280d4e451e872403454a3947d2cb10ad2f96f92b070f886a5ed771332f51889d18cc2ad941c6638eebefe5fe6cc4b79468b1e469adb08c34982783d49be95f32a774f51db6788d34f26a8af1b31fc709d78338c6c4d9834fa25a910b4b63e05aa6d383d282bba31c9378e3f27f985088e682e11a46a9a1c8e18be9b7c7ef02d9faa8bb08a53d5ffe059d48d440c6364292fb42df68a04aacdfaaba029f1460912b11a6f8eb57578832fb3c10675c7989114177bdb3b587a80856f351177d01eefbb1de210839c9b8b9ba621942f250dc6b4bbf014899b77f52e8e40cb06f7063371251c419bb8aa9d3c0bd83ce651520b6e2911dd38db6c0fff7cc64550320c5cac621765175460cc8f54ee4a0026ec8e57fd789e0fef66c4940cabc3944ff4e3fe10229294e6fcb3059adb4f9868e53b52a7da60f5ae75d4e42e6a94c44e375960f9ed44a56c0b6c49e7c5775dcc1a819547bf3f146e8bdb13f305ea0d15b5616623db4c0657059d07df7acf1062069705ae5699ce86d9d0de16f3afae97f5327fd340693f756aba0bf338af880927cd7f976705c2b511077bbd19b9a842cb7a9b2f713f183c1e4eac23bee3ac081be66de275451773444e81536e183c8e2f033766b854cc92773205abb0f39d90e762c1f0673af8e4051205f752757d2deb7dce6adf8a55297a3a56fe1265368970e8d480b8ee61fa38313e5a7069579f10ac2db37c05c58b38544a8750e186c43a153c7780cb8ddfa309fac85b487b43b1452624afbc4005bbc0777eda63c96edc94016d1fe880dcb2faab472802921adc29dd3410628b8431186a7ef3a6839eb16123ffccf40c4c0907764e40d1dabbaa12a3a9ed3f341c070fb622600b6f4595f4601f67f2d03e7221d57a20bad2e0fcde738107a57fea1e1257a89970bc94517fecfe4990c67f7bfde2ad523f0f4dc760e0e8958eb32c6a7b0e7d952e75fdf08550ab72b6824122697846e599c8a2cd7f3066c8c444e2bb41aa38cb063493af2106eeae5228247706a53169f306d6b0d74867db69d29e9a6eca2b77aeb3353fa5a593b9f4fa379ffd01464a6929841f0fe934d68c8ae9e523a87d02ce5df5636cc5dcd72536d044cedd2a1d1e9047712a399627a44c8776cc5d0b2fa7e3dbef52102602f97090afab7a6070af4a1e50d19fb646f25212a7e750d6d0f765c0d5324721afe49d1070a686b05dafd39114bc65cc11cb30cda09b872c79521273ff2f075ebc4a2e5444c6880b3854d623bf1c42be1026d8b0409fe0001a1470107fffd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000010000000000e4e1c0175abe3cc14d94456c949372ed6ace0b5b92e58c595a6297e6c1cdfbe5bd750a00061b1000025b15002f7b27451dba144086e3ccebd9726c2e1af20883b676631e13908f2a8a0dcabd2fe4d39cd5b0c9d007a4dc3f1a5624208693e461d934fcbb48428eb1cfba41682d19ab1221e11648459eee58b855ea196e854047d82869b62b2efc978f520eb13e35e3752e53026a0f7b22ddf65de7a4e3fea56fc86b9c85cb8df4cfd0262cf3bbe1e16eb25574db8fd56cee33cbfdb73073f700b9992c19ac808354db41e488aa31d6b8a3be8d151732aaf6353f30a18cd773285cddbd3b279e6455f9b90aa37d7055ccd9fdafa66ab0ad216fd123e4a4aadb25f9e004c5a8e794abced52cd6e208d8d2657d03f5179b6353133a9eaf151cf5d47298058ad85ada4e42d29b9c61524ac20264d3b6551efef60789c6a91cf1ce4e069e3ad123fba8d515d705c0e2f3aec108ab317116d593100a4db4affd32b4091a1368cfe3c91b422ff53c75ade0ed61dd562e55cd674e5a0c5ca5952c2204c401d9c61c25b565c83c38cc09eaa6e25c42d234875f2a353813b8db46f031f0392f73a9cc8a88925347e04cfb477f3a7c5578edaa447927c048aa631b4f3dafcb608fafef2e125a549e029f2598cae566f93febee55ab1175359913732673a44fd2a8e28f71e281917c7d0fc04816a5774a3411d1db8f290fe442e3d3141ab366633f07e46528d44528a422c9225129b0ae4e7f3b76c9c8fb25b9c7722be9b5862ce866980003752c178e7a94224a2b0b84fa1e2635b5e2207ade589baa96372d4453868b3e01e9ac2d1d80bc0c7e06a22c205f85e578d55e3283e9b2defb70f46a6084ea40e92b25f35550e7f3fc3c9e764dd4eea7b85df10175a0799e6a46cae937ddb1fbc43795a88850870cdd0f5bfb4506f227b1420f6e8d39b7ccbdc888a7df7066cfcd8da6ba6bceb2aa80bf14fd5d096f604af41ae92b9f4e3c6a7f29208e8b3fb70cf81fd56ff17ee56adf35cb18b39a067f67f9fc3f94e8c30d44760da2322a77679bd6ca0da0e47f206ef7b644448e7330bf19455ca777418896f0e07e551ae4995c7ac5026a7b84c0cd07baf9569135ae4cc3893339ac886ab704b37d46aa3edcfeaa6c4c8aec3ec8bf8ffb51a591ff02f04367314c267a3152070069950d384b99235e5398547d5084c4afbe86ce6a6a60964b8cd89b20c690e8de12606f64ea559a36431dc98d6189a3f15d439dcafd831a9dab64bf8dd5722c91d59fab00fad5b27a89d5f4237cb20ca55648a8256e8a0ba18394608840336d15a84451821619a2363eb51c5ee83e1722476106248962ccb5f7bd843e7dd14620a2b40107d972fa6b1e4ed7bd6b13b659b012870b64906229c93c2625b02da587d2239e09d64bec81f40c50398eb666ff8912711c2377165cbb0ad16999c0b09bc36f354cd495d63fc6cd7b89e4063ff1b63b319afa4a7a64da69aa41ba9af3131df5cb384ce68ab74898d699ea09730b53d3d862521927b0cde151844a9eb11aef8e0e25a3fb607d57c2a9f83eaf01f1c387e09be5fc5ac0eb091359ca26c8fb36cea3ccae8a63e57dcbc60ee66975bfee8964cfdfc840b114dc6ed5eac0babd26f99ec251c45ec602a44fcc6965c0d7c065c186569be6426658b8a5b32fc2dee315a0457e963635795a0d699db9587eeacf8f893699410a87b6390d8c8736552e325f5ccec1b6d4b45002b678649b234301610ecd605f9e56c5dc45d81cccf73e442b872369cbbd3a03eaba79c0af36e23ed97cf73808dc5ed0f5fcd751772b57e2666c5a70b1a5af61a0dd461e8a383e8426369ee6a4e03cf256a6bc3570997c3c06879946ffdd2924aa329e006759c0d6b564c89198c0c09f88c2c9d0c2cd4e0163e8f98145456cc859f55aa8d11a9969ae253d93d56d9fc6a386ae7c6ee884f483913bd739124f69b4bd5238fdc33d860a9cd4670de5dafe4a24fe0001a147010700fd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000000000000001312d00af8cea7a0a1be6687852d7f2249be3b445ae0cddc097a05ea468ef797567856d00061b100003e5507553c137054ae33b5d7cdaf142fbdbfae51246ea3db4441251aa49dac2e8cac063650741353682da85f0df1b1db72997177b74940bcabcb961f364c1861a493066a9cf2533aca5f866b280419fe04f9e9807d3049b759f78d7ce322554ed7891184e73d934c62f88ca33f7cd75d56617664cc674d74f553b8674093e60f014a89e1ab4ea7adde5d54f28a9976d2a3ddb20854b3a1f3ecac8490a9243234c7ce9c5df1b0e45d862c847fd93b99ed1f4d5fd5b06ca416ed048433782d4bd867d36196a7ebe1106f687d0868cd2f2cc1fd94f999de8e59e9a9b333aac6304ac7c01cb91d20c09d4e4f38397ccc1a0cbb46865b7292b84d4f11a783abb0d5da2e31fdc7c2885c6c90053f716264d0b8ba1ce53dc72e8f04e702e319029ca234b8bf72f5321382723010153eaf113e0c823914bf6ea319a1083efe2f0736e1f9b36125c4b77b9e4a581e6fecec959be70f2e21b8c9bd568641d45feb3ffdd39491abc973f310cb1f9c46a874471e5edc3ea7d4063274a9e10e2d21a9e836e99d386d062ac31bf19a14ad6d39c19752eb749c4dacc64aa4591228c039a1f3c0dfd919a2576edf70cbe018075d90c94bebbb91290487cb9cc613eef312a87025891ea6d8b02435b26abf72fea504aad333100e4f14ee916c678c6c9a42b3421bb8f7389b642f89b5abaf99f0df25e5859bee216c3845bbc9fdb032acb88cc144306cc17fe754280bccfecd86670d6994833c4847c71913caed6d904525d22d28aadf3d4e867a617f20a9cf61e95152eb1cab90bae7885da01effee11b7780bd2398d37f04096e7d14252e2026f329422c077762996f45a7c4d546d7ad0422996e659c98f1feebc5c73eeecab837c025a44d94cb5ee39d5a1bbd95973e8e5cdd97cabb7b8f4f86a5bb228c0d12be6b9b2032c54795f4f7180828e94377bf6e0f56545dfcd5252da0936d79aef70caa8715e8416579ca1a928c2aecfa1e03b6cd795528a967fb3dbe524996be92b6168222112063077fb39811ebf9dbb97abafe1b315488c8812224faef56c1eb86837ffcf61bc22a60de7e4f6e0df67c332389556df7da838200dcceeafcb6e8a94aaad013c30934ec0ada06fa17afb5414de4812deb6fa96517df57d503e1e521562ed62a65073252dd08aafba343c029e04faf58b4c451061f156669be5ef68a2adff506cba6351e407d3c6077b71807f21e93d0ca69843f2d5fd6b7c7f415d740fac1f1f8c8bd8f95297ee1ffafbe1b41c548b69dd6d211352c556f14bb4e5af76244e7656f882452201e2bfd90a87413904081a78c2d6849a6a07ad77920465141da19034b70c3edac53a560e7b5c07923c9bc6e17b8091257fe7431e7cb18d770ba1457e62714a47beb8e503874f24405c49121d2cb024edee3458b31ebe1bf40a13f564b8ff95dc537ec926311f59150544fcc57efcef53d9f43c41b0dc32f93b91d2663e9f6b09f9483b4a788b0642d667c722d10ddf09a70886153fc8c943a9d6f3eecec3ab7144669c10b3c5864a174c7343a74e4b58da29f0ae480d5be8adb5d5b55e9cc3c11da1a935c2b1cb966d85612b6c4bbd42aaaeeebada9f062175f502c2cde7daa345239f99201be98ae989cd8a3cd0610779fd413ef1c6c48cd6e8bcb454b847815016e214f1e6162df1b388503b06bad1e5e35ca424fa1ac4407757504e311f4ee0f4d8e5dce45759a96031460842697d577205255b9ba2bbbe4eb08528a9224087a40129906655170405ad4491052ba71d6dde4000b7d7154634db60db540a16cc430b5998dc1465bc57b5a4125c967e4fc451d66026b650b9a70c26998f559793809df549ca1d5e730db0759ed40d3a030c8a1c6bccb28b5fd9230c55ef83d23b369938200099274978bcc017a96bd1b506c6d28b46788f9a483f3e0b234fe0001a147010700fd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000010000000000e4e1c0415149b9751963f87c1583f34e4fb1bc5599cb48dc36e0ab744c37fb0630f93e00061b1000033fabe908e0bb47028814d9361e2518822ec95a72c8e611b1562fb595ad452db3272b17a6f9280115fa6e17434cfc1639b3b1795ff6cfe4aaa80b3393100ba4a2308b9e42dbbb3cba55bec53bbf574b9307e2023f3e13e6d0424ce7b4376280ea0d3a187c8585212fe9effee9aa102873a48f1b71a2ae81cc97fdf6075b4ee17716bbcbcd930a23d483f04907dcf89d9d55d62ac6935ce45afe8549de48a363c595876e32e8ba33806671cb54a92a81409f9bc8964983552f709335efb962d17c2bcc348fcf9af666ae7cff45833b4b0449481a6cc869a6cc2d928ccba9499e6415a52c455ebce14cd94a36fa0bf127682e890be81a1dcc77dd2b94f886103d793ffce97430edf04f4b8a0aaf9622b34bfceaef96bd6848717430d2212f2df30105354306fe8d5d452c32e1b0f1872cb7c953295d2794805fcad0c50cebd26b14cc903fd995d4eb397d85d3bbcc92a4b640ec4448a16eaae0941f2f074ea2f292b55c8cd8051f5b43c4f0d14ae04038d06216cf01b344fb3b8b42ae2680d7998c5f24c16813dcfc521525b5bda78bf604edab7edcac3934cbb9e5e1baae044ea136613ac003e86e3caa9f0e93c38c5997a17ab472004726730f79248be824fbe894422e2b07f951d31ccc32c7f513a61c7e0a4f3790faf2dca5fc4afca1a0f14b0a7a07178402f1875430c905250a5d4d8d70521418cca1c82a2b3721831087e342d74bab5395c5099fe8710c667fb463e50f59da0b67d7f8b2a4b47896c84954a82d604a570f41fb1a5a9906592ee050b0cad8fdc652a411f8dd0d4aa5c46540479b202f7752985311671be2fca007d87b2f27f16be6a590357aeb87f9f75d9f2c798599f3e8bc2fe5b1e7b5d4e3a779761d983f52e5d4844c7eabbd4e543436954a9e361f2f099bcb603a94f2cfc1f1409aa50aee8644c5a1222e2df52fd8a1f884c0a1a800c92be47b7bed41ceff4ee44700c88e527b55c0abdd04f86ce1dae36f0c2802bc4cc74e0698092fed0e8ab7a95e1bfdf0b6fd160a7c80b0f1bba8dde89740e4caa6681f1d4c926a49ee2088282d18e3646e4ade293e1b2cb5a0155d3ee662d5c468cb5d2ac7557e7666396336e3608d84c97fb1f48dd8ca7f8528b218eeac9a825b161147849d1c2a1ee461150cf8984f492d02221ea43d994821790f5e7357e615ce721817e89495bc3422b98a7e6d6288d75c3b727d1669742b682560660079d8860ed65022d279ca414cd56e169e1147d27ad808290f7daeb665a949b793ae503c1dc9d3f1ae0e57b02d373357408d881e2354337143f7d0b99dd2adf48e6c4f4983bc1de85345892e246c6edbd9aee3747fcc2efa956e3e89e9f5a9be0401933b51d625e25c8296be8ef13720d15411d029a471387fa2c28a168f72e0802d84b3085937f9a83353e002b39596e1567d712e729aa55e648f87043be90b359d3e1fd439091d3ded3b72c0c0bdac8e3033a2b2d6e4e074094b7ff6eb631e2b62ec36e35ea434154e0189b1b1e8b9b60da771cf0077ff267e7e861045d24d0c3fa57b9408d3e2116bf75ba02b4d8cd78701ca9836074440c6c0c0ccdb2cd490ee616bc6d3f8d10ddaf60dfe77003ea326673193d21032997f8fb77c2393ce7af5f1d3d3abef5550e80c5a4e3b7ae75ae3f79950a2f8e5065e8d8fecd2f195aac557d7a4b4a5d1e0c5f1e60c0005d73df261401196c93384fcc271b0ebf0a9da2adee213319ed1fb5bec02202b4a0778bdd48420e701105a135b8bf2d4f50b6939ca0890174d7ce9852473456059b9b7a85db8e4664fa67dbc2d729080df312f3d8970e28e7304510b4828624279cf789d9deebf08cce0c1cf84ffb2abc52784230a5b488fe167632882be37fd1564c22b0dc89cf86ea5ffe6d7880b7199eecb9957b28ba3035316b763a4c0a0691bfe0001a147010700010000000000000000000000000218d33da90930d78dfda97b94815fd8543f4485d0574727143932171cc4d796ab0afd01810200000000010223c6a41f797be480b870e70fbc0b567935abc812d5b0e9bdb57e9992dbb54d770000000000000000005b11e204242c6a81fdfaf3d00b8825ca2b3eb16548c572ff9d1d8eef653d7e240000000000000000000360e316000000000022002049205995639f7357505aed54f7b392d2662276bc5d8968da78e6d623d9e6c7fc0a77010000000000160014cf85091a7295547ebd2066aecb1a295440a9f6f0906e010000000000225120a4ac7358d683a4a296a84a196286ea8bf7c80683f3ea8c30be932eeb30b4cfac0140ec5f7239b44a84754b02b947a6210e6d76ab2e57ad71a68b4080b3e68e3d3355f426455a86dcd678677d62262352258d5a170621aeca6643b8f94677667fa44102483045022100ff4ba957db4a6f149ed4513bc8cd64c9cc430f4c5810c1d01d73df72926e492e022013495ad376bd22c6683cc334542192411369201e4eaef605b59d34403c1f5de0012103fcfbfa87a789958625832770826a2e391b87880a5698e601033942c75173460a801a0600061a8000002a0000ffb0d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2344fde6f10018b4cec394cce110bac5d84cce63839bed6752d3c1498c5662b9620001006c02483045022100ff4ba957db4a6f149ed4513bc8cd64c9cc430f4c5810c1d01d73df72926e492e022013495ad376bd22c6683cc334542192411369201e4eaef605b59d34403c1f5de0012103fcfbfa87a789958625832770826a2e391b87880a5698e601033942c75173460a000200000000000000020004ff0000000000000000ff0000000000000001000000000000000000000000000000000001000027100000000027a31840000000002de544802444fde6f10018b4cec394cce110bac5d84cce63839bed6752d3c1498c5662b962000000002b60e316000000000022002049205995639f7357505aed54f7b392d2662276bc5d8968da78e6d623d9e6c7fc00fd0129020000000144fde6f10018b4cec394cce110bac5d84cce63839bed6752d3c1498c5662b9620000000000521a7a8006983a000000000000220020016c1483322b7ef8721387e87a51948fa2e8ecc28dfe33a48327a9eaafd70376983a000000000000220020092dc847c0de4b3408a7b0dced30f091ef9d73e5f8e719794e4267399ed5286d983a0000000000002200209c01da2ecb83e740748c0120909a95cd06387b45902c49810e55f86d34189a85204e000000000000220020c5a1bd48f7ffbc9cd20dadd15f11380a4e878dac76e56080c555134940f2e66ca8250a00000000002200202b0c7734d2e70db4213bca81a2e46458f6e105ff6e2e66aaa3b76133134c3dfea8880b0000000000160014cf85091a7295547ebd2066aecb1a295440a9f6f080f0fc2021396d48be2b7fbc88fd5fe4ab91a19d7af24be98161251a487a8616cf6b425a7980d99b2a2105ccd02fcc73f3449fb169573f0d411ff78aa338eaa344cf549300041124d570c959ea1c621f803b28f343b929732718012795559fe1532e03c42b29e59b000000002b983a000000000000220020016c1483322b7ef8721387e87a51948fa2e8ecc28dfe33a48327a9eaafd70376005e0200000001d570c959ea1c621f803b28f343b929732718012795559fe1532e03c42b29e59b00000000000000000001221f0000000000002200202b0c7734d2e70db4213bca81a2e46458f6e105ff6e2e66aaa3b76133134c3dfe00000000175abe3cc14d94456c949372ed6ace0b5b92e58c595a6297e6c1cdfbe5bd750a000000000000000100061b100a7a13c66544686dfc48ad7fc7d5abfb15a1404f9cb3df4abbb78e810023923100d382bd7186e9d7b47a3be1d8bf7b54eca322931d7514a3260bc6dd4cbb56131124d570c959ea1c621f803b28f343b929732718012795559fe1532e03c42b29e59b010000002b983a000000000000220020092dc847c0de4b3408a7b0dced30f091ef9d73e5f8e719794e4267399ed5286d005e0200000001d570c959ea1c621f803b28f343b929732718012795559fe1532e03c42b29e59b01000000000000000001221f0000000000002200202b0c7734d2e70db4213bca81a2e46458f6e105ff6e2e66aaa3b76133134c3dfe00000000342bb8ca8412a52cbaa04072e0e285ff414a4bf36be1828482705a6bd13501a2000000000000000000061b10c5e470c9b1c309e76daf590ed9f1a7a1111ddef24029bef966622220d059acaf72d241dcade8b164a4309422d2d02130b50f81c0652cef0944d410b3a38a04531324d570c959ea1c621f803b28f343b929732718012795559fe1532e03c42b29e59b020000002b983a0000000000002200209c01da2ecb83e740748c0120909a95cd06387b45902c49810e55f86d34189a85005e0200000001d570c959ea1c621f803b28f343b929732718012795559fe1532e03c42b29e59b02000000000000000001b2200000000000002200202b0c7734d2e70db4213bca81a2e46458f6e105ff6e2e66aaa3b76133134c3dfe101b0600415149b9751963f87c1583f34e4fb1bc5599cb48dc36e0ab744c37fb0630f93e000000000000000100061b10cd3756e951fcd4e7638026ce54e7962dfa1d6cc829a0ee998281b23118f5c83e2d7ff2aef3c1842b2fb552f2d2783f3d8187db9021cf41450d4f1ca15c1582f71324d570c959ea1c621f803b28f343b929732718012795559fe1532e03c42b29e59b030000002b204e000000000000220020c5a1bd48f7ffbc9cd20dadd15f11380a4e878dac76e56080c555134940f2e66c005e0200000001d570c959ea1c621f803b28f343b929732718012795559fe1532e03c42b29e59b030000000000000000013a340000000000002200202b0c7734d2e70db4213bca81a2e46458f6e105ff6e2e66aaa3b76133134c3dfe101b0600af8cea7a0a1be6687852d7f2249be3b445ae0cddc097a05ea468ef797567856d000000000000000000061b10c2ed771e0c811887d21c9d5ad6b72039505e19d879892aa99b83b943374b380e188a459147347953f4a3c34a29d471309e672b9d72c97bd6e64a6e9ff26e31f300000000000000010004000000000000000000000000000000000001ff0000000000000000ff000000000000000100002710000000002de544800000000027a31840b315b404d6bae38833774438441fb36bd2925416d27eeec63326e772032c950f029ced6a1eb5263d34d0d6877178f5b9027cbd9014780197c22fb6fe6849d8c966000000ff0357ca5aa7f38f8ae405263cb1adf2e1469c7f39ee4f9c9b1e70ba2bd02b625478000100400000ffffffffffff0020a409b8983df586e5b71d21ad4824f82a2719d3cad0fdb071c20d0f265b9ba9d080007fffffffffff800002000000000000000000034273b111141c4f04ba55c7b2db01fb50000000000000000100039eb27ed9be0947c7be5941507f4f316200000101eecdd250c851e3ff0344421a4e791eaa008829834f77192587723797bbc77e9f05a886000220d741fabc6a2c7be66dde92517ef89f6ce9ee5288e02f46727ecc12038e9491ad408776ba708b6dafa314db2906226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f01eecdd250c851e368414f7c030100900000000000000000000858b800000014000000001dcd650000000003d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d230000000000000000000000000000061a80ff00012444fde6f10018b4cec394cce110bac5d84cce63839bed6752d3c1498c5662b962000000002b60e316000000000022002049205995639f7357505aed54f7b392d2662276bc5d8968da78e6d623d9e6c7fc00000000000218d33da90930d78dfda97b94815fd8543f4485d0574727143932171cc4d796ab02790a3f53b2f208b1ff65cbd654baf8c0f105b405e8e587438f88eb38bd081479000000061a80000000000000044c00002710000000000001ff0300000000000000022444fde6f10018b4cec394cce110bac5d84cce63839bed6752d3c1498c5662b9620000000022002049205995639f7357505aed54f7b392d2662276bc5d8968da78e6d623d9e6c7fcfffffffd0000000027a31840000000002de544800000000003dfd24002000000000000000622002029fedca3b62676f613aaaea53ff97b497f8799f40a5dc586e67f023c5b1a58750000000027a318400000000045bcc8800000000003dfd2400000000101000000000000000024f48b66833f2ecbd6f125eb8b4c38dd3836ea3e122b64fbe4e293fff54f32ffd0000000002b200b200000000000225120a4ac7358d683a4a296a84a196286ea8bf7c80683f3ea8c30be932eeb30b4cfac00000000000000020100000000000000040000000000183c10225120a4ac7358d683a4a296a84a196286ea8bf7c80683f3ea8c30be932eeb30b4cfac01000000000000000800000000000186a0220020aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa00061a8086d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2341f6c672e644f3aefdda12824a642ca2cce409ef69c5530dcbb2438d64a56ce70000fd025940e6616fcd9dce55776bd871840e88a65415dae895324ce3d454a59626ddc029474733535c6f675d83cade543d03cf7e422d8bb7f1dea3c9821d40cd55035691f10000000000000000020004fffd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000000000000000e4e1c0342bb8ca8412a52cbaa04072e0e285ff414a4bf36be1828482705a6bd13501a200061b100003e368b6d07bc35ff7e3475feddeb313912771046d96fa86ec9a5ba2e9024f5df44c2609468b1f86640de3863c1e3ff60042667392d43809462bc3d18d454ad70de3d85e738909aaa1930559a093e363449e7a00c85cb62562298a108131f6f88635d990d4a6de980d076bfee5281d62828c19da4db7843c304c6d2fe742a8421ffb46c26b5e06249fb8fb4ff0b4d1f08d44d8d7d87953db3da6154f8d31d5af9780f6e45f4867fc7d77a1e438dd87172758779414784275dd05b88eafca27ed4424f1249c14d0894202377b2a394e3cc063badd9bd57f66122bb391fff3718520fc9dffa0b225b1eba27be4f0a386aeda91f85b126771f356afa9d1c9aeed249ac100ece76fc8b32903eaa9caeb2c0e3e6d05f69ad025d7ba625d536e6ad06d8b77aceb6d7a01dbb17adee7e17eca234b251e353e26de28849876b8cf88bd57f4a04745d6a88d32f00b35afdf416efd8f84826840e9e974cbd8aa4a102507f0c7e441ad9e1c1275ccc89266bde55f4a3582b78fa8aafa1994ef36b35d3943d3cd326bc70a3ddd377e895b498d79cbc857b5bb2e61f30ac8ca48fe186e40326fd9fef0215e2ef31844bcaec6334490a89dcd42219d64bfeacec7978294ac964b66b49958130a77f7569b083b1c280d4e451e872403454a3947d2cb10ad2f96f92b070f886a5ed771332f51889d18cc2ad941c6638eebefe5fe6cc4b79468b1e469adb08c34982783d49be95f32a774f51db6788d34f26a8af1b31fc709d78338c6c4d9834fa25a910b4b63e05aa6d383d282bba31c9378e3f27f985088e682e11a46a9a1c8e18be9b7c7ef02d9faa8bb08a53d5ffe059d48d440c6364292fb42df68a04aacdfaaba029f1460912b11a6f8eb57578832fb3c10675c7989114177bdb3b587a80856f351177d01eefbb1de210839c9b8b9ba621942f250dc6b4bbf014899b77f52e8e40cb06f7063371251c419bb8aa9d3c0bd83ce651520b6e2911dd38db6c0fff7cc64550320c5cac621765175460cc8f54ee4a0026ec8e57fd789e0fef66c4940cabc3944ff4e3fe10229294e6fcb3059adb4f9868e53b52a7da60f5ae75d4e42e6a94c44e375960f9ed44a56c0b6c49e7c5775dcc1a819547bf3f146e8bdb13f305ea0d15b5616623db4c0657059d07df7acf1062069705ae5699ce86d9d0de16f3afae97f5327fd340693f756aba0bf338af880927cd7f976705c2b511077bbd19b9a842cb7a9b2f713f183c1e4eac23bee3ac081be66de275451773444e81536e183c8e2f033766b854cc92773205abb0f39d90e762c1f0673af8e4051205f752757d2deb7dce6adf8a55297a3a56fe1265368970e8d480b8ee61fa38313e5a7069579f10ac2db37c05c58b38544a8750e186c43a153c7780cb8ddfa309fac85b487b43b1452624afbc4005bbc0777eda63c96edc94016d1fe880dcb2faab472802921adc29dd3410628b8431186a7ef3a6839eb16123ffccf40c4c0907764e40d1dabbaa12a3a9ed3f341c070fb622600b6f4595f4601f67f2d03e7221d57a20bad2e0fcde738107a57fea1e1257a89970bc94517fecfe4990c67f7bfde2ad523f0f4dc760e0e8958eb32c6a7b0e7d952e75fdf08550ab72b6824122697846e599c8a2cd7f3066c8c444e2bb41aa38cb063493af2106eeae5228247706a53169f306d6b0d74867db69d29e9a6eca2b77aeb3353fa5a593b9f4fa379ffd01464a6929841f0fe934d68c8ae9e523a87d02ce5df5636cc5dcd72536d044cedd2a1d1e9047712a399627a44c8776cc5d0b2fa7e3dbef52102602f97090afab7a6070af4a1e50d19fb646f25212a7e750d6d0f765c0d5324721afe49d1070a686b05dafd39114bc65cc11cb30cda09b872c79521273ff2f075ebc4a2e5444c6880b3854d623bf1c42be1026d8b0409fe0001a1470107fffd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000010000000000e4e1c0175abe3cc14d94456c949372ed6ace0b5b92e58c595a6297e6c1cdfbe5bd750a00061b1000025b15002f7b27451dba144086e3ccebd9726c2e1af20883b676631e13908f2a8a0dcabd2fe4d39cd5b0c9d007a4dc3f1a5624208693e461d934fcbb48428eb1cfba41682d19ab1221e11648459eee58b855ea196e854047d82869b62b2efc978f520eb13e35e3752e53026a0f7b22ddf65de7a4e3fea56fc86b9c85cb8df4cfd0262cf3bbe1e16eb25574db8fd56cee33cbfdb73073f700b9992c19ac808354db41e488aa31d6b8a3be8d151732aaf6353f30a18cd773285cddbd3b279e6455f9b90aa37d7055ccd9fdafa66ab0ad216fd123e4a4aadb25f9e004c5a8e794abced52cd6e208d8d2657d03f5179b6353133a9eaf151cf5d47298058ad85ada4e42d29b9c61524ac20264d3b6551efef60789c6a91cf1ce4e069e3ad123fba8d515d705c0e2f3aec108ab317116d593100a4db4affd32b4091a1368cfe3c91b422ff53c75ade0ed61dd562e55cd674e5a0c5ca5952c2204c401d9c61c25b565c83c38cc09eaa6e25c42d234875f2a353813b8db46f031f0392f73a9cc8a88925347e04cfb477f3a7c5578edaa447927c048aa631b4f3dafcb608fafef2e125a549e029f2598cae566f93febee55ab1175359913732673a44fd2a8e28f71e281917c7d0fc04816a5774a3411d1db8f290fe442e3d3141ab366633f07e46528d44528a422c9225129b0ae4e7f3b76c9c8fb25b9c7722be9b5862ce866980003752c178e7a94224a2b0b84fa1e2635b5e2207ade589baa96372d4453868b3e01e9ac2d1d80bc0c7e06a22c205f85e578d55e3283e9b2defb70f46a6084ea40e92b25f35550e7f3fc3c9e764dd4eea7b85df10175a0799e6a46cae937ddb1fbc43795a88850870cdd0f5bfb4506f227b1420f6e8d39b7ccbdc888a7df7066cfcd8da6ba6bceb2aa80bf14fd5d096f604af41ae92b9f4e3c6a7f29208e8b3fb70cf81fd56ff17ee56adf35cb18b39a067f67f9fc3f94e8c30d44760da2322a77679bd6ca0da0e47f206ef7b644448e7330bf19455ca777418896f0e07e551ae4995c7ac5026a7b84c0cd07baf9569135ae4cc3893339ac886ab704b37d46aa3edcfeaa6c4c8aec3ec8bf8ffb51a591ff02f04367314c267a3152070069950d384b99235e5398547d5084c4afbe86ce6a6a60964b8cd89b20c690e8de12606f64ea559a36431dc98d6189a3f15d439dcafd831a9dab64bf8dd5722c91d59fab00fad5b27a89d5f4237cb20ca55648a8256e8a0ba18394608840336d15a84451821619a2363eb51c5ee83e1722476106248962ccb5f7bd843e7dd14620a2b40107d972fa6b1e4ed7bd6b13b659b012870b64906229c93c2625b02da587d2239e09d64bec81f40c50398eb666ff8912711c2377165cbb0ad16999c0b09bc36f354cd495d63fc6cd7b89e4063ff1b63b319afa4a7a64da69aa41ba9af3131df5cb384ce68ab74898d699ea09730b53d3d862521927b0cde151844a9eb11aef8e0e25a3fb607d57c2a9f83eaf01f1c387e09be5fc5ac0eb091359ca26c8fb36cea3ccae8a63e57dcbc60ee66975bfee8964cfdfc840b114dc6ed5eac0babd26f99ec251c45ec602a44fcc6965c0d7c065c186569be6426658b8a5b32fc2dee315a0457e963635795a0d699db9587eeacf8f893699410a87b6390d8c8736552e325f5ccec1b6d4b45002b678649b234301610ecd605f9e56c5dc45d81cccf73e442b872369cbbd3a03eaba79c0af36e23ed97cf73808dc5ed0f5fcd751772b57e2666c5a70b1a5af61a0dd461e8a383e8426369ee6a4e03cf256a6bc3570997c3c06879946ffdd2924aa329e006759c0d6b564c89198c0c09f88c2c9d0c2cd4e0163e8f98145456cc859f55aa8d11a9969ae253d93d56d9fc6a386ae7c6ee884f483913bd739124f69b4bd5238fdc33d860a9cd4670de5dafe4a24fe0001a147010700fd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000000000000001312d00af8cea7a0a1be6687852d7f2249be3b445ae0cddc097a05ea468ef797567856d00061b100003e5507553c137054ae33b5d7cdaf142fbdbfae51246ea3db4441251aa49dac2e8cac063650741353682da85f0df1b1db72997177b74940bcabcb961f364c1861a493066a9cf2533aca5f866b280419fe04f9e9807d3049b759f78d7ce322554ed7891184e73d934c62f88ca33f7cd75d56617664cc674d74f553b8674093e60f014a89e1ab4ea7adde5d54f28a9976d2a3ddb20854b3a1f3ecac8490a9243234c7ce9c5df1b0e45d862c847fd93b99ed1f4d5fd5b06ca416ed048433782d4bd867d36196a7ebe1106f687d0868cd2f2cc1fd94f999de8e59e9a9b333aac6304ac7c01cb91d20c09d4e4f38397ccc1a0cbb46865b7292b84d4f11a783abb0d5da2e31fdc7c2885c6c90053f716264d0b8ba1ce53dc72e8f04e702e319029ca234b8bf72f5321382723010153eaf113e0c823914bf6ea319a1083efe2f0736e1f9b36125c4b77b9e4a581e6fecec959be70f2e21b8c9bd568641d45feb3ffdd39491abc973f310cb1f9c46a874471e5edc3ea7d4063274a9e10e2d21a9e836e99d386d062ac31bf19a14ad6d39c19752eb749c4dacc64aa4591228c039a1f3c0dfd919a2576edf70cbe018075d90c94bebbb91290487cb9cc613eef312a87025891ea6d8b02435b26abf72fea504aad333100e4f14ee916c678c6c9a42b3421bb8f7389b642f89b5abaf99f0df25e5859bee216c3845bbc9fdb032acb88cc144306cc17fe754280bccfecd86670d6994833c4847c71913caed6d904525d22d28aadf3d4e867a617f20a9cf61e95152eb1cab90bae7885da01effee11b7780bd2398d37f04096e7d14252e2026f329422c077762996f45a7c4d546d7ad0422996e659c98f1feebc5c73eeecab837c025a44d94cb5ee39d5a1bbd95973e8e5cdd97cabb7b8f4f86a5bb228c0d12be6b9b2032c54795f4f7180828e94377bf6e0f56545dfcd5252da0936d79aef70caa8715e8416579ca1a928c2aecfa1e03b6cd795528a967fb3dbe524996be92b6168222112063077fb39811ebf9dbb97abafe1b315488c8812224faef56c1eb86837ffcf61bc22a60de7e4f6e0df67c332389556df7da838200dcceeafcb6e8a94aaad013c30934ec0ada06fa17afb5414de4812deb6fa96517df57d503e1e521562ed62a65073252dd08aafba343c029e04faf58b4c451061f156669be5ef68a2adff506cba6351e407d3c6077b71807f21e93d0ca69843f2d5fd6b7c7f415d740fac1f1f8c8bd8f95297ee1ffafbe1b41c548b69dd6d211352c556f14bb4e5af76244e7656f882452201e2bfd90a87413904081a78c2d6849a6a07ad77920465141da19034b70c3edac53a560e7b5c07923c9bc6e17b8091257fe7431e7cb18d770ba1457e62714a47beb8e503874f24405c49121d2cb024edee3458b31ebe1bf40a13f564b8ff95dc537ec926311f59150544fcc57efcef53d9f43c41b0dc32f93b91d2663e9f6b09f9483b4a788b0642d667c722d10ddf09a70886153fc8c943a9d6f3eecec3ab7144669c10b3c5864a174c7343a74e4b58da29f0ae480d5be8adb5d5b55e9cc3c11da1a935c2b1cb966d85612b6c4bbd42aaaeeebada9f062175f502c2cde7daa345239f99201be98ae989cd8a3cd0610779fd413ef1c6c48cd6e8bcb454b847815016e214f1e6162df1b388503b06bad1e5e35ca424fa1ac4407757504e311f4ee0f4d8e5dce45759a96031460842697d577205255b9ba2bbbe4eb08528a9224087a40129906655170405ad4491052ba71d6dde4000b7d7154634db60db540a16cc430b5998dc1465bc57b5a4125c967e4fc451d66026b650b9a70c26998f559793809df549ca1d5e730db0759ed40d3a030c8a1c6bccb28b5fd9230c55ef83d23b369938200099274978bcc017a96bd1b506c6d28b46788f9a483f3e0b234fe0001a147010700fd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000010000000000e4e1c0415149b9751963f87c1583f34e4fb1bc5599cb48dc36e0ab744c37fb0630f93e00061b1000033fabe908e0bb47028814d9361e2518822ec95a72c8e611b1562fb595ad452db3272b17a6f9280115fa6e17434cfc1639b3b1795ff6cfe4aaa80b3393100ba4a2308b9e42dbbb3cba55bec53bbf574b9307e2023f3e13e6d0424ce7b4376280ea0d3a187c8585212fe9effee9aa102873a48f1b71a2ae81cc97fdf6075b4ee17716bbcbcd930a23d483f04907dcf89d9d55d62ac6935ce45afe8549de48a363c595876e32e8ba33806671cb54a92a81409f9bc8964983552f709335efb962d17c2bcc348fcf9af666ae7cff45833b4b0449481a6cc869a6cc2d928ccba9499e6415a52c455ebce14cd94a36fa0bf127682e890be81a1dcc77dd2b94f886103d793ffce97430edf04f4b8a0aaf9622b34bfceaef96bd6848717430d2212f2df30105354306fe8d5d452c32e1b0f1872cb7c953295d2794805fcad0c50cebd26b14cc903fd995d4eb397d85d3bbcc92a4b640ec4448a16eaae0941f2f074ea2f292b55c8cd8051f5b43c4f0d14ae04038d06216cf01b344fb3b8b42ae2680d7998c5f24c16813dcfc521525b5bda78bf604edab7edcac3934cbb9e5e1baae044ea136613ac003e86e3caa9f0e93c38c5997a17ab472004726730f79248be824fbe894422e2b07f951d31ccc32c7f513a61c7e0a4f3790faf2dca5fc4afca1a0f14b0a7a07178402f1875430c905250a5d4d8d70521418cca1c82a2b3721831087e342d74bab5395c5099fe8710c667fb463e50f59da0b67d7f8b2a4b47896c84954a82d604a570f41fb1a5a9906592ee050b0cad8fdc652a411f8dd0d4aa5c46540479b202f7752985311671be2fca007d87b2f27f16be6a590357aeb87f9f75d9f2c798599f3e8bc2fe5b1e7b5d4e3a779761d983f52e5d4844c7eabbd4e543436954a9e361f2f099bcb603a94f2cfc1f1409aa50aee8644c5a1222e2df52fd8a1f884c0a1a800c92be47b7bed41ceff4ee44700c88e527b55c0abdd04f86ce1dae36f0c2802bc4cc74e0698092fed0e8ab7a95e1bfdf0b6fd160a7c80b0f1bba8dde89740e4caa6681f1d4c926a49ee2088282d18e3646e4ade293e1b2cb5a0155d3ee662d5c468cb5d2ac7557e7666396336e3608d84c97fb1f48dd8ca7f8528b218eeac9a825b161147849d1c2a1ee461150cf8984f492d02221ea43d994821790f5e7357e615ce721817e89495bc3422b98a7e6d6288d75c3b727d1669742b682560660079d8860ed65022d279ca414cd56e169e1147d27ad808290f7daeb665a949b793ae503c1dc9d3f1ae0e57b02d373357408d881e2354337143f7d0b99dd2adf48e6c4f4983bc1de85345892e246c6edbd9aee3747fcc2efa956e3e89e9f5a9be0401933b51d625e25c8296be8ef13720d15411d029a471387fa2c28a168f72e0802d84b3085937f9a83353e002b39596e1567d712e729aa55e648f87043be90b359d3e1fd439091d3ded3b72c0c0bdac8e3033a2b2d6e4e074094b7ff6eb631e2b62ec36e35ea434154e0189b1b1e8b9b60da771cf0077ff267e7e861045d24d0c3fa57b9408d3e2116bf75ba02b4d8cd78701ca9836074440c6c0c0ccdb2cd490ee616bc6d3f8d10ddaf60dfe77003ea326673193d21032997f8fb77c2393ce7af5f1d3d3abef5550e80c5a4e3b7ae75ae3f79950a2f8e5065e8d8fecd2f195aac557d7a4b4a5d1e0c5f1e60c0005d73df261401196c93384fcc271b0ebf0a9da2adee213319ed1fb5bec02202b4a0778bdd48420e701105a135b8bf2d4f50b6939ca0890174d7ce9852473456059b9b7a85db8e4664fa67dbc2d729080df312f3d8970e28e7304510b4828624279cf789d9deebf08cce0c1cf84ffb2abc52784230a5b488fe167632882be37fd1564c22b0dc89cf86ea5ffe6d7880b7199eecb9957b28ba3035316b763a4c0a0691bfe0001a1470107000027100000000027a318400000000045bcc8802441f6c672e644f3aefdda12824a642ca2cce409ef69c5530dcbb2438d64a56ce7010000002be0fd1c000000000022002029fedca3b62676f613aaaea53ff97b497f8799f40a5dc586e67f023c5b1a587500fd0129020000000141f6c672e644f3aefdda12824a642ca2cce409ef69c5530dcbb2438d64a56ce70100000000521a7a8006983a000000000000220020016c1483322b7ef8721387e87a51948fa2e8ecc28dfe33a48327a9eaafd70376983a000000000000220020092dc847c0de4b3408a7b0dced30f091ef9d73e5f8e719794e4267399ed5286d983a0000000000002200209c01da2ecb83e740748c0120909a95cd06387b45902c49810e55f86d34189a85204e000000000000220020c5a1bd48f7ffbc9cd20dadd15f11380a4e878dac76e56080c555134940f2e66ca8250a00000000002200202b0c7734d2e70db4213bca81a2e46458f6e105ff6e2e66aaa3b76133134c3dfe28a3110000000000160014cf85091a7295547ebd2066aecb1a295440a9f6f080f0fc2000000000000000000001000400fd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000000000000000e4e1c0342bb8ca8412a52cbaa04072e0e285ff414a4bf36be1828482705a6bd13501a200061b100003e368b6d07bc35ff7e3475feddeb313912771046d96fa86ec9a5ba2e9024f5df44c2609468b1f86640de3863c1e3ff60042667392d43809462bc3d18d454ad70de3d85e738909aaa1930559a093e363449e7a00c85cb62562298a108131f6f88635d990d4a6de980d076bfee5281d62828c19da4db7843c304c6d2fe742a8421ffb46c26b5e06249fb8fb4ff0b4d1f08d44d8d7d87953db3da6154f8d31d5af9780f6e45f4867fc7d77a1e438dd87172758779414784275dd05b88eafca27ed4424f1249c14d0894202377b2a394e3cc063badd9bd57f66122bb391fff3718520fc9dffa0b225b1eba27be4f0a386aeda91f85b126771f356afa9d1c9aeed249ac100ece76fc8b32903eaa9caeb2c0e3e6d05f69ad025d7ba625d536e6ad06d8b77aceb6d7a01dbb17adee7e17eca234b251e353e26de28849876b8cf88bd57f4a04745d6a88d32f00b35afdf416efd8f84826840e9e974cbd8aa4a102507f0c7e441ad9e1c1275ccc89266bde55f4a3582b78fa8aafa1994ef36b35d3943d3cd326bc70a3ddd377e895b498d79cbc857b5bb2e61f30ac8ca48fe186e40326fd9fef0215e2ef31844bcaec6334490a89dcd42219d64bfeacec7978294ac964b66b49958130a77f7569b083b1c280d4e451e872403454a3947d2cb10ad2f96f92b070f886a5ed771332f51889d18cc2ad941c6638eebefe5fe6cc4b79468b1e469adb08c34982783d49be95f32a774f51db6788d34f26a8af1b31fc709d78338c6c4d9834fa25a910b4b63e05aa6d383d282bba31c9378e3f27f985088e682e11a46a9a1c8e18be9b7c7ef02d9faa8bb08a53d5ffe059d48d440c6364292fb42df68a04aacdfaaba029f1460912b11a6f8eb57578832fb3c10675c7989114177bdb3b587a80856f351177d01eefbb1de210839c9b8b9ba621942f250dc6b4bbf014899b77f52e8e40cb06f7063371251c419bb8aa9d3c0bd83ce651520b6e2911dd38db6c0fff7cc64550320c5cac621765175460cc8f54ee4a0026ec8e57fd789e0fef66c4940cabc3944ff4e3fe10229294e6fcb3059adb4f9868e53b52a7da60f5ae75d4e42e6a94c44e375960f9ed44a56c0b6c49e7c5775dcc1a819547bf3f146e8bdb13f305ea0d15b5616623db4c0657059d07df7acf1062069705ae5699ce86d9d0de16f3afae97f5327fd340693f756aba0bf338af880927cd7f976705c2b511077bbd19b9a842cb7a9b2f713f183c1e4eac23bee3ac081be66de275451773444e81536e183c8e2f033766b854cc92773205abb0f39d90e762c1f0673af8e4051205f752757d2deb7dce6adf8a55297a3a56fe1265368970e8d480b8ee61fa38313e5a7069579f10ac2db37c05c58b38544a8750e186c43a153c7780cb8ddfa309fac85b487b43b1452624afbc4005bbc0777eda63c96edc94016d1fe880dcb2faab472802921adc29dd3410628b8431186a7ef3a6839eb16123ffccf40c4c0907764e40d1dabbaa12a3a9ed3f341c070fb622600b6f4595f4601f67f2d03e7221d57a20bad2e0fcde738107a57fea1e1257a89970bc94517fecfe4990c67f7bfde2ad523f0f4dc760e0e8958eb32c6a7b0e7d952e75fdf08550ab72b6824122697846e599c8a2cd7f3066c8c444e2bb41aa38cb063493af2106eeae5228247706a53169f306d6b0d74867db69d29e9a6eca2b77aeb3353fa5a593b9f4fa379ffd01464a6929841f0fe934d68c8ae9e523a87d02ce5df5636cc5dcd72536d044cedd2a1d1e9047712a399627a44c8776cc5d0b2fa7e3dbef52102602f97090afab7a6070af4a1e50d19fb646f25212a7e750d6d0f765c0d5324721afe49d1070a686b05dafd39114bc65cc11cb30cda09b872c79521273ff2f075ebc4a2e5444c6880b3854d623bf1c42be1026d8b0409fe0001a147010700fd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000010000000000e4e1c0175abe3cc14d94456c949372ed6ace0b5b92e58c595a6297e6c1cdfbe5bd750a00061b1000025b15002f7b27451dba144086e3ccebd9726c2e1af20883b676631e13908f2a8a0dcabd2fe4d39cd5b0c9d007a4dc3f1a5624208693e461d934fcbb48428eb1cfba41682d19ab1221e11648459eee58b855ea196e854047d82869b62b2efc978f520eb13e35e3752e53026a0f7b22ddf65de7a4e3fea56fc86b9c85cb8df4cfd0262cf3bbe1e16eb25574db8fd56cee33cbfdb73073f700b9992c19ac808354db41e488aa31d6b8a3be8d151732aaf6353f30a18cd773285cddbd3b279e6455f9b90aa37d7055ccd9fdafa66ab0ad216fd123e4a4aadb25f9e004c5a8e794abced52cd6e208d8d2657d03f5179b6353133a9eaf151cf5d47298058ad85ada4e42d29b9c61524ac20264d3b6551efef60789c6a91cf1ce4e069e3ad123fba8d515d705c0e2f3aec108ab317116d593100a4db4affd32b4091a1368cfe3c91b422ff53c75ade0ed61dd562e55cd674e5a0c5ca5952c2204c401d9c61c25b565c83c38cc09eaa6e25c42d234875f2a353813b8db46f031f0392f73a9cc8a88925347e04cfb477f3a7c5578edaa447927c048aa631b4f3dafcb608fafef2e125a549e029f2598cae566f93febee55ab1175359913732673a44fd2a8e28f71e281917c7d0fc04816a5774a3411d1db8f290fe442e3d3141ab366633f07e46528d44528a422c9225129b0ae4e7f3b76c9c8fb25b9c7722be9b5862ce866980003752c178e7a94224a2b0b84fa1e2635b5e2207ade589baa96372d4453868b3e01e9ac2d1d80bc0c7e06a22c205f85e578d55e3283e9b2defb70f46a6084ea40e92b25f35550e7f3fc3c9e764dd4eea7b85df10175a0799e6a46cae937ddb1fbc43795a88850870cdd0f5bfb4506f227b1420f6e8d39b7ccbdc888a7df7066cfcd8da6ba6bceb2aa80bf14fd5d096f604af41ae92b9f4e3c6a7f29208e8b3fb70cf81fd56ff17ee56adf35cb18b39a067f67f9fc3f94e8c30d44760da2322a77679bd6ca0da0e47f206ef7b644448e7330bf19455ca777418896f0e07e551ae4995c7ac5026a7b84c0cd07baf9569135ae4cc3893339ac886ab704b37d46aa3edcfeaa6c4c8aec3ec8bf8ffb51a591ff02f04367314c267a3152070069950d384b99235e5398547d5084c4afbe86ce6a6a60964b8cd89b20c690e8de12606f64ea559a36431dc98d6189a3f15d439dcafd831a9dab64bf8dd5722c91d59fab00fad5b27a89d5f4237cb20ca55648a8256e8a0ba18394608840336d15a84451821619a2363eb51c5ee83e1722476106248962ccb5f7bd843e7dd14620a2b40107d972fa6b1e4ed7bd6b13b659b012870b64906229c93c2625b02da587d2239e09d64bec81f40c50398eb666ff8912711c2377165cbb0ad16999c0b09bc36f354cd495d63fc6cd7b89e4063ff1b63b319afa4a7a64da69aa41ba9af3131df5cb384ce68ab74898d699ea09730b53d3d862521927b0cde151844a9eb11aef8e0e25a3fb607d57c2a9f83eaf01f1c387e09be5fc5ac0eb091359ca26c8fb36cea3ccae8a63e57dcbc60ee66975bfee8964cfdfc840b114dc6ed5eac0babd26f99ec251c45ec602a44fcc6965c0d7c065c186569be6426658b8a5b32fc2dee315a0457e963635795a0d699db9587eeacf8f893699410a87b6390d8c8736552e325f5ccec1b6d4b45002b678649b234301610ecd605f9e56c5dc45d81cccf73e442b872369cbbd3a03eaba79c0af36e23ed97cf73808dc5ed0f5fcd751772b57e2666c5a70b1a5af61a0dd461e8a383e8426369ee6a4e03cf256a6bc3570997c3c06879946ffdd2924aa329e006759c0d6b564c89198c0c09f88c2c9d0c2cd4e0163e8f98145456cc859f55aa8d11a9969ae253d93d56d9fc6a386ae7c6ee884f483913bd739124f69b4bd5238fdc33d860a9cd4670de5dafe4a24fe0001a1470107fffd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000000000000001312d00af8cea7a0a1be6687852d7f2249be3b445ae0cddc097a05ea468ef797567856d00061b100003e5507553c137054ae33b5d7cdaf142fbdbfae51246ea3db4441251aa49dac2e8cac063650741353682da85f0df1b1db72997177b74940bcabcb961f364c1861a493066a9cf2533aca5f866b280419fe04f9e9807d3049b759f78d7ce322554ed7891184e73d934c62f88ca33f7cd75d56617664cc674d74f553b8674093e60f014a89e1ab4ea7adde5d54f28a9976d2a3ddb20854b3a1f3ecac8490a9243234c7ce9c5df1b0e45d862c847fd93b99ed1f4d5fd5b06ca416ed048433782d4bd867d36196a7ebe1106f687d0868cd2f2cc1fd94f999de8e59e9a9b333aac6304ac7c01cb91d20c09d4e4f38397ccc1a0cbb46865b7292b84d4f11a783abb0d5da2e31fdc7c2885c6c90053f716264d0b8ba1ce53dc72e8f04e702e319029ca234b8bf72f5321382723010153eaf113e0c823914bf6ea319a1083efe2f0736e1f9b36125c4b77b9e4a581e6fecec959be70f2e21b8c9bd568641d45feb3ffdd39491abc973f310cb1f9c46a874471e5edc3ea7d4063274a9e10e2d21a9e836e99d386d062ac31bf19a14ad6d39c19752eb749c4dacc64aa4591228c039a1f3c0dfd919a2576edf70cbe018075d90c94bebbb91290487cb9cc613eef312a87025891ea6d8b02435b26abf72fea504aad333100e4f14ee916c678c6c9a42b3421bb8f7389b642f89b5abaf99f0df25e5859bee216c3845bbc9fdb032acb88cc144306cc17fe754280bccfecd86670d6994833c4847c71913caed6d904525d22d28aadf3d4e867a617f20a9cf61e95152eb1cab90bae7885da01effee11b7780bd2398d37f04096e7d14252e2026f329422c077762996f45a7c4d546d7ad0422996e659c98f1feebc5c73eeecab837c025a44d94cb5ee39d5a1bbd95973e8e5cdd97cabb7b8f4f86a5bb228c0d12be6b9b2032c54795f4f7180828e94377bf6e0f56545dfcd5252da0936d79aef70caa8715e8416579ca1a928c2aecfa1e03b6cd795528a967fb3dbe524996be92b6168222112063077fb39811ebf9dbb97abafe1b315488c8812224faef56c1eb86837ffcf61bc22a60de7e4f6e0df67c332389556df7da838200dcceeafcb6e8a94aaad013c30934ec0ada06fa17afb5414de4812deb6fa96517df57d503e1e521562ed62a65073252dd08aafba343c029e04faf58b4c451061f156669be5ef68a2adff506cba6351e407d3c6077b71807f21e93d0ca69843f2d5fd6b7c7f415d740fac1f1f8c8bd8f95297ee1ffafbe1b41c548b69dd6d211352c556f14bb4e5af76244e7656f882452201e2bfd90a87413904081a78c2d6849a6a07ad77920465141da19034b70c3edac53a560e7b5c07923c9bc6e17b8091257fe7431e7cb18d770ba1457e62714a47beb8e503874f24405c49121d2cb024edee3458b31ebe1bf40a13f564b8ff95dc537ec926311f59150544fcc57efcef53d9f43c41b0dc32f93b91d2663e9f6b09f9483b4a788b0642d667c722d10ddf09a70886153fc8c943a9d6f3eecec3ab7144669c10b3c5864a174c7343a74e4b58da29f0ae480d5be8adb5d5b55e9cc3c11da1a935c2b1cb966d85612b6c4bbd42aaaeeebada9f062175f502c2cde7daa345239f99201be98ae989cd8a3cd0610779fd413ef1c6c48cd6e8bcb454b847815016e214f1e6162df1b388503b06bad1e5e35ca424fa1ac4407757504e311f4ee0f4d8e5dce45759a96031460842697d577205255b9ba2bbbe4eb08528a9224087a40129906655170405ad4491052ba71d6dde4000b7d7154634db60db540a16cc430b5998dc1465bc57b5a4125c967e4fc451d66026b650b9a70c26998f559793809df549ca1d5e730db0759ed40d3a030c8a1c6bccb28b5fd9230c55ef83d23b369938200099274978bcc017a96bd1b506c6d28b46788f9a483f3e0b234fe0001a1470107fffd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000010000000000e4e1c0415149b9751963f87c1583f34e4fb1bc5599cb48dc36e0ab744c37fb0630f93e00061b1000033fabe908e0bb47028814d9361e2518822ec95a72c8e611b1562fb595ad452db3272b17a6f9280115fa6e17434cfc1639b3b1795ff6cfe4aaa80b3393100ba4a2308b9e42dbbb3cba55bec53bbf574b9307e2023f3e13e6d0424ce7b4376280ea0d3a187c8585212fe9effee9aa102873a48f1b71a2ae81cc97fdf6075b4ee17716bbcbcd930a23d483f04907dcf89d9d55d62ac6935ce45afe8549de48a363c595876e32e8ba33806671cb54a92a81409f9bc8964983552f709335efb962d17c2bcc348fcf9af666ae7cff45833b4b0449481a6cc869a6cc2d928ccba9499e6415a52c455ebce14cd94a36fa0bf127682e890be81a1dcc77dd2b94f886103d793ffce97430edf04f4b8a0aaf9622b34bfceaef96bd6848717430d2212f2df30105354306fe8d5d452c32e1b0f1872cb7c953295d2794805fcad0c50cebd26b14cc903fd995d4eb397d85d3bbcc92a4b640ec4448a16eaae0941f2f074ea2f292b55c8cd8051f5b43c4f0d14ae04038d06216cf01b344fb3b8b42ae2680d7998c5f24c16813dcfc521525b5bda78bf604edab7edcac3934cbb9e5e1baae044ea136613ac003e86e3caa9f0e93c38c5997a17ab472004726730f79248be824fbe894422e2b07f951d31ccc32c7f513a61c7e0a4f3790faf2dca5fc4afca1a0f14b0a7a07178402f1875430c905250a5d4d8d70521418cca1c82a2b3721831087e342d74bab5395c5099fe8710c667fb463e50f59da0b67d7f8b2a4b47896c84954a82d604a570f41fb1a5a9906592ee050b0cad8fdc652a411f8dd0d4aa5c46540479b202f7752985311671be2fca007d87b2f27f16be6a590357aeb87f9f75d9f2c798599f3e8bc2fe5b1e7b5d4e3a779761d983f52e5d4844c7eabbd4e543436954a9e361f2f099bcb603a94f2cfc1f1409aa50aee8644c5a1222e2df52fd8a1f884c0a1a800c92be47b7bed41ceff4ee44700c88e527b55c0abdd04f86ce1dae36f0c2802bc4cc74e0698092fed0e8ab7a95e1bfdf0b6fd160a7c80b0f1bba8dde89740e4caa6681f1d4c926a49ee2088282d18e3646e4ade293e1b2cb5a0155d3ee662d5c468cb5d2ac7557e7666396336e3608d84c97fb1f48dd8ca7f8528b218eeac9a825b161147849d1c2a1ee461150cf8984f492d02221ea43d994821790f5e7357e615ce721817e89495bc3422b98a7e6d6288d75c3b727d1669742b682560660079d8860ed65022d279ca414cd56e169e1147d27ad808290f7daeb665a949b793ae503c1dc9d3f1ae0e57b02d373357408d881e2354337143f7d0b99dd2adf48e6c4f4983bc1de85345892e246c6edbd9aee3747fcc2efa956e3e89e9f5a9be0401933b51d625e25c8296be8ef13720d15411d029a471387fa2c28a168f72e0802d84b3085937f9a83353e002b39596e1567d712e729aa55e648f87043be90b359d3e1fd439091d3ded3b72c0c0bdac8e3033a2b2d6e4e074094b7ff6eb631e2b62ec36e35ea434154e0189b1b1e8b9b60da771cf0077ff267e7e861045d24d0c3fa57b9408d3e2116bf75ba02b4d8cd78701ca9836074440c6c0c0ccdb2cd490ee616bc6d3f8d10ddaf60dfe77003ea326673193d21032997f8fb77c2393ce7af5f1d3d3abef5550e80c5a4e3b7ae75ae3f79950a2f8e5065e8d8fecd2f195aac557d7a4b4a5d1e0c5f1e60c0005d73df261401196c93384fcc271b0ebf0a9da2adee213319ed1fb5bec02202b4a0778bdd48420e701105a135b8bf2d4f50b6939ca0890174d7ce9852473456059b9b7a85db8e4664fa67dbc2d729080df312f3d8970e28e7304510b4828624279cf789d9deebf08cce0c1cf84ffb2abc52784230a5b488fe167632882be37fd1564c22b0dc89cf86ea5ffe6d7880b7199eecb9957b28ba3035316b763a4c0a0691bfe0001a1470107000027100000000045bcc8800000000027a318401078a5654313845429e9ff89f5848fb34caef8f89701d89c1f989a2169a7d1ea029ced6a1eb5263d34d0d6877178f5b9027cbd9014780197c22fb6fe6849d8c96600") val decoded = channelDataCodec.decode(bin.bits).require.value.asInstanceOf[DATA_NORMAL] + assert(decoded.channelParams.commitmentFormat == DefaultCommitmentFormat) + assert(decoded.channelParams.channelFeatures.features == Set(Features.DualFunding, Features.StaticRemoteKey)) + // Local params. + assert(!decoded.channelParams.localParams.isChannelOpener) + assert(decoded.channelParams.localParams.toSelfDelay == CltvExpiryDelta(144)) + assert(decoded.channelParams.localParams.dustLimit == 1000.sat) + assert(decoded.channelParams.localParams.htlcMinimum == 1000.msat) + assert(decoded.channelParams.localParams.maxAcceptedHtlcs == 30) + assert(decoded.channelParams.localParams.maxHtlcValueInFlightMsat == 1_000_000_000.msat) + // Remote params. + assert(decoded.channelParams.remoteParams.toSelfDelay == CltvExpiryDelta(144)) + assert(decoded.channelParams.remoteParams.dustLimit == 1100.sat) + assert(decoded.channelParams.remoteParams.htlcMinimum == 0.msat) + assert(decoded.channelParams.remoteParams.maxAcceptedHtlcs == 100) + assert(decoded.channelParams.remoteParams.maxHtlcValueInFlightMsat == UInt64(500_000_000)) + // Signing session. assert(decoded.spliceStatus.isInstanceOf[SpliceStatus.SpliceWaitingForSigs]) val spliceStatus = decoded.spliceStatus.asInstanceOf[SpliceStatus.SpliceWaitingForSigs] + assert(spliceStatus.signingSession.fundingTxIndex == 1) + assert(spliceStatus.signingSession.commitInput.outPoint == OutPoint(TxId.fromValidHex("e76ca5648d43b2cb0d53c569ef09e4cca22c644a8212dafdaef344e672c6f641"), 1)) + assert(spliceStatus.signingSession.commitInput.txOut == TxOut(1_900_000 sat, hex"002029fedca3b62676f613aaaea53ff97b497f8799f40a5dc586e67f023c5b1a5875")) assert(spliceStatus.signingSession.localCommit.isLeft) val unsignedLocalCommit = spliceStatus.signingSession.localCommit.left.toOption.get assert(unsignedLocalCommit.index == 2) @@ -243,6 +280,63 @@ class ChannelCodecs4Spec extends AnyFunSuite { assert(!nonInitiatorDecoded.commitments.localChannelParams.paysCommitTxFees) } + test("decode splice funding status") { + val bin = hex"00180288db77733522f259128f787f0a22f9c2390632979883f46a0d1ff8d67a1e64390101041040100002bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e6300099a646c0bc1ac29cc54d267a136c058acda0bec29753f44d86d03f166c830e4dd8000000000000000000003e8000000003b9aca0000000000000003e80090001e0000000000140800000000000000000000000000100822aa698202aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa000000000000044c000000001dcd6500000000000000000000900064034ed663d94a40aa41a1cee6a3a51e61a8d850dcb279b3fdcc2cc3de9429aeb58702272bcc43918fa19c19ec1fe51d2f880d519672fc9c8b1b16897e1effdb9da6b303db2ac88901d891e5535c90a41a6f3b8f07c94d46c4d5a836e02987a5eea700940214a6afde6d3e2af143d9b213ccf01358bca3207c21b98f6ba73c57f4a8712d2b0000186b0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000180822aa69820000000000000000000000000000000000000000000200000000000000020004fffd05b188db77733522f259128f787f0a22f9c2390632979883f46a0d1ff8d67a1e643900000000000000000000000000e4e1c082c2d32d968632e86cd06acf803d2d05f01d3597c87304dbf0c8e49942d98d8c00061b1000028a074fea4c0f9a59f316f0b61b109f516315e73d86a38a3c8cc52a88f076caa0119c980e5f307fe7d43e34de0fbc89bab1a0d962e91421ad832fae5f3c6906b5a624ba40d32692ec9fde23bf9a8f165bdfd657d76f99351913f04f59c6051f460af79c14567a9ca99a1e4882f92b9b8ac201d98f028b555ef542fe083099d2c7e146e936fb44e5fb4f99079895b70447564cffee641fdcde15e08832753411e6c3a89f8b3b8cf302686b539c589754be7ea78d3401d7aff82f7001e0095e1ee3f9b7756f3af91f8741465462761a84ae850aee95434ff2b2807043b7b17eecbb4a8125533c1c6a86ca8e4a0386f9ae7361d96563994bce7fed637fe3741b7255cbc1da887f52f37f7d11baf9fc05fd31b1a9f6bd50dd554393f63ca887cd33b98a98a1018426ff755734086e02b2e807c2761d81c835ea2bb3d9d965400ebe56023d13846308181cd6dc3d79b7ec2a3fdd777bdf3a8ac716c335e2dcec499a73dc76f32b4915c771ddbc4d680e47198cd7a1965ce9dbe7ec181967167e729e008e88203d83f2763b5f23d8b0eb9673548e15d597462cf21433d80d490703e1ba9972b4a27ee7ee0f75f8866cb8120e8114bdd6ad864079c71bb5877e86f699fc8e5d0b3d104b12827eec7caee60edbe29f0365d9d892594278ee7e0e96584f8b0d9b13cb89a468ce9c42224ff3da92ff6f27fb74c4eeaec3c198ac6f9e1bb57647d739510c846decbaa686a2c285be618968e4c7983e2e1ef30414090db0d64a1945c638a78811341aedc21ac0c746ab6a0f84ec21c2f35a9a31842adb1c5adb6df9709ef64c5ac8c4cf75d3ef8ca06024c1636ef47d4f8fd4c2b19bb73d317443cd74de11c0a9f9ad40a4c5f4d2ae586330930e3f4e009c913fe7b42ae385e125f8659035134ab6019bff84b2b9d5d40bef7577f91ebee5c3b351f1f3367efb4fa5bdbdf40b35d8ccab063d6005d4b70c18c46cf0400da206d843b9579ba2683b7117c0b793fcbab95f96589dc62c8614f2da74a4e5a3c38bdf753e6e8353bc572a4f4edc69b630d80309a57405844dac33562cae7bfde1a2499d6cf598455d281e92e45f23fd7fe92f62a115c6c861b4cf1d2427b6290ceef71d724ec48a1eac4f31e129c576eaf044f9f2f79102eac265f5c7b4048d868384702baafab5f6ffa5ab59969ae7d1c4318a4bd35c8b42eee940925ee215170a81d1a94c8b1065b90ff4626bb3d7fde2086edc5de13cd40c3eede7e963e8c775ea78366997e9a3169c80c494cfe897ea7ed7ba2a62be75c18eecc130786b17dba81b3d2dbb1ee43a781b2ffda07c6c0e458493530ffe99054f87d169824dee928410d5e411973e9b3f00a3d824b2ff7dfebae9e0f399814907d4176a058f6bc1e5f9ee795a7be4296703a184cffebe1966851feaac4063f2cb873600a33f250979e494234736fdf14862fb70c86acb751eb0648a5b41fb96adfdf907a2b1580c3e2d3e95a1c6e505a89a4a06fac2900e723c2e15591d7418467702da21d7f0c7366aa29a7312017faf46466cb156761403c0f875ac6afe98df4466ae98d2e22afa06d37c256558e703b3769138d534acb6775801ccc0faa9b278cc327dcc4eee928f20bf05229cc0f48ac2be90cd93cb694cdc0471e1d31f9da1e7e9a977f66aa2b8a38e33259585eda46fe9e82ff6045dd02bb7fc3efcdbdbd4e3eb5dd8553bebecf6712ee5162162ea6b1fe2cd4564b76ac1df08ad5678caf454509066f6ec698d6141fddf772143eb408df29173c356192ee7ec5d77607646c9d056cbf86c4d7931d58b2656d0ed2aeff07bc32cf8b4414cb376daa22cf74d65e984addb8831d18295ab85dd7b109bea7eaf45018b1057ebbf30d3aca0a8cf8c025537e4551137a00f9a7eae4137616d6207c2e127b8f97bbfef58898693cc5ffe0001a1470107fffd05b188db77733522f259128f787f0a22f9c2390632979883f46a0d1ff8d67a1e643900000000000000010000000000e4e1c0cdf8287d2073d6609eec784342ab5602f261f14cdde43e2acda1a5f90a292c0700061b100003722a2399cf3177602f17f4d874a6c9fd72c899dad457a29068e2171e8f9a4ed5950db41d0b129c05c97a720ba35121cf8f4197e0e5104a88a96b37d8131466554f6978ab35d4649c58c902fe94e015fe8c0b9248bdb05c0c66b477fa40b15be3dc58e0ff2db305d8e47728cd8d5aaf643d9ff82a4af2be1f59db05ebe1e0bf4744d8075da2df109b745966551785e1a61f62a2dd5d65035933baa95af075a4dcb2894215715631b01a05dffa2efa53c53fc5923b84c38fd2888c8955efe535ded16712b493ea8e8e7f3d91273233ef900ec1097611ec8c201f2ad6b0c7c8aac70d79fcb1451e1d8f6f6b92401899981f2462a4d9ce1fca2ec4add624b7334fd0017247e5a09a6cfb2173cd1203e35ae2d081e456bd7e1d6af93cee779b8a6d008db9651bed09ebee4ca68c4b387a28637cb557b70772325c745719344a49ada5193e2b9dca5c07e3d69f89abd44cae48da9127981b12079262193dcd0395761c94b7886297d323523ca7e10398e4b9a4359ff0b66370e87f3ee266d04f4ddcad5bc9e24ae22e899aa5653f8ed4821c3f7379f8d01b9bce03b40a9bca25fa4417cac5e81c6b3157b810fc8d5ac3d3ff709817098c65f652edbf955af263e75b54f351a339f0af55360b88ad02de6ae428728e5aaf6cd0553c3693359b6e4b842a53111e3e4374c06bc345fae8010034376f5dd9b7427c9da86de8f55c6f19728140e60458e35a49a02cb4f796485d9589890a9405186cc9944ec0f9da2f558b936e5f2895409dfd52b76de27a7688d2ac099f488066c90add0c5c3a683111325538c944c02b5dea4d13fc3804982f3ffba4f67167d172f2390d87c5070803d46217e2f57050511161984513a1e6f1acf30c74e50fae89c215cfc8f2f8d21a94ed9828e6d2ca05d47670d6219b0fba3890f55842f2e7979b82b8da0c105c034bc9b0c024e10f2d7f23c2b69864be11116859d1e397f6bbca615b2d9d84f2330a04ac213b66191864aaa6d2177baea64c05c47c06a0cf36decaa6613d366be5141eedc4e68063f476380ef8d32c3a365a66ff9b2c40ae66fd18550d28b9888398a5b451eef781021b8689e4828c914145374d90be02ef7efcdc4336c12cdb4feb84b0fca1a16bb05df307843a32605adae4d51ee34b80e1073db2328ed8d04605ad4572e7f33bfed0d1a5e3a2d7c7756f65f86fd585032371ff99bd9545a7a62a7aa60eaa201f3dae2653cf9bb0b217f035b24daf6c8b0f662452f05cb90cd27ecc8917a77fadc5d90e025968be3944be33dda246baf059ead13c023f890732026be8e731ba6657a7b2815765067e212b856c87ed8356ec2370ce7271d36d307976733a86919c0b2d475836200f46be6a158e95cb410d49b59b16c6a240a61ae75f430445d43e101adabe632ec8d6fcdbde202097c3a3e0ef7395551163f874978527e5ac440c26cb853dee881f5e6d977bb95e7d4bab1a6c492e709d51dbcb7a1cda3beda32ef12fb4a49aea8f19cdceefe44e37e9d43a7d486498fe14e6e927c3403050604a64c60c5e9cde8a5250bdf885331a71d619f452aad90fa9a168f26a0867d7463e0d3a86c22266364ec1b1bb1fa3625bee7a1f3257223a29e8bac794d1a7de6feced768b8ec0a02dd35269d42042bfa06c2a9570a8f8215ca10dc7c74d57468ae142912ee501f0384714baba32189caf0be0e3906de0ead04ede8adb3c4a5257b0c3fc145855282ad19ebe86631597898467cd09a7a75c25e08ade2d7765f747fe96cb762b19290df5603a82a8db9df2ff45884fb1dfe1ca24dbe4da61ca8a38ac38633dc5adf778e0559a771fc748eae51fe0cf303eb2cd86f700f1e135f35c74becc12b7dd0f010134d0e17229a7f7f6eef4cc8c489195fac42ee80f46ce509eec846ba3489afee7570053ce5b2b98fe0001a147010700fd05b188db77733522f259128f787f0a22f9c2390632979883f46a0d1ff8d67a1e643900000000000000000000000001312d00c7b3affe908092921acc012ddbd332d9b4de4cd0d3f4baa07b9ac8630ca1c48c00061b100003ed1256e3ec04374d407e7e55ad00d6217909bf5f95eaa52783ab8475b0c214f0d2e785867d545446aae27e5b8465ba2fac58293050d248992f5015a76bd51732cdabba5fb78fc7ac9c46a4c0b2c83c89a3103f803669414cd7c189d324714ee44323bf2c203ac5726e9f35268fa3427c2fd217e30b9ba23a7606a9058bf46b68007c5627390b3dd2b57e079778f180d1bfc4e2ef0a715d757c5a890617e719a26940a3fbc44b8a2a5abc23c775f04105eddd3838920b72f7a28988c0874e44f5ae083b8ba0884477f1c7a6b699bf567316fad67ce50ec8ca2dab228acbe91804c3e7d734df7b88bf0478cbeb5787af45724101946d81dc3326f650b5c260ba541c085ad95c70fd169ff44bd56896dd4db21aa3cf4a5eb1c0b3fdc3527c7cd1ae3a08113e67d69bd9854086f570b811668c2682b7dcf86f2a496115625e72cebf805b13d8d73efd9c0a7cf314cc3153b9470481655c9fe84dda120e10652c7e9ad2f6c6f8c221760f172aa2d7c51b54ada10970091e1f7a1cc985d4dd48a6e5b867a25fd261e22e7cb31425438fff4036a6bb08cc4482b9dbaa5aca16667d1cec6442e77832e1b5bc6e8542b8307dc3004a2854735de738cf2f4d5256597e9fe9dfbd3ae2ba64e56abb10fc9740f744227a830d112fd23c1304f23a6525cb8d3011f4cb0b39ca7306d4857d324cfddd39da92f16f654b0eb4173218678ad1547796fff00bf1529d1b3361c06797573c2f488e3307fccfc0f8d9bda4333cbcebfb3e805ddb59c053c58c04e5663e8151ff6855b194f555ca21100e965cad0da2cfee5a939420c2bb0764bdbe91af6f7fc485b7dda6a4483a0c09b4ac04217724bc70635f71cd1065d5c2488e3e0833ea03feb4f8fe1aa70379d7bfc573178e6f22c90b58ee85e181426b5af6b8b3ce5a7e1b78a727560ddef288284384520479188df239156b083f76b94d9042abb16c293f39271ad2cf5c6980ab58004bdfb5806908b4ff533c217fee88f8483dd14b55df97c2cd2627479c504d9e9fbd109dd63284514e8f4d43ba0086487c624dfa207df7640154fe587b10d97286ff4cb52fc6a06de4c429d0edd452631be4e0cf71b434555c596e8c45c299a885d283311780c4bb0855e0919a60d0671f6643a09a98cb7b9da858e2787814d2e139c3a244c5fc0245d51dd759700de5e53d757b078b7df7d7e2bd645dcc0fbf1856a4398409183853d119ddeee94dcafadd7ac8d457670155a36e4f009526330c6f8c205a4686fddc0db213ece3764fc16450bbd318059ac6adaba7c0a60465d168b3c09a553339bba95891273b097412d7d2cb5c5599434bcf3ec6f62c2f14e82e702e3efeade357d5870f6009a521d4b99487db44a6f85436935b9602a5b80a34731d73d4ff9202d82c74b1bd865689c25f378b2626db1e30b980a3567d1ddc2988cab6e9d6f9b5fe71a331e1efcdb5718af345b8bd6afc911f0e82c9001e297a71018141f43d6b694d1559944c0f51f1fd476633738d971a74ee7d60e0f9ec495f307411414901e6802c974690e6fbe74de9494efa5f9ce9443013552cbc73cfc4a15f476e69897d2e1f0b7e848c139a7e84f0c5f8e53f5d761ba30a98de6950cb497e9797823cb98b806143d5a412d4016b97f9c954c420cef50a80b26efc691167d2824ff07e6229016cf92125c51d8f7e2233b9f2cef8d04de2a74d18397f8d457bcd978a9355f7ba2b0d5afd5d076e8399bbaba89e8713e9c4cf37d5be57cfc39c60fd75b2cbabdc81ecb1a7c1b13450718d4a11a1bba70e9ec933904b269af5129226b373cfbac857010c2f69501ced5df7e0b1625383f33cdbde978aef56e7b835f3ee23837af9066d49d40e1e98f5e4be6fa6cd7b485401d64276c44a7c57aad2f367ff3466f2d1a8ecf962e79d060cc6731da9fe0001a147010700fd05b188db77733522f259128f787f0a22f9c2390632979883f46a0d1ff8d67a1e643900000000000000010000000000e4e1c0543ab5ecdaf8b274c0bda28ac14c1097f005a43c545ef0d30ce13c0a292344de00061b100002e0e8650e6adbaedc25afa18beda08790a9db0fcf0042556ffe84eab837f6a6b0c9bd896142015019742cfa46c2b86750473af812d901d486f3aa072c4736ab0b82aa79897090333df1f5097ed5490ce2404acbc6fb34d16180de9ffc7c557ac5896e576e30079e720d886f76472a202add1f954d81b7660cf9f3e24938f8fcae5bc2700eebe6f076f807766a6cf07be02eaa9a9cb2264ac65cbc42cbd52613525399614da2ae8103ad0d6e6930f0320762c43b3d42701a70d71284ef73e11edf5d4003f9e910aa69bb6b708b3cca0e3256d3fdd1ff3c295a5fe26d8669f1c081bd1e73687d7f5ecf97de95c06ccff7908b2bcda7cad01308b863a4f39c4bc73f499564b170a9f9f5d72059989f2c032133a4c7250de697c0108e5a17ce2551d2b3f5df969b1b13399ffc141b1fdb7e6e701b8a591f5fa050a277c07f4b6f435b6b01e06f71a9e8c769dcd3248a6b2b07c635d3896be395560cc1664264477ba3b0cf36701753465e774c5adf87e804d49280035799038da1225f62ccd269a004a749e99b78f23f8e5ac4060c3e056a9d654a8e04e1f519178395d16b0ed570cab5519dbfb0a01540d051b23754ebfb35fc0c391362b1b0193fdb0e0c2e768b0cd81a73c8b6ac48f972e1ac6a3eb39115a591187bf5cdba870ab32cc5b8669a330dedd692413ed130a09b73ffb3a1af74a9c892272c3a5057816a36416769e3698873035656863d4e6ec100244dd65ad3695f1e47bc20fbd6bbc662a55e52058fe11d23650c3e37cd1f00da99b0840a794818df62b1dc0508fc03b99ce41400c1aa00a52090d7df37f49851c83468158cfafa7b851af9af61d136ec0ba6ca95466925081a1e85cc28cafb82bf6866e117eb32575decfea78e065175de8ae66db5e136a1089034af2804addbb39febb82b1955de49492b074769264c37d8dd870fec2424f7f09e418ddf668a1fbba77d9b575bbe119cc48b3a16c8eaefe1edb857966ffb8142676ebd6a2334ddb26084247a0f1001df83624f56c005936fec2625f6618661f939e27796c5d61906ea9438bf90540366e55609a21aeabafc217c23a60821a9a3c21a13f43a65dd88a4c9ea9dfdf41c206eeb42157b17ec1bbfc5608550b19bc0abdb30f22b2dd02c3b474cad58d08165a708341719174fcc4bc8cc986d66d382b1a20e58f0eb72450ae9c56c57d958c39ac3c0fe2c9584fec432db05f78f66c04f049296f85353792659f98c18c42da952193ef0b4b7259886aaaf69d6adcd8009498d880da8f43ac6e1d8c893eaa38ed5ef5a2716a1e0a61d9f39d77dec91d9c6b14a0b424203a700bc41c07b36ab0d180cb738deb676832e13a3d8d701fd38eb192a8f3d5affb9b5f56d3e5e3c9127c4d7c81c495a9fad90efdb4d6f86224117af17b4f86ef5e12fa17054f3770dfc964c9323ddf6b8e850fcac7e82c620cbcf50ea6962e69deb2f3a6c3f650a9c5c14653bd69a3d2d250efe5a05f4b1dff36b78aefdfc55a129b54d42fdbad60b37419c76a3e9970adc3cfd0ec348ae7a9d9c0716406e626af2948b2d2696002d11918b57ed3d1b902682daabeffec13b899567b5bd2b09ab713f1687d0309db22e316326ba7cdba717a3aac9811bb0e5a0fa4f1f8abc54cfdb076e1f231ece9e571bfccf406f4f5d85acd3c7b7f4acca04eec47b36494426fcc4d74ed1a0a95d38b7df77cbd073a7badb980bc09827fc5b48a0adf908dc7b690f72dda5523a01bf0c0af864e6f3ec452c4ce7caa032f9563352ee973be3988a79cff1ef69848f7eb8a9b54193121a55322b184275b08c21d891fb84f879557d7ada6e0c0db1785b4bf621c8caac7fa094cbba8a8538ac2bb8489158db8ef1fd2818a5daa794e15572250bff62379e5c99bd8a84ffbf68895eeaaae495b1fafb9593d54e7ad430fe0001a1470107000300000002000000000000000102ab7a85d532b3cf955d440c7b92a368c65067cd05588385ae3bbb45d8187825da070002ff03000000000000000224a96e5c0f0fc3cd3d7b17e192c6dcb09c5bb1b5be0cfa3779c2c24cb37ffc4d34010000002200201af088d9988507e32444ab21a9f81616aea1c3bcd16ad4ccf2a5707255dcd1a9fffffffd0000000027a318400000000045bcc8800000000003dfd240020000000000000004220020205f93e0d37d24c30b744c6557445988860ed0fc3ca24482a4c55b63884661040000000027a3184000000000638a2d800000000003dfd2400000000101000000000000000024a29b71d6f6d8bb043041ef7c26d5fc5424678d770a8e577e860d77e0128d7e0d000000001fa0252600000000001600141dabbcfbe5ad548fb216213f29a3c1a1425be153000000000000000101000000000000000600000000001e5d7a1600141dabbcfbe5ad548fb216213f29a3c1a1425be15300061a808688db77733522f259128f787f0a22f9c2390632979883f46a0d1ff8d67a1e643915d166c2439ed1d5bc23187430f880d78120873deae182197fb6690c63edde560000fd0259406c23a27b31022ba82a5011ea1330b46f67b3d8b96de7fa2ae9d7fc0b1b2807e97c1fa4e5a11e0cc7ec09810ba719af7b95c09a2ab1c5400723087d2f0c4a3f4af388db77733522f259128f787f0a22f9c2390632979883f46a0d1ff8d67a1e643915d166c2439ed1d5bc23187430f880d78120873deae182197fb6690c63edde560001006b0247304402200e163c65da5d684d8a16f17377e0fa0afd13c82338de0d210c7a1a0f28a17ecb02204f2f1212f1b123e6536341a246d575750365e4c90f0d720bf176f0e4ef266313012102c26f5ff5c5a29adbe0cf8759332d602136094a0f11c4423bc251ee53aef87143fd025940f391ffededb5b824f85db92103f7f0a70903a18336c1ef6ee57759ee94fa8d20358ab2eda8a7ed6f4b271ea1416007940cb694ff0c29ad712ad0a168e2c787b2ff00040047304402206c23a27b31022ba82a5011ea1330b46f67b3d8b96de7fa2ae9d7fc0b1b2807e902207c1fa4e5a11e0cc7ec09810ba719af7b95c09a2ab1c5400723087d2f0c4a3f4a01483045022100f391ffededb5b824f85db92103f7f0a70903a18336c1ef6ee57759ee94fa8d200220358ab2eda8a7ed6f4b271ea1416007940cb694ff0c29ad712ad0a168e2c787b2014752210208ecc4fb85c80be134fe55d186aadc6223da2130c19e0054bd73f3e92188bc1b2102db80bf3ce539911d400ad7c8fc6455f2919171601cdb6dd1eaba9a13ec6fd29d52ae00061a8088db77733522f259128f787f0a22f9c2390632979883f46a0d1ff8d67a1e6439000000000000000000000000000007a120ff000124a96e5c0f0fc3cd3d7b17e192c6dcb09c5bb1b5be0cfa3779c2c24cb37ffc4d34010000002be0fd1c00000000002200201af088d9988507e32444ab21a9f81616aea1c3bcd16ad4ccf2a5707255dcd1a9000000000102db80bf3ce539911d400ad7c8fc6455f2919171601cdb6dd1eaba9a13ec6fd29d02ab7a85d532b3cf955d440c7b92a368c65067cd05588385ae3bbb45d8187825da000000061a80000000000000044c000027100000000100000000000000020004ff0000000000000000ff0000000000000001000000000000000000000000000000000001000009c40000000027a3184000000000638a2d8099db32761e7adfe0311fc55cd09e6b885b9da5306aec45f18a094fec153747a52415d166c2439ed1d5bc23187430f880d78120873deae182197fb6690c63edde56000000002b009f240000000000220020205f93e0d37d24c30b744c6557445988860ed0fc3ca24482a4c55b63884661040001cb527b847d4dd0996a9976469ccb970742d0d671f0630d955211924df177631d7502ab009fb7b25773f69cefe6935338ca1988f60e47653ac35a4029d34f5c96000439f05280ab3807c97fae8fddc28dbd6d4205b18486ac42040934099f4325db61067eca53a21847522796799f44e0ef3416f622efbb6d934396abb8aebb5e6e9c8f6ea7d8d841456ad634bdc64fd3234b3172280d24397c4ae9886c7c8aab88957a22c5e5ad74465546c5d66eb6850caac0124232aea9e5d0077daf70a30e325ba0bd0066bd3767873f67e7c5d3290388702bc59dac3259b2b14032499e8fe2646284d99290910aba711423ad3bae2cb1b4e47350b0dd5d5c924ae720bbd814604b5249580cef86c2fe9394f5fa07f56b7aacee46ca00a127bf0f5747d9dd8c93352731d92b8891ccb3072170612c981b6c73a76e36043ed812e5aeaf89d7faad00000000000000010004000000000000000000000000000000000001ff0000000000000000ff0000000000000001000009c400000000638a2d800000000027a31840a46aa4f35b1c3ebd340deb44bf796b673f59c76d526a4559bd3de933685387150346de84396b4d528a5df97c98d3de60ca4d3b35d28a910aa5c1bf9bf65da976350000000001000000000000000102db80bf3ce539911d400ad7c8fc6455f2919171601cdb6dd1eaba9a13ec6fd29d0afd01fd02000000000102296099d8ecfa948512f56c9bd7eafc3874e9b464790301211ff040d3c2bf65d60000000000000000004cb8b39d3b78c2333c7e074acf487458da3682aad6fda9a372255d3cc1514a1f0000000000fdffffff03a086010000000000220020aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaae0fd1c00000000002200201af088d9988507e32444ab21a9f81616aea1c3bcd16ad4ccf2a5707255dcd1a9103c1800000000002251209a59d6588f94df3775cc37fcf530dc76580286ad414d9b988331a71552d0fa590140857b2f29ff5384ea1ad85d6d076e9a8ec8258a88367d0d892b565cfca18f810e5d3e301f253012dac79545842f8bb59f91351deda07365ea40d6be71738f5db30400483045022100a4b4da4bef4d9584f68206adef9b493def56cea0de598488c194ff179d8c7b8002206e4b0eb365e4d43e95c349016b2b592a413ec5537c2ded6af0b13216535a9c5501483045022100d5fd961852c5ea9f326ee5696875cb69ed2ac082b93b881aac09fc2dad0ed79c0220392085519a39c9fd48996b2413cf8c816748004e7487ca0ad5bf26b1566182fb014752210210f9b3c19267dd4a2150f041096ab92b59db063fabfcb84bb1c3bf7da09cebe521039f967afce64ab9a5da4edbac9f3f0dddb2bdec02d12c124c415690e588eb50a052ae801a0600061a8000002a0001ff8688db77733522f259128f787f0a22f9c2390632979883f46a0d1ff8d67a1e6439a96e5c0f0fc3cd3d7b17e192c6dcb09c5bb1b5be0cfa3779c2c24cb37ffc4d340000fd025940a4b4da4bef4d9584f68206adef9b493def56cea0de598488c194ff179d8c7b806e4b0eb365e4d43e95c349016b2b592a413ec5537c2ded6af0b13216535a9c55000100000000000000020004ff0000000000000000ff0000000000000001000000000000000000000000000000000001000009c40000000027a318400000000045bcc8807ba7b4e5bc38a5539fc4af56886665324dae2388a36062dbc02a3b8ad62abd2b24a96e5c0f0fc3cd3d7b17e192c6dcb09c5bb1b5be0cfa3779c2c24cb37ffc4d34010000002be0fd1c00000000002200201af088d9988507e32444ab21a9f81616aea1c3bcd16ad4ccf2a5707255dcd1a90001f13220c847e1bd076e28522c94cf35f787bcd4fc11a1aefdbcfbd0d448ba7150523549e4347f9a59fda8961e658d8b363a6f9d8eff378cce170ad269c4b978f60004739cc824f0700b15a42ebf94401769ea2d22823353fa903be65181362a888b306c537515bfcb0dc1e1115eeb4e815887324e030b30a8fc32c89415618c24344f1ef7ee9cec3e72b92dab710efee375a4772fc4a0ab7cab72a8267dc4dad963f06222ec4eda3b2b35a056a5c999864235c8c29e2d67cb2084fe5cf4feb68f4e986472411da6723729081f67ca093275aeb3ba31757f778ceb8fc765df4a3c02b50253efa47f9112cf7b9427218b40219c31c127492ccf51a0aca39df911b7e76a5eb9299b716b18f12298bda3970460247b60ef2b56160e7d7446ee96258dd5fe7dce1b64d3fbbf76aa48abcc7118c17e09005b865b6da07b9482c331df56cf3c00000000000000010004000000000000000000000000000000000001ff0000000000000000ff0000000000000001000009c40000000045bcc8800000000027a318400edc489971adba4e677a7d5fef263e13620dcd03963aadb7c52bc164a66307ab0346de84396b4d528a5df97c98d3de60ca4d3b35d28a910aa5c1bf9bf65da9763500000000000000000000000000039f967afce64ab9a5da4edbac9f3f0dddb2bdec02d12c124c415690e588eb50a00afd016302000000000102549d7ace74fc0232a7e14f65cd5dac0dc4c68b9495f921c1493324c52c4bc9f700000000000000000035f42f05a50f23f7e81c8d3110871c8bd2b82d32a3e2c62779397e6df37995400000000000000000000360e31600000000002200206aa3d195b1c5bda9d5bbe9b9b481a9b2e17473fed97702704d21fd6e00f39c55d8760100000000002251209a59d6588f94df3775cc37fcf530dc76580286ad414d9b988331a71552d0fa59906e0100000000002251209a59d6588f94df3775cc37fcf530dc76580286ad414d9b988331a71552d0fa59014044fcfc2a2d35676ceb83c5217e55db7cfb30129b3350eb23abaf42d583d101817bcf14ce64513375b88acdd25eab5769b58b519e92306f0b2a29c6418aa73e8e0140e3d766cd2476e1c40c520d6a2c68a65ac5f25202a42a6bfd753958ff096aa78ba4aa029d5eabc899bb7828aeb800f8fe622803ebbddb10a882960aa332aa21a9801a0600061a8000002a0000ff8688db77733522f259128f787f0a22f9c2390632979883f46a0d1ff8d67a1e64394cb8b39d3b78c2333c7e074acf487458da3682aad6fda9a372255d3cc1514a1f000100420140e3d766cd2476e1c40c520d6a2c68a65ac5f25202a42a6bfd753958ff096aa78ba4aa029d5eabc899bb7828aeb800f8fe622803ebbddb10a882960aa332aa21a9000200000000000000020004ff0000000000000000ff0000000000000001000000000000000000000000000000000001000009c40000000027a31840000000002de544807637465daef2054492a18c48e33e4b67a5abb832fb4f36bc0de8006ed23ab72e244cb8b39d3b78c2333c7e074acf487458da3682aad6fda9a372255d3cc1514a1f000000002b60e31600000000002200206aa3d195b1c5bda9d5bbe9b9b481a9b2e17473fed97702704d21fd6e00f39c550001d8737c0d71faaced2f80aafaeb2b7fdc1561fcc50a04dcbd751f071e236239857ab5956ffce0017fff28635bca90aabc5a89f15e1a2a28fe31e474e97a419edb0004a1e194f4fa7b508de8b8e032dbaf4fe0c6fd6e50f3c843c58e7dbe85795b8f141d9c1e4509d0087df32d77dd42192e958018bc4cc945e7eff49e6351f6f121c7ed0fc6efd1c37bf6532c11ea356fd5112c182d58e35a0537652053a1da69a96737884ab9d55a2c7ed5a3fc4d4013c46d9a05268d6c3074613d9d4f6634ddf56b8f86bc8d4cd7496c1f1ca53755849ba6d532a7b364d3ee35e6e75ee81f4823ea449f6a4ec2cda780bf57804004d269a348881676b32f08fb2cd4f76d382dca16196296afe40f961d8d89690fb78d69b09a03e804e3a9f1f0e8622df513666d4c3b37b27b232d5e1fe2daef59c891946a5153b2017354fc7808939d78a6bdc18700000000000000010004000000000000000000000000000000000001ff0000000000000000ff0000000000000001000009c4000000002de544800000000027a318407628ac5ffaec424592e2982d06f0d33af3a11a433a49ae2a34d0ffbca4354af50346de84396b4d528a5df97c98d3de60ca4d3b35d28a910aa5c1bf9bf65da97635000000ff03e55ccaeae4f9ac9f6881a9190ba34d2a14e043a4c4ebd91cb0b0646375183ac3000100400000ffffffffffff0020df6e8e87e6778d04281bf8a7c764c5941f3e6ba30bfafb78bd5cee9cd06953ff80007fffffffffff80000200000000000000000003070b89f1d49c4cfba425b334768ee20500000000000000010003da3fa65ef88e47fb9fe95351fc8690ec00000102fc6501ffb2af39ff0121386985e0987d008868beb4605ff90ae905650aad67d7e9df4ce486f5d6f979bb6980e405382f5e05573bbf403a7641cf93ee4e7fed11997cc970f4e0cea278d689799086e0b0d6d806226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f02fc6501ffb2af39684bf79d030100900000000000000000000858b800000014000000001dcd650000000001" + val decoded = channelDataCodec.decode(bin.bits).require.value.asInstanceOf[DATA_NORMAL] + assert(decoded.spliceStatus == SpliceStatus.NoSplice) + assert(decoded.closeStatus_opt.isEmpty) + assert(decoded.commitments.active.size == 3) + assert(decoded.commitments.all.size == 3) + assert(decoded.commitments.params.commitmentFormat == ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) + assert(decoded.commitments.params.channelFeatures.features == Set(Features.DualFunding, Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx)) + // Local params. + assert(!decoded.commitments.params.localParams.isChannelOpener) + assert(decoded.commitments.params.localParams.toSelfDelay == CltvExpiryDelta(144)) + assert(decoded.commitments.params.localParams.dustLimit == 1000.sat) + assert(decoded.commitments.params.localParams.htlcMinimum == 1000.msat) + assert(decoded.commitments.params.localParams.maxAcceptedHtlcs == 30) + assert(decoded.commitments.params.localParams.maxHtlcValueInFlightMsat == 1_000_000_000.msat) + // Remote params. + assert(decoded.commitments.params.remoteParams.toSelfDelay == CltvExpiryDelta(144)) + assert(decoded.commitments.params.remoteParams.dustLimit == 1100.sat) + assert(decoded.commitments.params.remoteParams.htlcMinimum == 0.msat) + assert(decoded.commitments.params.remoteParams.maxAcceptedHtlcs == 100) + assert(decoded.commitments.params.remoteParams.maxHtlcValueInFlightMsat == UInt64(500_000_000)) + // The latest splice is unconfirmed. + assert(decoded.commitments.active.head.fundingTxIndex == 2) + assert(decoded.commitments.active.head.firstRemoteCommitIndex == 1) + assert(decoded.commitments.active.head.remoteFundingPubKey == PublicKey(hex"02ab7a85d532b3cf955d440c7b92a368c65067cd05588385ae3bbb45d8187825da")) + assert(decoded.commitments.active.head.fundingTxId == TxId.fromValidHex("56deed630c69b67f1982e1ea3d872081d780f830741823bcd5d19e43c266d115")) + assert(decoded.commitments.active.head.commitInput.outPoint == OutPoint(TxId.fromValidHex("56deed630c69b67f1982e1ea3d872081d780f830741823bcd5d19e43c266d115"), 0)) + assert(decoded.commitments.active.head.commitInput.txOut == TxOut(2_400_000 sat, hex"0020205f93e0d37d24c30b744c6557445988860ed0fc3ca24482a4c55b6388466104")) + assert(decoded.commitments.active.head.commitTxIds == CommitTxIds(TxId.fromValidHex("99db32761e7adfe0311fc55cd09e6b885b9da5306aec45f18a094fec153747a5"), TxId.fromValidHex("a46aa4f35b1c3ebd340deb44bf796b673f59c76d526a4559bd3de93368538715"), None)) + assert(decoded.commitments.active.head.localFundingStatus.isInstanceOf[LocalFundingStatus.DualFundedUnconfirmedFundingTx]) + assert(decoded.commitments.active.head.localFundingStatus.signedTx_opt.contains(Transaction.read("02000000000102a29b71d6f6d8bb043041ef7c26d5fc5424678d770a8e577e860d77e0128d7e0d000000000000000000a96e5c0f0fc3cd3d7b17e192c6dcb09c5bb1b5be0cfa3779c2c24cb37ffc4d340100000000fdffffff02009f240000000000220020205f93e0d37d24c30b744c6557445988860ed0fc3ca24482a4c55b63884661047a5d1e00000000001600141dabbcfbe5ad548fb216213f29a3c1a1425be1530247304402200e163c65da5d684d8a16f17377e0fa0afd13c82338de0d210c7a1a0f28a17ecb02204f2f1212f1b123e6536341a246d575750365e4c90f0d720bf176f0e4ef266313012102c26f5ff5c5a29adbe0cf8759332d602136094a0f11c4423bc251ee53aef87143040047304402206c23a27b31022ba82a5011ea1330b46f67b3d8b96de7fa2ae9d7fc0b1b2807e902207c1fa4e5a11e0cc7ec09810ba719af7b95c09a2ab1c5400723087d2f0c4a3f4a01483045022100f391ffededb5b824f85db92103f7f0a70903a18336c1ef6ee57759ee94fa8d200220358ab2eda8a7ed6f4b271ea1416007940cb694ff0c29ad712ad0a168e2c787b2014752210208ecc4fb85c80be134fe55d186aadc6223da2130c19e0054bd73f3e92188bc1b2102db80bf3ce539911d400ad7c8fc6455f2919171601cdb6dd1eaba9a13ec6fd29d52ae801a0600"))) + assert(decoded.commitments.active.head.remoteFundingStatus == RemoteFundingStatus.NotLocked) + // The previous splice is confirmed but not locked by the remote node. + assert(decoded.commitments.active(1).fundingTxIndex == 1) + assert(decoded.commitments.active(1).firstRemoteCommitIndex == 1) + assert(decoded.commitments.active(1).remoteFundingPubKey == PublicKey(hex"02db80bf3ce539911d400ad7c8fc6455f2919171601cdb6dd1eaba9a13ec6fd29d")) + assert(decoded.commitments.active(1).fundingTxId == TxId.fromValidHex("344dfc7fb34cc2c27937fa0cbeb5b15b9cb0dcc692e1177b3dcdc30f0f5c6ea9")) + assert(decoded.commitments.active(1).commitInput.outPoint == OutPoint(TxId.fromValidHex("344dfc7fb34cc2c27937fa0cbeb5b15b9cb0dcc692e1177b3dcdc30f0f5c6ea9"), 1)) + assert(decoded.commitments.active(1).commitInput.txOut == TxOut(1_900_000 sat, hex"00201af088d9988507e32444ab21a9f81616aea1c3bcd16ad4ccf2a5707255dcd1a9")) + assert(decoded.commitments.active(1).commitTxIds == CommitTxIds(TxId.fromValidHex("7ba7b4e5bc38a5539fc4af56886665324dae2388a36062dbc02a3b8ad62abd2b"), TxId.fromValidHex("0edc489971adba4e677a7d5fef263e13620dcd03963aadb7c52bc164a66307ab"), None)) + assert(decoded.commitments.active(1).localFundingStatus.isInstanceOf[LocalFundingStatus.ConfirmedFundingTx]) + assert(decoded.commitments.active(1).localFundingStatus.signedTx_opt.contains(Transaction.read("02000000000102296099d8ecfa948512f56c9bd7eafc3874e9b464790301211ff040d3c2bf65d60000000000000000004cb8b39d3b78c2333c7e074acf487458da3682aad6fda9a372255d3cc1514a1f0000000000fdffffff03a086010000000000220020aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaae0fd1c00000000002200201af088d9988507e32444ab21a9f81616aea1c3bcd16ad4ccf2a5707255dcd1a9103c1800000000002251209a59d6588f94df3775cc37fcf530dc76580286ad414d9b988331a71552d0fa590140857b2f29ff5384ea1ad85d6d076e9a8ec8258a88367d0d892b565cfca18f810e5d3e301f253012dac79545842f8bb59f91351deda07365ea40d6be71738f5db30400483045022100a4b4da4bef4d9584f68206adef9b493def56cea0de598488c194ff179d8c7b8002206e4b0eb365e4d43e95c349016b2b592a413ec5537c2ded6af0b13216535a9c5501483045022100d5fd961852c5ea9f326ee5696875cb69ed2ac082b93b881aac09fc2dad0ed79c0220392085519a39c9fd48996b2413cf8c816748004e7487ca0ad5bf26b1566182fb014752210210f9b3c19267dd4a2150f041096ab92b59db063fabfcb84bb1c3bf7da09cebe521039f967afce64ab9a5da4edbac9f3f0dddb2bdec02d12c124c415690e588eb50a052ae801a0600"))) + assert(decoded.commitments.active(1).remoteFundingStatus == RemoteFundingStatus.NotLocked) + // The initial funding transaction is confirmed and locked. + assert(decoded.commitments.active.last.fundingTxIndex == 0) + assert(decoded.commitments.active.last.firstRemoteCommitIndex == 0) + assert(decoded.commitments.active.last.remoteFundingPubKey == PublicKey(hex"039f967afce64ab9a5da4edbac9f3f0dddb2bdec02d12c124c415690e588eb50a0")) + assert(decoded.commitments.active.last.fundingTxId == TxId.fromValidHex("1f4a51c13c5d2572a3a9fdd6aa8236da587448cf4a077e3c33c2783b9db3b84c")) + assert(decoded.commitments.active.last.commitInput.outPoint == OutPoint(TxId.fromValidHex("1f4a51c13c5d2572a3a9fdd6aa8236da587448cf4a077e3c33c2783b9db3b84c"), 0)) + assert(decoded.commitments.active.last.commitInput.txOut == TxOut(1_500_000 sat, hex"00206aa3d195b1c5bda9d5bbe9b9b481a9b2e17473fed97702704d21fd6e00f39c55")) + assert(decoded.commitments.active.last.commitTxIds == CommitTxIds(TxId.fromValidHex("7637465daef2054492a18c48e33e4b67a5abb832fb4f36bc0de8006ed23ab72e"), TxId.fromValidHex("7628ac5ffaec424592e2982d06f0d33af3a11a433a49ae2a34d0ffbca4354af5"), None)) + assert(decoded.commitments.active.last.localFundingStatus.isInstanceOf[LocalFundingStatus.ConfirmedFundingTx]) + assert(decoded.commitments.active.last.localFundingStatus.signedTx_opt.contains(Transaction.read("02000000000102549d7ace74fc0232a7e14f65cd5dac0dc4c68b9495f921c1493324c52c4bc9f700000000000000000035f42f05a50f23f7e81c8d3110871c8bd2b82d32a3e2c62779397e6df37995400000000000000000000360e31600000000002200206aa3d195b1c5bda9d5bbe9b9b481a9b2e17473fed97702704d21fd6e00f39c55d8760100000000002251209a59d6588f94df3775cc37fcf530dc76580286ad414d9b988331a71552d0fa59906e0100000000002251209a59d6588f94df3775cc37fcf530dc76580286ad414d9b988331a71552d0fa59014044fcfc2a2d35676ceb83c5217e55db7cfb30129b3350eb23abaf42d583d101817bcf14ce64513375b88acdd25eab5769b58b519e92306f0b2a29c6418aa73e8e0140e3d766cd2476e1c40c520d6a2c68a65ac5f25202a42a6bfd753958ff096aa78ba4aa029d5eabc899bb7828aeb800f8fe622803ebbddb10a882960aa332aa21a9801a0600"))) + assert(decoded.commitments.active.last.remoteFundingStatus == RemoteFundingStatus.Locked) + } + test("encode/decode cold origins") { val origins = Map( 13L -> Origin.Cold(Upstream.Cold.Channel(randomBytes32(), 2765, 1863 msat)), @@ -264,7 +358,7 @@ class ChannelCodecs4Spec extends AnyFunSuite { assert(originCodec.decode(trampolineRelayedBin.bits).require.value == trampolineRelayed) } - test("include directed htlc_id for closing channel") { + test("include htlc_id for closing channel") { val closing1 = channelDataCodec.decode(hex"0011015847757d05a5e2639d5777a9f46a8dff49fcd5dfba142d93c4de6af54e4620e701010310100002aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa0009888a8f1a9231e36da2168b0c3147a097dfab1f4798cc0a31d3e1dce336471cd080000001000000000000044c000000001dcd65000000000000002710000000000000000000900064c0000000186b02000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000001808022a698202bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e6300000000000003e8000000003b9aca000000000000004e2000000000000003e80090001e03b0a410a12070c62d9b401915adead19b3ff2ccdca0e26eb6ef3bfcaf441ea243033429bb2a4d35268c55113ea97544f4e5c74e85d5fc686722b00927572ae1778303e62d2b2f0eb6256482a72e0b3680e5a2ce9f81b09822336c1b253df3fad71e40023b7b1a73bfc39356e17713e070cd4e9d5206cb4a6b2b0c5965bb687c39f5d7d2000000140800000000000000000000000000100802aa6982000000014a00825847757d05a5e2639d5777a9f46a8dff49fcd5dfba142d93c4de6af54e4620e700000000000000004422f65fd6bb15259a70d2ec79aaa6953d2a58e7d3a4eae779276eab45fe40f30000000000000000000000000000000000040000000000000001000200fd05b15847757d05a5e2639d5777a9f46a8dff49fcd5dfba142d93c4de6af54e4620e7000000000000000300000000047868c0e60afd49d053a0660837e692ca97e6a3a83536bd40a7cce944d0e2c1d2abe7dd00061b10000332c48fe95695da4f2099a3943abf99be9705cbec2f1af961298d701bec53decce487d6910cead042d5a45c37b7009fa196fa2dbc55b44490000a6a60f7a18d7b2fd832253c16a70295a8099a685c42da3a0334248bcfd91417dde694c3a0afd9afdd74f710b6df5a0e1674432670728a221c4eb52b4a4971cfdc8a8205493ce15d3ac666ac8cd6fd579fd62a17b946319c9b67fe2e401c0a5685430a08cf7da7309f54a23cc0d2e5b72d643cdb9a63e8a8f3add3130cca9eac0efc23db0c97da94d1ea8fb512d6b0a2fa47c21b43b034bfb0f2918585c62d6f4af2a95c4ffa27632cd3724033a53fe1d78fe7ae98b81e6e724949182fb1e214765796de6ea9622af13d6e90181c66287ffd2811b4fd8b6b71d50ff45d8ec297906b8d917262eb9572fe92ca181480037d9e0a0c81d6df5894f70e989579a0a363bd5a690d69752d854ae28c79025082e59c01345c2b054f43655d94fc6c088e12be04266f2c1aa6574ede4f20892acbd531f82321a752c65a8760f264e9de46f4462b2bba67192cdeb4e281a371a121595e8389cdd90885cc51b05f3712ec2dfb934ee90709affde926c6b6eb542709cb2d8bced2ac903bd8c15ecf9fc2189929ea031133ff1b4897b26ae5da97e9ed49d25bbf0143a22c0827b0b24b6a020fe2ce5f769939d71c1ddc94b7ebbb84547b1b1f49e90728d19bd55f3730044e583089cea132462184c6ce6d7f7b07b4b2bd6fdf89f1340b65031813a9abe9ce51377885235bffc005346af8218e9d32410c7c0584ae13a4b0e75075b76fddc9ce36ab9ec3dae511cef81760d98653431a464dc81bbef029797896032f9601d9a6b0c45ad790b0f16914c382d698dc46111a6d4aed0f7515e338d1e3668ae27bf06c0b50f14f9ec8e14abc88ac68b0ad3c6c4cc307ce6b6c084ec3399c1b1a7748d61911ee1c425973c737b55d4504f4509e25d5efb5793fc382cd7e0f260bbba79e666f09dad12aded5d402bc99e65098f4ad68f5f88179930cace8b9b23720f3261efa542cb2b0c488fd6d0ddc3566b6896580349b90584ba61bea6c20a1541be43f71f74db167282ab6e19501f477835a4d444717de9b1f70361cbceed8b71b51e91fd939084c53199fe3171a22cf53bbcb8b245a523cf5af1a40bb9ba6e86f8592d9292bfa3efdce8a34583eb32beebb0c8847e051802f977e6e63765c3235fc7b8b3964f3b82ec5686e77f5901cf3887e5653ec5f2b02842454274db8bc9fa1bd73d6fa1ef786bca55fd6813e3016abc4ba87db319ac6ab7ddf8b9ee11cd92f3a506b3af86e825cea498a5ce72dc6a6c763e6392a21edd3722d6a049011530ebfc6f0784430ebdbd5e4fe3c35bed8cfc51d915a101ffb87cf2b269274aaaf8b37ead9451e371ee8b5958ea8300ee568c707c913344f4bb5344e62b095384212b1d8b00873af5feb6e756818e0806e42771875911aa67ab6e0c4077f67a6a8841ca0fc69d57c818152a52d8468f4cd623feb1a30241834a3d02348c103ccefde6997709bc8e7569efabf4c5f712826fb03ba1c01e8f57edb113734e03a4933223149b4ecd844e933210ab4ca366d630b02005a8d0189737a2bff410f0a97aa3ae5811877e27de9b451e35f1c901c379f864e02c823ad6cf3e92db32437f53c108960a012c4d28d3b66f39b66b1b57af170b00fb976fc63b6123cb22ee216cdc48b0a9e8e11bef5fc36f6e5bf3d40eb28f7c0d7c498d7c740c0008a9cade8d147fd99a5b323b92e93fc64ec2599d7d19a997fcf8653f059e082fda5bed8c79ee29c356597a56dc08a5965aa69f44b7d2377d7bf75819a74721eddc5218cf1f16a1ff5f50e37fe14a51d4206d576ce63c2b9cf472e871fa253a36e4c469abfaf17e8e910f966b6ce0075a6088a45cdcf4ef095df06e37ed994b5e0fe0001a1470107fffd05b15847757d05a5e2639d5777a9f46a8dff49fcd5dfba142d93c4de6af54e4620e700000000000000000000000004c4b40095cc3acd7792306bc5792d504325926816fe42457c6ad29b00741901fbe46d6c00061b10000281f8622444b2130756cff4fa963aebe1a6591ba779ae258b2c4a4d29bd1f1c5227f7db4ff5d623465d52dae4445a3e4079fb3d10e286e1f51c38e5f336e543ffd11936c2802f5f952689b7f6aab330304fab68e3eb52eaf384291241330c1e397275f0b5c75033f68ac4231db6e9909bba53fe6f5c811f1d08043d47c2c29830cc1765297c6f175dcd0fc66aba6939a5d8572c1e6211f38081b6072ce0baa79f51d5a24b4ad545a57fc40d55b5a6e2f403789635478df97f821b5ac59bade80d3ff7a61619818924fd22397f8eab9ba8b627cc1574f217ed31ab8beb2f14568d2959b859baa7ecbdc7f83d58f24c406c9600081d087a039c01c42e8e998558265318a63c17c8768a4f5aa08c611f24378f8f05b376e51b9cb2d303fc587f25112e2b02fee7783ee6956c1cfc170a3e5e4efe3daed57dbd60d82b3ddb2c3621150a29b043a99aa25ec4e1e6dd8cd3e530fb423b2826a97f7947e73fda2d0334f22282f65306a54d0544fac2b2e1910f3627cfa9cc933fc70f78c70f8d9e7a48988981393355c0170d7ae1b5e3bf0cbac64796ef9780b6bee51394d9dab37f7706318c29432cc28ce7133c6f9ab109e414b1c721bd61acecc903c3cf7018eeac6cd76414905abb2139f32225238d546f70b18164d48f46521b367053fcb3ddf58256c46296f0a38f049e789248bb3b91744206e445444280487eb53e35af868c9a5c097be32413523e25ec589fff8809053c68ac22c312e113c8266f1f4e6648f48ba435d03a5dcd22eae0065e5dd2a4e8054952fefb71630350ea229b6a22e3799713fce8b97e89dfa792a140f3105a32fc3e61b9e536ecf7e0617c4e285eab7a6aa3ea53842ac994e302c1699f0efc2fb9d8ac7ece2179821cb94d8c52079c74fc4178bbce4031137f1c78d2275eae8e7dd43b627329261fe8215421b4aa5e3ad3034686801863132707eb2b36f0510c90ba0cb901c05a9b32d3535669b2ed2a43d594ded9bb4c6ff228b352737fe84bddae6b2f6320d46853e30b14c842aadd9ad2f19d0ef26ee99f1bfad18983b3fd523fd55af06c2a95a8bf9eee832028d23a48667dbb4132cdabad7939dde02e77e3f0848a433591a4e75115623fb29bf03a88a2a744dbbcd6997e079150f1818ef6d31880665ba03ba9e9def8fd468f9fd2ef67f240dbb6c74c2f2ba303ff544db022ad5d034080e6eebaee744500f5d970837658661bd09c6ce4fed4fb828743ea26624b256318e6741f005533692c8a4e67296551f393a922a3576a03a2b1734acfbd4574663dcaeb594d5572b6006b8f593d25b91ba19991c5df5e9861a85206a677beff4c89426b374f150514331864a436f5477f2d1533584ef56dc0b9f2d4c8743e0dcae471a1eafde3b9c353bbbef9fe41305ec332b7e1043282fc7c057d4ebeb15e6c3f2cd774d73842bc20724f4a965c902d13088329ed66783467b03c186a6ed2f5ab19d62a0ac03a01eccdbbd6d1f09fc9d6113b33dabe3140b10bccefeccaa66ea04850818293d44a33829dd6cc80b2be8e9ebbf3061fde598a603d4fe8f4135dee713f4f88459c2ac791d89ad7cb59f0dcbfd96d2ddc954ea735a9aeaee8f47127b7dd4cfd7b08019602e8acc6cd37a7d640e24b589abf2bcb9d1eb4abd19bbe1150938ff90f49b9c5203f1c17c8b85c8a26105995f053fa54f56db9bfdd10f37d9aba6899a2ed375f49197cc3e36cc82839edc36f37d5b661ced3a7528f8f8e438aeb6e8f5c566fa9249fca6966a2dc26f01ae949dabbbdfd03a849b17351dbfa181b219abe93bc97d793f5f72a1d19adaa92043e0ec1200b592c52b3270fd77560f5f2b4340bf936366d79dab9d6c1c7c867019019324b7a74c8d82990e21d2b7bb55ec836f2dbbb2543b77a6f565475ab4fea1822c99a4c1e5885b280fe0001a147010700010000000000000000000000000392de23ee259d18e48e80b3dc7dad83f8cd87a64b4ac0da7dd94871e5ea876ec60a5e020000000101010101010101010101010101010101010101010101010101010101010101012a00000000ffffffff0140420f00000000002200203ac31ef83f5b594ea58c988be0f62bfef04500ba6d678c11aaeeda1fe662f0c700000000061a8000002a000000000200000000000000070002000000000000000003ff0000000000000000000009c40000000007735940000000002aea5400245847757d05a5e2639d5777a9f46a8dff49fcd5dfba142d93c4de6af54e4620e7000000002b40420f00000000002200203ac31ef83f5b594ea58c988be0f62bfef04500ba6d678c11aaeeda1fe662f0c7475221032991639ef70a233d4d0df569b4d065ed8df5b68ccb507208b8f10984c5a297b6210392de23ee259d18e48e80b3dc7dad83f8cd87a64b4ac0da7dd94871e5ea876ec652aefd013502000000015847757d05a5e2639d5777a9f46a8dff49fcd5dfba142d93c4de6af54e4620e700000000004c2d0d80064a010000000000002200200f4391a26bcc5afde68e6465183d9b38c91f7b096fb0c3afd8f0561c3a406ea54a01000000000000220020a4bfb70b647bd0ad161401e362f88c00d41151a2b3d4d058a7c23dda3fc755aef82401000000000022002084f49a8cfdec3da99b5faa1459f97c3838223cd8a0ed102ce9365ae154c6d9b5803801000000000022002099f285919d127466e41c9cf5044afbbe12ecb2f6408be9d7cbab3625d6e85d415ed701000000000022002064a48a683d489888a75d7cf57d5427e6bc332b20acf6abca8085d52da28da7e480fc0a00000000002200200ef18b6f3cdd44b9028e77fd9041c643ba5c5e27de4571405d169fb34ddd7b4dc886ad207b5bf5eb953734dcc0d6a94161f2bf5cdc6fac417dd69b52220c6308768af21906ca7423d76ca3542a62a581e7ea505e5606583a48cd1f29c4f5462c6a38b1f3000212241dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd9020000002bf82401000000000022002084f49a8cfdec3da99b5faa1459f97c3838223cd8a0ed102ce9365ae154c6d9b58876a9146e2711de9703f69190ce9c77675c385fa44368838763ac6721027fafdc0dc58427092109a17a7a16a600730db3482352a04edafc99eccf1f802f7c820120876475527c2102d6a274c66362e55b5b2ac0a8f63cf7fa61b4374843e8cbbc28017dd7cf3e789c52ae67a914a397093635d220f06843b4dca27686b9591dc62a88ac6851b275685e02000000011dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd902000000000100000001771e01000000000022002064a48a683d489888a75d7cf57d5427e6bc332b20acf6abca8085d52da28da7e4101b0600000000000000000300061b10067a0d3a45dfb874cc69c374aa4379885f02326e13e2eb65bf694a3ebcd902a869ef62d89258677fced5f4371a9dc2242d31ce84e2f1d65af2ad4ae3a3a1f12111241dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd9030000002b803801000000000022002099f285919d127466e41c9cf5044afbbe12ecb2f6408be9d7cbab3625d6e85d418e76a9146e2711de9703f69190ce9c77675c385fa44368838763ac6721027fafdc0dc58427092109a17a7a16a600730db3482352a04edafc99eccf1f802f7c8201208763a914ff7c99067754ff00b64b0c55f7bf392afda34a5788527c2102d6a274c66362e55b5b2ac0a8f63cf7fa61b4374843e8cbbc28017dd7cf3e789c52ae677503101b06b175ac6851b275685e02000000011dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd9030000000001000000019b3101000000000022002064a48a683d489888a75d7cf57d5427e6bc332b20acf6abca8085d52da28da7e40000000095cc3acd7792306bc5792d504325926816fe42457c6ad29b00741901fbe46d6c000000000000000000061b10f4a0c14c421977d0fbfb4da089096f1b41559df84365b7270ad67e8b92fb28d34c514614affa5ea47eb2b2647457f9d2ea23fb47d449d8ccb9c4b202bac08a2100000000000000080002ff0000000000000003000000000000000000000009c4000000002aea54000000000007735940441c3be8f90738b9d65acd2896726cc8b9796b4c5aca307720997082f876d2ee0327e1109720bd3fc2432f24ca56709ce978688310e88141288440730a357f4d9b000000ff02d87e2e97bfb17b65793e748712a44445bfcc14db5337bcbf323e9faaa44e06da0001003d0000fffffffffff80100349e5c6e9d10f61e0ab643d0d82cee98332c306cbb2d825b6d9c4bcb92c1861c0003ffffffffffe000010000000000000003000369b82e7d4d2f49598062820ba3cad8970000061a80160014761879f7b274ce995f87150a02e75cc0c037e8e300000000fffd0212020000000001015847757d05a5e2639d5777a9f46a8dff49fcd5dfba142d93c4de6af54e4620e700000000004c2d0d80064a010000000000002200200f4391a26bcc5afde68e6465183d9b38c91f7b096fb0c3afd8f0561c3a406ea54a01000000000000220020a4bfb70b647bd0ad161401e362f88c00d41151a2b3d4d058a7c23dda3fc755aef82401000000000022002084f49a8cfdec3da99b5faa1459f97c3838223cd8a0ed102ce9365ae154c6d9b5803801000000000022002099f285919d127466e41c9cf5044afbbe12ecb2f6408be9d7cbab3625d6e85d415ed701000000000022002064a48a683d489888a75d7cf57d5427e6bc332b20acf6abca8085d52da28da7e480fc0a00000000002200200ef18b6f3cdd44b9028e77fd9041c643ba5c5e27de4571405d169fb34ddd7b4d0400483045022100881537b143aff4129e0a1918d91ea02c0318d6aa2ee47e9265b6403f5cbe61bc022006f4a18a4bcd58160444daf5bc29f2ae1d460d18bb23076adfa92df656e6e62b0147304402207b5bf5eb953734dcc0d6a94161f2bf5cdc6fac417dd69b52220c6308768af219022006ca7423d76ca3542a62a581e7ea505e5606583a48cd1f29c4f5462c6a38b1f301475221032991639ef70a233d4d0df569b4d065ed8df5b68ccb507208b8f10984c5a297b6210392de23ee259d18e48e80b3dc7dad83f8cd87a64b4ac0da7dd94871e5ea876ec652aec886ad20ff241dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd9040000002b5ed701000000000022002064a48a683d489888a75d7cf57d5427e6bc332b20acf6abca8085d52da28da7e44d63210321fe12ac0817cd8b7b32c43e1c37d341edc6b6c0ef2e7a4a193247569ad8ae4467029000b275210262ad7b97170d7443c86d24fc3f4e3fd0710f4eda2d0726f1d953fd49db573e2068acec020000000001011dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd90400000000900000000180c4010000000000160014761879f7b274ce995f87150a02e75cc0c037e8e3034730440220710e9165f60bd915b94714b59c8c2062b02adad671d1464385d03204aa885d3402203fab7f3a8987bf55c6ce6cbb95740e7f5c78c818eb1fc05b25ce36c5b2e2136601004d63210321fe12ac0817cd8b7b32c43e1c37d341edc6b6c0ef2e7a4a193247569ad8ae4467029000b275210262ad7b97170d7443c86d24fc3f4e3fd0710f4eda2d0726f1d953fd49db573e2068ac000000000002241dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd902000000ff12241dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd9020000002bf82401000000000022002084f49a8cfdec3da99b5faa1459f97c3838223cd8a0ed102ce9365ae154c6d9b58876a9146e2711de9703f69190ce9c77675c385fa44368838763ac6721027fafdc0dc58427092109a17a7a16a600730db3482352a04edafc99eccf1f802f7c820120876475527c2102d6a274c66362e55b5b2ac0a8f63cf7fa61b4374843e8cbbc28017dd7cf3e789c52ae67a914a397093635d220f06843b4dca27686b9591dc62a88ac6851b27568fd017d020000000001011dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd902000000000100000001771e01000000000022002064a48a683d489888a75d7cf57d5427e6bc332b20acf6abca8085d52da28da7e405004730440220067a0d3a45dfb874cc69c374aa4379885f02326e13e2eb65bf694a3ebcd902a8022069ef62d89258677fced5f4371a9dc2242d31ce84e2f1d65af2ad4ae3a3a1f12183483045022100b9ea29e68afbc2ddc5e3cc2e15bb104e8914daa47154a55fa1540925ac45cea1022021e3440f5fbc93b11b78805d30bb02125b57e45f0acf2fe925ea42e3644d44eb01008876a9146e2711de9703f69190ce9c77675c385fa44368838763ac6721027fafdc0dc58427092109a17a7a16a600730db3482352a04edafc99eccf1f802f7c820120876475527c2102d6a274c66362e55b5b2ac0a8f63cf7fa61b4374843e8cbbc28017dd7cf3e789c52ae67a914a397093635d220f06843b4dca27686b9591dc62a88ac6851b27568101b0600000000000000000300061b10241dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd903000000ff11241dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd9030000002b803801000000000022002099f285919d127466e41c9cf5044afbbe12ecb2f6408be9d7cbab3625d6e85d418e76a9146e2711de9703f69190ce9c77675c385fa44368838763ac6721027fafdc0dc58427092109a17a7a16a600730db3482352a04edafc99eccf1f802f7c8201208763a914ff7c99067754ff00b64b0c55f7bf392afda34a5788527c2102d6a274c66362e55b5b2ac0a8f63cf7fa61b4374843e8cbbc28017dd7cf3e789c52ae677503101b06b175ac6851b27568fd01a4020000000001011dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd9030000000001000000019b3101000000000022002064a48a683d489888a75d7cf57d5427e6bc332b20acf6abca8085d52da28da7e40500483045022100f4a0c14c421977d0fbfb4da089096f1b41559df84365b7270ad67e8b92fb28d302204c514614affa5ea47eb2b2647457f9d2ea23fb47d449d8ccb9c4b202bac08a21834830450221009f00e2782a17f91a5a7bbda12e8fd24aec9d967341d83c95f5f1f5cd7a39e97a02206591dbc6815ef01826186348db314e51d4a8c2247dc7b514d8147a02502cb2fd01204422f65fd6bb15259a70d2ec79aaa6953d2a58e7d3a4eae779276eab45fe40f38e76a9146e2711de9703f69190ce9c77675c385fa44368838763ac6721027fafdc0dc58427092109a17a7a16a600730db3482352a04edafc99eccf1f802f7c8201208763a914ff7c99067754ff00b64b0c55f7bf392afda34a5788527c2102d6a274c66362e55b5b2ac0a8f63cf7fa61b4374843e8cbbc28017dd7cf3e789c52ae677503101b06b175ac6851b275680000000095cc3acd7792306bc5792d504325926816fe42457c6ad29b00741901fbe46d6c000000000000000000061b10000224e456c1304d4f950521995e6df1a82ee9bd7b37fb096985ad5c50818180f0d4be000000002b771e01000000000022002064a48a683d489888a75d7cf57d5427e6bc332b20acf6abca8085d52da28da7e44d63210321fe12ac0817cd8b7b32c43e1c37d341edc6b6c0ef2e7a4a193247569ad8ae4467029000b275210262ad7b97170d7443c86d24fc3f4e3fd0710f4eda2d0726f1d953fd49db573e2068aced02000000000101e456c1304d4f950521995e6df1a82ee9bd7b37fb096985ad5c50818180f0d4be00000000009000000001990b010000000000160014761879f7b274ce995f87150a02e75cc0c037e8e303483045022100e5bc8ad2039c412edfc977397feb1d274d3404995db5f1243a664148db316f6c02203a1b80894a4f5b610810ac75dc9e5ccdb31301e5e6dce4966251f6527ecb27a101004d63210321fe12ac0817cd8b7b32c43e1c37d341edc6b6c0ef2e7a4a193247569ad8ae4467029000b275210262ad7b97170d7443c86d24fc3f4e3fd0710f4eda2d0726f1d953fd49db573e2068ac0000000024b2911362612c021a5f9e5f3693483960b105ac335ccd7fa4085d9596cdc623a2000000002b9b3101000000000022002064a48a683d489888a75d7cf57d5427e6bc332b20acf6abca8085d52da28da7e44d63210321fe12ac0817cd8b7b32c43e1c37d341edc6b6c0ef2e7a4a193247569ad8ae4467029000b275210262ad7b97170d7443c86d24fc3f4e3fd0710f4eda2d0726f1d953fd49db573e2068acec02000000000101b2911362612c021a5f9e5f3693483960b105ac335ccd7fa4085d9596cdc623a200000000009000000001bd1e010000000000160014761879f7b274ce995f87150a02e75cc0c037e8e30347304402200b8ae397cf7c0a30f25c5aa96540ce3600b5157dc52c5767a895a3982bcaf28e02206fc02f2481f5fc145ceba7c2c4cdf59753edd98a64c0a369fbfbc60473bcb51b01004d63210321fe12ac0817cd8b7b32c43e1c37d341edc6b6c0ef2e7a4a193247569ad8ae4467029000b275210262ad7b97170d7443c86d24fc3f4e3fd0710f4eda2d0726f1d953fd49db573e2068ac00000000000112241dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd9010000002b4a01000000000000220020a4bfb70b647bd0ad161401e362f88c00d41151a2b3d4d058a7c23dda3fc755ae2821032991639ef70a233d4d0df569b4d065ed8df5b68ccb507208b8f10984c5a297b6ac736460b2683302000000011dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd901000000000000000000000000000000061b100003245847757d05a5e2639d5777a9f46a8dff49fcd5dfba142d93c4de6af54e4620e700000000fd0212020000000001015847757d05a5e2639d5777a9f46a8dff49fcd5dfba142d93c4de6af54e4620e700000000004c2d0d80064a010000000000002200200f4391a26bcc5afde68e6465183d9b38c91f7b096fb0c3afd8f0561c3a406ea54a01000000000000220020a4bfb70b647bd0ad161401e362f88c00d41151a2b3d4d058a7c23dda3fc755aef82401000000000022002084f49a8cfdec3da99b5faa1459f97c3838223cd8a0ed102ce9365ae154c6d9b5803801000000000022002099f285919d127466e41c9cf5044afbbe12ecb2f6408be9d7cbab3625d6e85d415ed701000000000022002064a48a683d489888a75d7cf57d5427e6bc332b20acf6abca8085d52da28da7e480fc0a00000000002200200ef18b6f3cdd44b9028e77fd9041c643ba5c5e27de4571405d169fb34ddd7b4d0400483045022100881537b143aff4129e0a1918d91ea02c0318d6aa2ee47e9265b6403f5cbe61bc022006f4a18a4bcd58160444daf5bc29f2ae1d460d18bb23076adfa92df656e6e62b0147304402207b5bf5eb953734dcc0d6a94161f2bf5cdc6fac417dd69b52220c6308768af219022006ca7423d76ca3542a62a581e7ea505e5606583a48cd1f29c4f5462c6a38b1f301475221032991639ef70a233d4d0df569b4d065ed8df5b68ccb507208b8f10984c5a297b6210392de23ee259d18e48e80b3dc7dad83f8cd87a64b4ac0da7dd94871e5ea876ec652aec886ad20241dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd902000000fd017d020000000001011dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd902000000000100000001771e01000000000022002064a48a683d489888a75d7cf57d5427e6bc332b20acf6abca8085d52da28da7e405004730440220067a0d3a45dfb874cc69c374aa4379885f02326e13e2eb65bf694a3ebcd902a8022069ef62d89258677fced5f4371a9dc2242d31ce84e2f1d65af2ad4ae3a3a1f12183483045022100b9ea29e68afbc2ddc5e3cc2e15bb104e8914daa47154a55fa1540925ac45cea1022021e3440f5fbc93b11b78805d30bb02125b57e45f0acf2fe925ea42e3644d44eb01008876a9146e2711de9703f69190ce9c77675c385fa44368838763ac6721027fafdc0dc58427092109a17a7a16a600730db3482352a04edafc99eccf1f802f7c820120876475527c2102d6a274c66362e55b5b2ac0a8f63cf7fa61b4374843e8cbbc28017dd7cf3e789c52ae67a914a397093635d220f06843b4dca27686b9591dc62a88ac6851b27568101b0600241dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd903000000fd01a4020000000001011dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd9030000000001000000019b3101000000000022002064a48a683d489888a75d7cf57d5427e6bc332b20acf6abca8085d52da28da7e40500483045022100f4a0c14c421977d0fbfb4da089096f1b41559df84365b7270ad67e8b92fb28d302204c514614affa5ea47eb2b2647457f9d2ea23fb47d449d8ccb9c4b202bac08a21834830450221009f00e2782a17f91a5a7bbda12e8fd24aec9d967341d83c95f5f1f5cd7a39e97a02206591dbc6815ef01826186348db314e51d4a8c2247dc7b514d8147a02502cb2fd01204422f65fd6bb15259a70d2ec79aaa6953d2a58e7d3a4eae779276eab45fe40f38e76a9146e2711de9703f69190ce9c77675c385fa44368838763ac6721027fafdc0dc58427092109a17a7a16a600730db3482352a04edafc99eccf1f802f7c8201208763a914ff7c99067754ff00b64b0c55f7bf392afda34a5788527c2102d6a274c66362e55b5b2ac0a8f63cf7fa61b4374843e8cbbc28017dd7cf3e789c52ae677503101b06b175ac6851b27568000000000000000000".bits).require.value.asInstanceOf[DATA_CLOSING] assert(closing1.localCommitPublished.nonEmpty) val lcp = closing1.localCommitPublished.get From e950165df1009014a7b16d5c8f9b576bcc025b2f Mon Sep 17 00:00:00 2001 From: t-bast Date: Mon, 23 Jun 2025 16:35:35 +0200 Subject: [PATCH 2/6] Extract `CommitParams` to individual commitments We introduce a new channel codec version where: - we extract per-commitment parameters (commitment format and params such as `dust_limit` which we may want to dynamically update in the future) - we clean-up the splice iterations on the `Commitments` structure - we remove the funding transaction from the confirmed local funding status (once confirmed, we don't need it anymore: it is wasting DB space for no good reason, we can get it from the blockchain) - we add a `maxClosingFeerate_opt` to the closing state, to allow limiting the feerate used in RBF attempts for safe transactions --- .../eclair/SpendFromChannelAddress.scala | 2 +- .../acinq/eclair/balance/CheckBalance.scala | 4 +- .../fr/acinq/eclair/channel/ChannelData.scala | 47 +- .../fr/acinq/eclair/channel/Commitments.scala | 263 ++++----- .../fr/acinq/eclair/channel/Helpers.scala | 115 ++-- .../fr/acinq/eclair/channel/fsm/Channel.scala | 117 ++-- .../channel/fsm/ChannelOpenDualFunded.scala | 52 +- .../channel/fsm/ChannelOpenSingleFunded.scala | 71 +-- .../channel/fsm/CommonFundingHandlers.scala | 8 +- .../eclair/channel/fsm/ErrorHandlers.scala | 18 +- .../channel/fund/InteractiveTxBuilder.scala | 59 +- .../crypto/keymanager/CommitmentKeys.scala | 12 +- .../eclair/io/OpenChannelInterceptor.scala | 24 +- .../acinq/eclair/json/JsonSerializers.scala | 4 +- .../payment/relay/OnTheFlyFunding.scala | 2 +- .../acinq/eclair/reputation/Reputation.scala | 26 +- .../eclair/transactions/Transactions.scala | 61 ++- .../wire/internal/channel/ChannelCodecs.scala | 4 +- .../channel/version0/ChannelCodecs0.scala | 10 +- .../channel/version0/ChannelTypes0.scala | 57 +- .../channel/version1/ChannelCodecs1.scala | 8 +- .../channel/version2/ChannelCodecs2.scala | 8 +- .../channel/version3/ChannelCodecs3.scala | 12 +- .../channel/version3/ChannelTypes3.scala | 44 +- .../channel/version4/ChannelCodecs4.scala | 298 +++++----- .../channel/version4/ChannelTypes4.scala | 217 +++++++- .../channel/version5/ChannelCodecs5.scala | 510 ++++++++++++++++++ .../channel/version5/ChannelTypes5.scala | 84 +++ .../000003-DATA_NORMAL/fundee/data.json | 35 +- .../000003-DATA_NORMAL/funder/data.json | 35 +- .../020002-DATA_NORMAL/funder/data.json | 35 +- .../funder/data.json | 35 +- .../funder/data.json | 35 +- .../funder/data.json | 35 +- .../fundee/data.json | 36 +- .../funder/data.json | 36 +- .../fundee/data.json | 36 +- .../funder/data.json | 36 +- .../04000e-DATA_NORMAL/announced/data.json | 36 +- .../splicing-private/data.json | 87 +-- .../scala/fr/acinq/eclair/TestConstants.scala | 6 +- .../eclair/balance/CheckBalanceSpec.scala | 2 +- .../eclair/channel/CommitmentsSpec.scala | 32 +- .../fr/acinq/eclair/channel/FuzzySpec.scala | 9 +- .../fr/acinq/eclair/channel/HelpersSpec.scala | 4 +- .../channel/InteractiveTxBuilderSpec.scala | 82 +-- .../publish/ReplaceableTxPublisherSpec.scala | 8 +- .../channel/publish/TxPublisherSpec.scala | 12 +- .../ChannelStateTestsHelperMethods.scala | 20 +- .../a/WaitForAcceptChannelStateSpec.scala | 8 +- .../a/WaitForOpenChannelStateSpec.scala | 10 +- .../c/WaitForDualFundingReadyStateSpec.scala | 4 +- .../states/e/NormalSplicesStateSpec.scala | 20 +- .../channel/states/e/NormalStateSpec.scala | 8 +- .../io/OpenChannelInterceptorSpec.scala | 4 +- .../io/PendingChannelsRateLimiterSpec.scala | 8 +- .../fr/acinq/eclair/io/SwitchboardSpec.scala | 2 +- .../eclair/json/JsonSerializersSpec.scala | 52 +- .../eclair/payment/PaymentPacketSpec.scala | 13 +- .../payment/PostRestartHtlcCleanerSpec.scala | 2 +- .../payment/relay/OnTheFlyFundingSpec.scala | 4 +- .../fr/acinq/eclair/router/RouterSpec.scala | 2 +- .../eclair/transactions/TestVectorsSpec.scala | 5 +- .../transactions/TransactionsSpec.scala | 9 +- .../internal/channel/ChannelCodecsSpec.scala | 30 +- .../channel/version4/ChannelCodecs4Spec.scala | 206 +++---- .../channel/version5/ChannelCodecs5Spec.scala | 71 +++ 67 files changed, 2054 insertions(+), 1193 deletions(-) create mode 100644 eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5.scala create mode 100644 eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelTypes5.scala create mode 100644 eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5Spec.scala diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/SpendFromChannelAddress.scala b/eclair-core/src/main/scala/fr/acinq/eclair/SpendFromChannelAddress.scala index dbd6120d10..5196498093 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/SpendFromChannelAddress.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/SpendFromChannelAddress.scala @@ -41,7 +41,7 @@ trait SpendFromChannelAddress { inputTx <- appKit.wallet.getTransaction(outPoint.txid) channelKeys = appKit.nodeParams.channelKeyManager.channelKeys(ChannelConfig.standard, fundingKeyPath) localFundingKey = channelKeys.fundingKey(fundingTxIndex) - inputInfo = InputInfo(outPoint, inputTx.txOut(outPoint.index.toInt), ByteVector.empty) + inputInfo = InputInfo(outPoint, inputTx.txOut(outPoint.index.toInt)) // classify as splice, doesn't really matter tx = Transactions.SpliceTx(inputInfo, unsignedTx) localSig = tx.sign(localFundingKey, remoteFundingPubkey, extraUtxos = Map.empty) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/balance/CheckBalance.scala b/eclair-core/src/main/scala/fr/acinq/eclair/balance/CheckBalance.scala index dc62645202..24ea89d04f 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/balance/CheckBalance.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/balance/CheckBalance.scala @@ -176,8 +176,8 @@ object CheckBalance { case d: DATA_SHUTDOWN => this.copy(shutdown = this.shutdown.addChannelBalance(d.commitments)) // If one of our closing transactions is in the mempool or recently confirmed, and thus included in our on-chain // balance, we ignore this channel in our off-chain balance to avoid counting it twice. - case d: DATA_NEGOTIATING if recentlySpentInputs.contains(d.commitments.latest.commitInput.outPoint) => this - case d: DATA_NEGOTIATING_SIMPLE if recentlySpentInputs.contains(d.commitments.latest.commitInput.outPoint) => this + case d: DATA_NEGOTIATING if recentlySpentInputs.contains(d.commitments.latest.fundingInput) => this + case d: DATA_NEGOTIATING_SIMPLE if recentlySpentInputs.contains(d.commitments.latest.fundingInput) => this // Otherwise, that means the closing transactions aren't in the mempool yet, so we include our off-chain balance. case d: DATA_NEGOTIATING => this.copy(negotiating = this.negotiating.addChannelBalance(d.commitments)) case d: DATA_NEGOTIATING_SIMPLE => this.copy(negotiating = this.negotiating.addChannelBalance(d.commitments)) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala index 93c9c7647e..6d2a4a3f78 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala @@ -20,7 +20,6 @@ import akka.actor.{ActorRef, PossiblyHarmful, typed} import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.{ByteVector32, DeterministicWallet, OutPoint, Satoshi, SatoshiLong, Transaction, TxId, TxOut} import fr.acinq.eclair.blockchain.fee.{ConfirmationTarget, FeeratePerKw} -import fr.acinq.eclair.channel.LocalFundingStatus.DualFundedUnconfirmedFundingTx import fr.acinq.eclair.channel.fund.InteractiveTxBuilder._ import fr.acinq.eclair.channel.fund.{InteractiveTxBuilder, InteractiveTxSigningSession} import fr.acinq.eclair.io.Peer @@ -430,6 +429,7 @@ case class RevokedCommitPublished(commitTx: Transaction, localOutput_opt: Option case class ShortIdAliases(localAlias: Alias, remoteAlias_opt: Option[Alias]) sealed trait LocalFundingStatus { + /** While the transaction is unconfirmed, we keep the funding transaction (if available) to allow rebroadcasting. */ def signedTx_opt: Option[Transaction] /** We store local signatures for the purpose of retransmitting if the funding/splicing flow is interrupted. */ def localSigs_opt: Option[TxSignatures] @@ -458,8 +458,8 @@ object LocalFundingStatus { case class ZeroconfPublishedFundingTx(tx: Transaction, localSigs_opt: Option[TxSignatures], liquidityPurchase_opt: Option[LiquidityAds.PurchaseBasicInfo]) extends UnconfirmedFundingTx with Locked { override val signedTx_opt: Option[Transaction] = Some(tx) } - case class ConfirmedFundingTx(tx: Transaction, shortChannelId: RealShortChannelId, localSigs_opt: Option[TxSignatures], liquidityPurchase_opt: Option[LiquidityAds.PurchaseBasicInfo]) extends LocalFundingStatus with Locked { - override val signedTx_opt: Option[Transaction] = Some(tx) + case class ConfirmedFundingTx(txOut: TxOut, shortChannelId: RealShortChannelId, localSigs_opt: Option[TxSignatures], liquidityPurchase_opt: Option[LiquidityAds.PurchaseBasicInfo]) extends LocalFundingStatus with Locked { + override val signedTx_opt: Option[Transaction] = None } } @@ -567,6 +567,8 @@ final case class DATA_WAIT_FOR_ACCEPT_CHANNEL(initFunder: INPUT_INIT_CHANNEL_INI val channelId: ByteVector32 = initFunder.temporaryChannelId } final case class DATA_WAIT_FOR_FUNDING_INTERNAL(channelParams: ChannelParams, + localCommitParams: CommitParams, + remoteCommitParams: CommitParams, fundingAmount: Satoshi, pushAmount: MilliSatoshi, commitTxFeerate: FeeratePerKw, @@ -574,16 +576,22 @@ final case class DATA_WAIT_FOR_FUNDING_INTERNAL(channelParams: ChannelParams, remoteFirstPerCommitmentPoint: PublicKey, replyTo: akka.actor.typed.ActorRef[Peer.OpenChannelResponse]) extends TransientChannelData { val channelId: ByteVector32 = channelParams.channelId + val commitmentFormat: CommitmentFormat = channelParams.channelFeatures.commitmentFormat } final case class DATA_WAIT_FOR_FUNDING_CREATED(channelParams: ChannelParams, + localCommitParams: CommitParams, + remoteCommitParams: CommitParams, fundingAmount: Satoshi, pushAmount: MilliSatoshi, commitTxFeerate: FeeratePerKw, remoteFundingPubKey: PublicKey, remoteFirstPerCommitmentPoint: PublicKey) extends TransientChannelData { val channelId: ByteVector32 = channelParams.channelId + val commitmentFormat: CommitmentFormat = channelParams.channelFeatures.commitmentFormat } final case class DATA_WAIT_FOR_FUNDING_SIGNED(channelParams: ChannelParams, + localCommitParams: CommitParams, + remoteCommitParams: CommitParams, remoteFundingPubKey: PublicKey, fundingTx: Transaction, fundingTxFee: Satoshi, @@ -593,6 +601,7 @@ final case class DATA_WAIT_FOR_FUNDING_SIGNED(channelParams: ChannelParams, lastSent: FundingCreated, replyTo: akka.actor.typed.ActorRef[Peer.OpenChannelResponse]) extends TransientChannelData { val channelId: ByteVector32 = channelParams.channelId + val commitmentFormat: CommitmentFormat = channelParams.channelFeatures.commitmentFormat } final case class DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments: Commitments, waitingSince: BlockHeight, // how long have we been waiting for the funding tx to confirm @@ -610,6 +619,8 @@ final case class DATA_WAIT_FOR_ACCEPT_DUAL_FUNDED_CHANNEL(init: INPUT_INIT_CHANN } final case class DATA_WAIT_FOR_DUAL_FUNDING_CREATED(channelId: ByteVector32, channelParams: ChannelParams, + localCommitParams: CommitParams, + remoteCommitParams: CommitParams, secondRemotePerCommitmentPoint: PublicKey, localPushAmount: MilliSatoshi, remotePushAmount: MilliSatoshi, @@ -620,8 +631,7 @@ final case class DATA_WAIT_FOR_DUAL_FUNDING_SIGNED(channelParams: ChannelParams, secondRemotePerCommitmentPoint: PublicKey, localPushAmount: MilliSatoshi, remotePushAmount: MilliSatoshi, - signingSession: InteractiveTxSigningSession.WaitingForSigs, - remoteChannelData_opt: Option[ByteVector]) extends ChannelDataWithoutCommitments + signingSession: InteractiveTxSigningSession.WaitingForSigs) extends ChannelDataWithoutCommitments final case class DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED(commitments: Commitments, localPushAmount: MilliSatoshi, remotePushAmount: MilliSatoshi, @@ -629,9 +639,9 @@ final case class DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED(commitments: Commitments, lastChecked: BlockHeight, // last time we checked if the channel was double-spent status: DualFundingStatus, deferred: Option[ChannelReady]) extends ChannelDataWithCommitments { - def allFundingTxs: Seq[DualFundedUnconfirmedFundingTx] = commitments.active.map(_.localFundingStatus).collect { case fundingTx: DualFundedUnconfirmedFundingTx => fundingTx } - def latestFundingTx: DualFundedUnconfirmedFundingTx = commitments.latest.localFundingStatus.asInstanceOf[DualFundedUnconfirmedFundingTx] - def previousFundingTxs: Seq[DualFundedUnconfirmedFundingTx] = allFundingTxs diff Seq(latestFundingTx) + def allFundingTxs: Seq[LocalFundingStatus.DualFundedUnconfirmedFundingTx] = commitments.active.map(_.localFundingStatus).collect { case fundingTx: LocalFundingStatus.DualFundedUnconfirmedFundingTx => fundingTx } + def latestFundingTx: LocalFundingStatus.DualFundedUnconfirmedFundingTx = commitments.latest.localFundingStatus.asInstanceOf[LocalFundingStatus.DualFundedUnconfirmedFundingTx] + def previousFundingTxs: Seq[LocalFundingStatus.DualFundedUnconfirmedFundingTx] = allFundingTxs diff Seq(latestFundingTx) } final case class DATA_WAIT_FOR_DUAL_FUNDING_READY(commitments: Commitments, aliases: ShortIdAliases) extends ChannelDataWithCommitments @@ -639,10 +649,10 @@ final case class DATA_NORMAL(commitments: Commitments, aliases: ShortIdAliases, lastAnnouncement_opt: Option[ChannelAnnouncement], channelUpdate: ChannelUpdate, + spliceStatus: SpliceStatus, localShutdown: Option[Shutdown], remoteShutdown: Option[Shutdown], - closeStatus_opt: Option[CloseStatus], - spliceStatus: SpliceStatus) extends ChannelDataWithCommitments { + closeStatus_opt: Option[CloseStatus]) extends ChannelDataWithCommitments { val lastAnnouncedCommitment_opt: Option[AnnouncedCommitment] = lastAnnouncement_opt.flatMap(ann => commitments.resolveCommitment(ann.shortChannelId).map(c => AnnouncedCommitment(c, ann))) val lastAnnouncedFundingTxId_opt: Option[TxId] = lastAnnouncedCommitment_opt.map(_.fundingTxId) val isNegotiatingQuiescence: Boolean = spliceStatus.isNegotiatingQuiescence @@ -675,24 +685,21 @@ final case class DATA_CLOSING(commitments: Commitments, remoteCommitPublished: Option[RemoteCommitPublished] = None, nextRemoteCommitPublished: Option[RemoteCommitPublished] = None, futureRemoteCommitPublished: Option[RemoteCommitPublished] = None, - revokedCommitPublished: List[RevokedCommitPublished] = Nil) extends ChannelDataWithCommitments { + revokedCommitPublished: List[RevokedCommitPublished] = Nil, + maxClosingFeerate_opt: Option[FeeratePerKw] = None) extends ChannelDataWithCommitments { val spendingTxs: List[Transaction] = mutualClosePublished.map(_.tx) ::: localCommitPublished.map(_.commitTx).toList ::: remoteCommitPublished.map(_.commitTx).toList ::: nextRemoteCommitPublished.map(_.commitTx).toList ::: futureRemoteCommitPublished.map(_.commitTx).toList ::: revokedCommitPublished.map(_.commitTx) require(spendingTxs.nonEmpty, "there must be at least one tx published in this state") } final case class DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT(commitments: Commitments, remoteChannelReestablish: ChannelReestablish) extends ChannelDataWithCommitments +/** Local params that apply for the channel's lifetime. */ case class LocalChannelParams(nodeId: PublicKey, fundingKeyPath: DeterministicWallet.KeyPath, - dustLimit: Satoshi, - maxHtlcValueInFlightMsat: UInt64, // Channel reserve applied to the remote peer, if we're not using [[Features.DualFunding]] (in // which case the reserve is set to 1%). If the channel is spliced, this initial value will be // ignored in favor of a 1% reserve of the resulting capacity. initialRequestedChannelReserve_opt: Option[Satoshi], - htlcMinimum: MilliSatoshi, - toRemoteDelay: CltvExpiryDelta, - maxAcceptedHtlcs: Int, isChannelOpener: Boolean, paysCommitTxFees: Boolean, upfrontShutdownScript_opt: Option[ByteVector], @@ -704,18 +711,12 @@ case class LocalChannelParams(nodeId: PublicKey, // The node responsible for the commit tx fees is also the node paying the mutual close fees. // The other node's balance may be empty, which wouldn't allow them to pay the closing fees. val paysClosingFees: Boolean = paysCommitTxFees - - val proposedCommitParams: ProposedCommitParams = ProposedCommitParams(dustLimit, htlcMinimum, maxHtlcValueInFlightMsat, maxAcceptedHtlcs, toRemoteDelay) } +/** Remote params that apply for the channel's lifetime. */ case class RemoteChannelParams(nodeId: PublicKey, - dustLimit: Satoshi, - maxHtlcValueInFlightMsat: UInt64, // See comment in LocalChannelParams for details. initialRequestedChannelReserve_opt: Option[Satoshi], - htlcMinimum: MilliSatoshi, - toRemoteDelay: CltvExpiryDelta, - maxAcceptedHtlcs: Int, revocationBasepoint: PublicKey, paymentBasepoint: PublicKey, delayedPaymentBasepoint: PublicKey, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index 4d1a2e5839..b8fb7e8cf8 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -3,7 +3,7 @@ package fr.acinq.eclair.channel import akka.event.LoggingAdapter import fr.acinq.bitcoin.crypto.musig2.IndividualNonce import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} -import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Crypto, Satoshi, SatoshiLong, Transaction, TxId} +import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Crypto, OutPoint, Satoshi, SatoshiLong, Transaction, TxId} import fr.acinq.eclair.blockchain.fee.{FeeratePerByte, FeeratePerKw, FeeratesPerKw, OnChainFeeConf} import fr.acinq.eclair.channel.Helpers.Closing import fr.acinq.eclair.channel.Monitoring.{Metrics, Tags} @@ -29,24 +29,13 @@ case class ChannelParams(channelId: ByteVector32, require(channelFeatures.hasFeature(Features.DualFunding) == localParams.initialRequestedChannelReserve_opt.isEmpty, "custom local channel reserve is incompatible with dual-funded channels") require(channelFeatures.hasFeature(Features.DualFunding) == remoteParams.initialRequestedChannelReserve_opt.isEmpty, "custom remote channel reserve is incompatible with dual-funded channels") - val commitmentFormat: CommitmentFormat = channelFeatures.commitmentFormat val announceChannel: Boolean = channelFlags.announceChannel - val localNodeId: PublicKey = localParams.nodeId val remoteNodeId: PublicKey = remoteParams.nodeId - - val localCommitParams: CommitParams = CommitParams(localParams.dustLimit, localParams.htlcMinimum, localParams.maxHtlcValueInFlightMsat, localParams.maxAcceptedHtlcs, remoteParams.toRemoteDelay) - val remoteCommitParams: CommitParams = CommitParams(remoteParams.dustLimit, remoteParams.htlcMinimum, remoteParams.maxHtlcValueInFlightMsat, remoteParams.maxAcceptedHtlcs, localParams.toRemoteDelay) - - // We can safely cast to millisatoshis since we verify that it's less than a valid millisatoshi amount. - val maxHtlcValueInFlight: MilliSatoshi = Seq(localParams.maxHtlcValueInFlightMsat, remoteParams.maxHtlcValueInFlightMsat, UInt64(MilliSatoshi.MaxMoney.toLong)).min.toBigInt.toLong.msat - // If we've set the 0-conf feature bit for this peer, we will always use 0-conf with them. val zeroConf: Boolean = localParams.initFeatures.hasFeature(Features.ZeroConf) - /** - * We update local/global features at reconnection - */ + /** We update local/global features at reconnection. */ def updateFeatures(localInit: Init, remoteInit: Init): ChannelParams = copy( localParams = localParams.copy(initFeatures = localInit.features), remoteParams = remoteParams.copy(initFeatures = remoteInit.features), @@ -58,20 +47,6 @@ case class ChannelParams(channelId: ByteVector32, */ def minDepth(defaultMinDepth: Int): Option[Int] = if (zeroConf) None else Some(defaultMinDepth) - /** Channel reserve that applies to our funds. */ - def localChannelReserveForCapacity(capacity: Satoshi, isSplice: Boolean): Satoshi = if (channelFeatures.hasFeature(Features.DualFunding) || isSplice) { - (capacity / 100).max(remoteCommitParams.dustLimit) - } else { - remoteParams.initialRequestedChannelReserve_opt.get // this is guarded by a require() in ChannelParams - } - - /** Channel reserve that applies to our peer's funds. */ - def remoteChannelReserveForCapacity(capacity: Satoshi, isSplice: Boolean): Satoshi = if (channelFeatures.hasFeature(Features.DualFunding) || isSplice) { - (capacity / 100).max(localCommitParams.dustLimit) - } else { - localParams.initialRequestedChannelReserve_opt.get // this is guarded by a require() in ChannelParams - } - /** * @param localScriptPubKey local script pubkey (provided in CMD_CLOSE, as an upfront shutdown script, or set to the current final onchain script) * @return an exception if the provided script is not valid @@ -189,51 +164,51 @@ object ChannelSpendSignature { * The local commitment maps to a commitment transaction that we can sign and broadcast if necessary. * The [[htlcRemoteSigs]] are stored in the order in which HTLC outputs appear in the commitment transaction. */ -case class LocalCommit(index: Long, spec: CommitmentSpec, txId: TxId, input: InputInfo, remoteSig: ChannelSpendSignature, htlcRemoteSigs: List[ByteVector64]) +case class LocalCommit(index: Long, spec: CommitmentSpec, txId: TxId, remoteSig: ChannelSpendSignature, htlcRemoteSigs: List[ByteVector64]) object LocalCommit { - def fromCommitSig(params: ChannelParams, commitKeys: LocalCommitmentKeys, fundingTxId: TxId, + def fromCommitSig(channelParams: ChannelParams, commitParams: CommitParams, commitKeys: LocalCommitmentKeys, fundingTxId: TxId, fundingKey: PrivateKey, remoteFundingPubKey: PublicKey, commitInput: InputInfo, - commit: CommitSig, localCommitIndex: Long, spec: CommitmentSpec): Either[ChannelException, LocalCommit] = { - val (localCommitTx, htlcTxs) = Commitment.makeLocalTxs(params, params.localCommitParams, commitKeys, localCommitIndex, fundingKey, remoteFundingPubKey, commitInput, spec) - val remoteCommitSigOk = params.commitmentFormat match { + commit: CommitSig, localCommitIndex: Long, spec: CommitmentSpec, commitmentFormat: CommitmentFormat): Either[ChannelException, LocalCommit] = { + val (localCommitTx, htlcTxs) = Commitment.makeLocalTxs(channelParams, commitParams, commitKeys, localCommitIndex, fundingKey, remoteFundingPubKey, commitInput, commitmentFormat, spec) + val remoteCommitSigOk = commitmentFormat match { case _: SegwitV0CommitmentFormat => localCommitTx.checkRemoteSig(fundingKey.publicKey, remoteFundingPubKey, ChannelSpendSignature.IndividualSignature(commit.signature)) case _: SimpleTaprootChannelCommitmentFormat => ??? } if (!remoteCommitSigOk) { - return Left(InvalidCommitmentSignature(params.channelId, fundingTxId, localCommitIndex, localCommitTx.tx)) + return Left(InvalidCommitmentSignature(channelParams.channelId, fundingTxId, localCommitIndex, localCommitTx.tx)) } - val commitTxRemoteSig = params.commitmentFormat match { + val commitTxRemoteSig = commitmentFormat match { case _: SegwitV0CommitmentFormat => ChannelSpendSignature.IndividualSignature(commit.signature) case _: SimpleTaprootChannelCommitmentFormat => ??? } val sortedHtlcTxs = htlcTxs.sortBy(_.input.outPoint.index) if (commit.htlcSignatures.size != sortedHtlcTxs.size) { - return Left(HtlcSigCountMismatch(params.channelId, sortedHtlcTxs.size, commit.htlcSignatures.size)) + return Left(HtlcSigCountMismatch(channelParams.channelId, sortedHtlcTxs.size, commit.htlcSignatures.size)) } val htlcRemoteSigs = sortedHtlcTxs.zip(commit.htlcSignatures).toList.map { case (htlcTx: HtlcTx, remoteSig) => if (!htlcTx.checkRemoteSig(commitKeys, remoteSig)) { - return Left(InvalidHtlcSignature(params.channelId, htlcTx.tx.txid)) + return Left(InvalidHtlcSignature(channelParams.channelId, htlcTx.tx.txid)) } remoteSig } - Right(LocalCommit(localCommitIndex, spec, localCommitTx.tx.txid, localCommitTx.input, commitTxRemoteSig, htlcRemoteSigs)) + Right(LocalCommit(localCommitIndex, spec, localCommitTx.tx.txid, commitTxRemoteSig, htlcRemoteSigs)) } } /** The remote commitment maps to a commitment transaction that only our peer can sign and broadcast. */ case class RemoteCommit(index: Long, spec: CommitmentSpec, txId: TxId, remotePerCommitmentPoint: PublicKey) { - def sign(params: ChannelParams, channelKeys: ChannelKeys, fundingTxIndex: Long, remoteFundingPubKey: PublicKey, commitInput: InputInfo): CommitSig = { + def sign(channelParams: ChannelParams, commitParams: CommitParams, channelKeys: ChannelKeys, fundingTxIndex: Long, remoteFundingPubKey: PublicKey, commitInput: InputInfo, commitmentFormat: CommitmentFormat): CommitSig = { val fundingKey = channelKeys.fundingKey(fundingTxIndex) - val commitKeys = RemoteCommitmentKeys(params, channelKeys, remotePerCommitmentPoint) - val (remoteCommitTx, htlcTxs) = Commitment.makeRemoteTxs(params, params.remoteCommitParams, commitKeys, index, fundingKey, remoteFundingPubKey, commitInput, spec) + val commitKeys = RemoteCommitmentKeys(channelParams, channelKeys, remotePerCommitmentPoint, commitmentFormat) + val (remoteCommitTx, htlcTxs) = Commitment.makeRemoteTxs(channelParams, commitParams, commitKeys, index, fundingKey, remoteFundingPubKey, commitInput, commitmentFormat, spec) val sortedHtlcTxs = htlcTxs.sortBy(_.input.outPoint.index) val htlcSigs = sortedHtlcTxs.map(_.localSig(commitKeys)) - params.commitmentFormat match { + commitmentFormat match { case _: SegwitV0CommitmentFormat => val sig = remoteCommitTx.sign(fundingKey, remoteFundingPubKey).sig - CommitSig(params.channelId, sig, htlcSigs.toList) + CommitSig(channelParams.channelId, sig, htlcSigs.toList) case _: SimpleTaprootChannelCommitmentFormat => ??? } } @@ -268,28 +243,51 @@ case class CommitTxIds(localCommitTxId: TxId, remoteCommitTxId: TxId, nextRemote */ case class Commitment(fundingTxIndex: Long, firstRemoteCommitIndex: Long, + fundingInput: OutPoint, + fundingAmount: Satoshi, remoteFundingPubKey: PublicKey, - localFundingStatus: LocalFundingStatus, remoteFundingStatus: RemoteFundingStatus, - localCommit: LocalCommit, remoteCommit: RemoteCommit, nextRemoteCommit_opt: Option[NextRemoteCommit]) { - val commitInput: InputInfo = localCommit.input - val fundingTxId: TxId = commitInput.outPoint.txid + localFundingStatus: LocalFundingStatus, + remoteFundingStatus: RemoteFundingStatus, + commitmentFormat: CommitmentFormat, + localCommitParams: CommitParams, + localCommit: LocalCommit, + remoteCommitParams: CommitParams, + remoteCommit: RemoteCommit, + nextRemoteCommit_opt: Option[NextRemoteCommit]) { + val fundingTxId: TxId = fundingInput.txid val commitTxIds: CommitTxIds = CommitTxIds(localCommit.txId, remoteCommit.txId, nextRemoteCommit_opt.map(_.commit.txId)) - val capacity: Satoshi = commitInput.txOut.amount + val capacity: Satoshi = fundingAmount + // We can safely cast to millisatoshis since we verify that it's less than a valid millisatoshi amount. + val maxHtlcValueInFlight: MilliSatoshi = Seq(localCommitParams.maxHtlcValueInFlight, remoteCommitParams.maxHtlcValueInFlight, UInt64(MilliSatoshi.MaxMoney.toLong)).min.toBigInt.toLong.msat /** Once the funding transaction is confirmed, short_channel_id matching this transaction. */ val shortChannelId_opt: Option[RealShortChannelId] = localFundingStatus match { case f: LocalFundingStatus.ConfirmedFundingTx => Some(f.shortChannelId) case _ => None } - def localKeys(params: ChannelParams, channelKeys: ChannelKeys): LocalCommitmentKeys = LocalCommitmentKeys(params, channelKeys, localCommit.index) + def localFundingKey(channelKeys: ChannelKeys): PrivateKey = channelKeys.fundingKey(fundingTxIndex) + + def commitInput(fundingKey: PrivateKey): InputInfo = Transactions.makeFundingInputInfo(fundingInput.txid, fundingInput.index.toInt, fundingAmount, fundingKey.publicKey, remoteFundingPubKey, commitmentFormat) + + def commitInput(channelKeys: ChannelKeys): InputInfo = commitInput(localFundingKey(channelKeys)) - def remoteKeys(params: ChannelParams, channelKeys: ChannelKeys, remotePerCommitmentPoint: PublicKey): RemoteCommitmentKeys = RemoteCommitmentKeys(params, channelKeys, remotePerCommitmentPoint) + def localKeys(params: ChannelParams, channelKeys: ChannelKeys): LocalCommitmentKeys = LocalCommitmentKeys(params, channelKeys, localCommit.index, commitmentFormat) + + def remoteKeys(params: ChannelParams, channelKeys: ChannelKeys, remotePerCommitmentPoint: PublicKey): RemoteCommitmentKeys = RemoteCommitmentKeys(params, channelKeys, remotePerCommitmentPoint, commitmentFormat) /** Channel reserve that applies to our funds. */ - def localChannelReserve(params: ChannelParams): Satoshi = params.localChannelReserveForCapacity(capacity, fundingTxIndex > 0) + def localChannelReserve(params: ChannelParams): Satoshi = if (params.channelFeatures.hasFeature(Features.DualFunding) || fundingTxIndex > 0) { + (fundingAmount / 100).max(remoteCommitParams.dustLimit) + } else { + params.remoteParams.initialRequestedChannelReserve_opt.get // this is guarded by a require() in ChannelParams + } /** Channel reserve that applies to our peer's funds. */ - def remoteChannelReserve(params: ChannelParams): Satoshi = params.remoteChannelReserveForCapacity(capacity, fundingTxIndex > 0) + def remoteChannelReserve(params: ChannelParams): Satoshi = if (params.channelFeatures.hasFeature(Features.DualFunding) || fundingTxIndex > 0) { + (fundingAmount / 100).max(localCommitParams.dustLimit) + } else { + params.localParams.initialRequestedChannelReserve_opt.get // this is guarded by a require() in ChannelParams + } // NB: when computing availableBalanceForSend and availableBalanceForReceive, the initiator keeps an extra buffer on // top of its usual channel reserve to avoid getting channels stuck in case the on-chain feerate increases (see @@ -457,11 +455,11 @@ case class Commitment(fundingTxIndex: Long, // we allowed mismatches between our feerates and our remote's as long as commitments didn't contain any HTLC at risk // we need to verify that we're not disagreeing on feerates anymore before offering new HTLCs // NB: there may be a pending update_fee that hasn't been applied yet that needs to be taken into account - val localFeerate = feeConf.getCommitmentFeerate(feerates, params.remoteNodeId, params.commitmentFormat, capacity) + val localFeerate = feeConf.getCommitmentFeerate(feerates, params.remoteNodeId, commitmentFormat, capacity) val remoteFeerate = localCommit.spec.commitTxFeerate +: changes.remoteChanges.all.collect { case f: UpdateFee => f.feeratePerKw } // What we want to avoid is having an HTLC in a commitment transaction that has a very low feerate, which we won't // be able to confirm in time to claim the HTLC, so we only need to check that the feerate isn't too low. - remoteFeerate.find(feerate => feeConf.feerateToleranceFor(params.remoteNodeId).isProposedFeerateTooLow(params.commitmentFormat, localFeerate, feerate)) match { + remoteFeerate.find(feerate => feeConf.feerateToleranceFor(params.remoteNodeId).isProposedFeerateTooLow(commitmentFormat, localFeerate, feerate)) match { case Some(feerate) => return Left(FeerateTooDifferent(params.channelId, localFeeratePerKw = localFeerate, remoteFeeratePerKw = feerate)) case None => } @@ -474,10 +472,10 @@ case class Commitment(fundingTxIndex: Long, val outgoingHtlcs = reduced.htlcs.collect(DirectedHtlc.incoming) // note that the initiator pays the fee, so if sender != initiator, both sides will have to afford this payment - val fees = commitTxTotalCost(params.remoteCommitParams.dustLimit, reduced, params.commitmentFormat) + val fees = commitTxTotalCost(remoteCommitParams.dustLimit, reduced, commitmentFormat) // the initiator needs to keep an extra buffer to be able to handle a x2 feerate increase and an additional htlc to avoid // getting the channel stuck (see https://github.com/lightningnetwork/lightning-rfc/issues/728). - val funderFeeBuffer = commitTxTotalCostMsat(params.remoteCommitParams.dustLimit, reduced.copy(commitTxFeerate = reduced.commitTxFeerate * 2), params.commitmentFormat) + htlcOutputFee(reduced.commitTxFeerate * 2, params.commitmentFormat) + val funderFeeBuffer = commitTxTotalCostMsat(remoteCommitParams.dustLimit, reduced.copy(commitTxFeerate = reduced.commitTxFeerate * 2), commitmentFormat) + htlcOutputFee(reduced.commitTxFeerate * 2, commitmentFormat) // NB: increasing the feerate can actually remove htlcs from the commit tx (if they fall below the trim threshold) // which may result in a lower commit tx fee; this is why we take the max of the two. val missingForSender = reduced.toRemote - localChannelReserve(params) - (if (params.localParams.paysCommitTxFees) fees.max(funderFeeBuffer.truncateToSatoshi) else 0.sat) @@ -508,39 +506,38 @@ case class Commitment(fundingTxIndex: Long, // We apply local *and* remote restrictions, to ensure both peers are happy with the resulting number of HTLCs. // NB: we need the `toSeq` because otherwise duplicate amountMsat would be removed (since outgoingHtlcs is a Set). val htlcValueInFlight = outgoingHtlcs.toSeq.map(_.amountMsat).sum - val allowedHtlcValueInFlight = UInt64(params.maxHtlcValueInFlight.toLong) - if (allowedHtlcValueInFlight < htlcValueInFlight) { - return Left(HtlcValueTooHighInFlight(params.channelId, maximum = allowedHtlcValueInFlight, actual = htlcValueInFlight)) + if (maxHtlcValueInFlight < htlcValueInFlight) { + return Left(HtlcValueTooHighInFlight(params.channelId, maximum = UInt64(maxHtlcValueInFlight.toLong), actual = htlcValueInFlight)) } - if (Seq(params.localCommitParams.maxAcceptedHtlcs, params.remoteCommitParams.maxAcceptedHtlcs).min < outgoingHtlcs.size) { - return Left(TooManyAcceptedHtlcs(params.channelId, maximum = Seq(params.localCommitParams.maxAcceptedHtlcs, params.remoteCommitParams.maxAcceptedHtlcs).min)) + if (Seq(localCommitParams.maxAcceptedHtlcs, remoteCommitParams.maxAcceptedHtlcs).min < outgoingHtlcs.size) { + return Left(TooManyAcceptedHtlcs(params.channelId, maximum = Seq(localCommitParams.maxAcceptedHtlcs, remoteCommitParams.maxAcceptedHtlcs).min)) } // If sending this htlc would overflow our dust exposure, we reject it. val maxDustExposure = feeConf.feerateToleranceFor(params.remoteNodeId).dustTolerance.maxExposure val localReduced = DustExposure.reduceForDustExposure(localCommit.spec, changes.localChanges.all, changes.remoteChanges.all) - val localDustExposureAfterAdd = DustExposure.computeExposure(localReduced, params.localCommitParams.dustLimit, params.commitmentFormat) + val localDustExposureAfterAdd = DustExposure.computeExposure(localReduced, localCommitParams.dustLimit, commitmentFormat) if (localDustExposureAfterAdd > maxDustExposure) { return Left(LocalDustHtlcExposureTooHigh(params.channelId, maxDustExposure, localDustExposureAfterAdd)) } val remoteReduced = DustExposure.reduceForDustExposure(remoteCommit1.spec, changes.remoteChanges.all, changes.localChanges.all) - val remoteDustExposureAfterAdd = DustExposure.computeExposure(remoteReduced, params.remoteCommitParams.dustLimit, params.commitmentFormat) + val remoteDustExposureAfterAdd = DustExposure.computeExposure(remoteReduced, remoteCommitParams.dustLimit, commitmentFormat) if (remoteDustExposureAfterAdd > maxDustExposure) { return Left(RemoteDustHtlcExposureTooHigh(params.channelId, maxDustExposure, remoteDustExposureAfterAdd)) } // Jamming protection // Must be the last checks so that they can be ignored for shadow deployment. - reputationScore.checkOutgoingChannelOccupancy(outgoingHtlcs.toSeq, params) + reputationScore.checkOutgoingChannelOccupancy(params.channelId, this, outgoingHtlcs.toSeq) } def canReceiveAdd(amount: MilliSatoshi, params: ChannelParams, changes: CommitmentChanges, feerates: FeeratesPerKw, feeConf: OnChainFeeConf): Either[ChannelException, Unit] = { // we allowed mismatches between our feerates and our remote's as long as commitments didn't contain any HTLC at risk // we need to verify that we're not disagreeing on feerates anymore before accepting new HTLCs // NB: there may be a pending update_fee that hasn't been applied yet that needs to be taken into account - val localFeerate = feeConf.getCommitmentFeerate(feerates, params.remoteNodeId, params.commitmentFormat, capacity) + val localFeerate = feeConf.getCommitmentFeerate(feerates, params.remoteNodeId, commitmentFormat, capacity) val remoteFeerate = localCommit.spec.commitTxFeerate +: changes.remoteChanges.all.collect { case f: UpdateFee => f.feeratePerKw } - remoteFeerate.find(feerate => feeConf.feerateToleranceFor(params.remoteNodeId).isProposedFeerateTooLow(params.commitmentFormat, localFeerate, feerate)) match { + remoteFeerate.find(feerate => feeConf.feerateToleranceFor(params.remoteNodeId).isProposedFeerateTooLow(commitmentFormat, localFeerate, feerate)) match { case Some(feerate) => return Left(FeerateTooDifferent(params.channelId, localFeeratePerKw = localFeerate, remoteFeeratePerKw = feerate)) case None => } @@ -550,7 +547,7 @@ case class Commitment(fundingTxIndex: Long, val incomingHtlcs = reduced.htlcs.collect(DirectedHtlc.incoming) // note that the initiator pays the fee, so if sender != initiator, both sides will have to afford this payment - val fees = commitTxTotalCost(params.localCommitParams.dustLimit, reduced, params.commitmentFormat) + val fees = commitTxTotalCost(localCommitParams.dustLimit, reduced, commitmentFormat) // NB: we don't enforce the funderFeeReserve (see sendAdd) because it would confuse a remote initiator that doesn't have this mitigation in place // We could enforce it once we're confident a large portion of the network implements it. val missingForSender = reduced.toRemote - remoteChannelReserve(params) - (if (params.localParams.paysCommitTxFees) 0.sat else fees) @@ -570,12 +567,12 @@ case class Commitment(fundingTxIndex: Long, // NB: we need the `toSeq` because otherwise duplicate amountMsat would be removed (since incomingHtlcs is a Set). val htlcValueInFlight = incomingHtlcs.toSeq.map(_.amountMsat).sum - if (params.localCommitParams.maxHtlcValueInFlight < htlcValueInFlight) { - return Left(HtlcValueTooHighInFlight(params.channelId, maximum = params.localCommitParams.maxHtlcValueInFlight, actual = htlcValueInFlight)) + if (localCommitParams.maxHtlcValueInFlight < htlcValueInFlight) { + return Left(HtlcValueTooHighInFlight(params.channelId, maximum = localCommitParams.maxHtlcValueInFlight, actual = htlcValueInFlight)) } - if (incomingHtlcs.size > params.localCommitParams.maxAcceptedHtlcs) { - return Left(TooManyAcceptedHtlcs(params.channelId, maximum = params.localCommitParams.maxAcceptedHtlcs)) + if (incomingHtlcs.size > localCommitParams.maxAcceptedHtlcs) { + return Left(TooManyAcceptedHtlcs(params.channelId, maximum = localCommitParams.maxAcceptedHtlcs)) } Right(()) @@ -586,7 +583,7 @@ case class Commitment(fundingTxIndex: Long, val reduced = CommitmentSpec.reduce(remoteCommit.spec, changes.remoteChanges.acked, changes.localChanges.proposed) // a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee // we look from remote's point of view, so if local is initiator remote doesn't pay the fees - val fees = commitTxTotalCost(params.remoteCommitParams.dustLimit, reduced, params.commitmentFormat) + val fees = commitTxTotalCost(remoteCommitParams.dustLimit, reduced, commitmentFormat) val missing = reduced.toRemote.truncateToSatoshi - localChannelReserve(params) - fees if (missing < 0.sat) { return Left(CannotAffordFees(params.channelId, missing = -missing, reserve = localChannelReserve(params), fees = fees)) @@ -597,12 +594,12 @@ case class Commitment(fundingTxIndex: Long, // this is the commitment as it would be if our update_fee was immediately signed by both parties (it is only an // estimate because there can be concurrent updates) val localReduced = DustExposure.reduceForDustExposure(localCommit.spec, changes.localChanges.all, changes.remoteChanges.all) - val localDustExposureAfterFeeUpdate = DustExposure.computeExposure(localReduced, targetFeerate, params.localCommitParams.dustLimit, params.commitmentFormat) + val localDustExposureAfterFeeUpdate = DustExposure.computeExposure(localReduced, targetFeerate, localCommitParams.dustLimit, commitmentFormat) if (localDustExposureAfterFeeUpdate > maxDustExposure) { return Left(LocalDustHtlcExposureTooHigh(params.channelId, maxDustExposure, localDustExposureAfterFeeUpdate)) } val remoteReduced = DustExposure.reduceForDustExposure(remoteCommit.spec, changes.remoteChanges.all, changes.localChanges.all) - val remoteDustExposureAfterFeeUpdate = DustExposure.computeExposure(remoteReduced, targetFeerate, params.remoteCommitParams.dustLimit, params.commitmentFormat) + val remoteDustExposureAfterFeeUpdate = DustExposure.computeExposure(remoteReduced, targetFeerate, remoteCommitParams.dustLimit, commitmentFormat) if (remoteDustExposureAfterFeeUpdate > maxDustExposure) { return Left(RemoteDustHtlcExposureTooHigh(params.channelId, maxDustExposure, remoteDustExposureAfterFeeUpdate)) } @@ -611,10 +608,10 @@ case class Commitment(fundingTxIndex: Long, } def canReceiveFee(targetFeerate: FeeratePerKw, params: ChannelParams, changes: CommitmentChanges, feerates: FeeratesPerKw, feeConf: OnChainFeeConf): Either[ChannelException, Unit] = { - val localFeerate = feeConf.getCommitmentFeerate(feerates, params.remoteNodeId, params.commitmentFormat, capacity) - if (feeConf.feerateToleranceFor(params.remoteNodeId).isProposedFeerateTooHigh(params.commitmentFormat, localFeerate, targetFeerate)) { + val localFeerate = feeConf.getCommitmentFeerate(feerates, params.remoteNodeId, commitmentFormat, capacity) + if (feeConf.feerateToleranceFor(params.remoteNodeId).isProposedFeerateTooHigh(commitmentFormat, localFeerate, targetFeerate)) { return Left(FeerateTooDifferent(params.channelId, localFeeratePerKw = localFeerate, remoteFeeratePerKw = targetFeerate)) - } else if (feeConf.feerateToleranceFor(params.remoteNodeId).isProposedFeerateTooLow(params.commitmentFormat, localFeerate, targetFeerate) && hasPendingOrProposedHtlcs(changes)) { + } else if (feeConf.feerateToleranceFor(params.remoteNodeId).isProposedFeerateTooLow(commitmentFormat, localFeerate, targetFeerate) && hasPendingOrProposedHtlcs(changes)) { // If the proposed feerate is too low, but we don't have any pending HTLC, we temporarily accept it. return Left(FeerateTooDifferent(params.channelId, localFeeratePerKw = localFeerate, remoteFeeratePerKw = targetFeerate)) } else { @@ -625,7 +622,7 @@ case class Commitment(fundingTxIndex: Long, // (it also means that we need to check the fee of the initial commitment tx somewhere) val reduced = CommitmentSpec.reduce(localCommit.spec, changes.localChanges.acked, changes.remoteChanges.proposed) // a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee - val fees = commitTxTotalCost(params.localCommitParams.dustLimit, reduced, params.commitmentFormat) + val fees = commitTxTotalCost(localCommitParams.dustLimit, reduced, commitmentFormat) val missing = reduced.toRemote.truncateToSatoshi - remoteChannelReserve(params) - fees if (missing < 0.sat) { return Left(CannotAffordFees(params.channelId, missing = -missing, reserve = remoteChannelReserve(params), fees = fees)) @@ -634,14 +631,14 @@ case class Commitment(fundingTxIndex: Long, if (feeConf.feerateToleranceFor(params.remoteNodeId).dustTolerance.closeOnUpdateFeeOverflow) { val maxDustExposure = feeConf.feerateToleranceFor(params.remoteNodeId).dustTolerance.maxExposure val localReduced = DustExposure.reduceForDustExposure(localCommit.spec, changes.localChanges.all, changes.remoteChanges.all) - val localDustExposureAfterFeeUpdate = DustExposure.computeExposure(localReduced, targetFeerate, params.localCommitParams.dustLimit, params.commitmentFormat) + val localDustExposureAfterFeeUpdate = DustExposure.computeExposure(localReduced, targetFeerate, localCommitParams.dustLimit, commitmentFormat) if (localDustExposureAfterFeeUpdate > maxDustExposure) { return Left(LocalDustHtlcExposureTooHigh(params.channelId, maxDustExposure, localDustExposureAfterFeeUpdate)) } // this is the commitment as it would be if their update_fee was immediately signed by both parties (it is only an // estimate because there can be concurrent updates) val remoteReduced = DustExposure.reduceForDustExposure(remoteCommit.spec, changes.remoteChanges.all, changes.localChanges.all) - val remoteDustExposureAfterFeeUpdate = DustExposure.computeExposure(remoteReduced, targetFeerate, params.remoteCommitParams.dustLimit, params.commitmentFormat) + val remoteDustExposureAfterFeeUpdate = DustExposure.computeExposure(remoteReduced, targetFeerate, remoteCommitParams.dustLimit, commitmentFormat) if (remoteDustExposureAfterFeeUpdate > maxDustExposure) { return Left(RemoteDustHtlcExposureTooHigh(params.channelId, maxDustExposure, remoteDustExposureAfterFeeUpdate)) } @@ -653,8 +650,8 @@ case class Commitment(fundingTxIndex: Long, def sendCommit(params: ChannelParams, channelKeys: ChannelKeys, commitKeys: RemoteCommitmentKeys, changes: CommitmentChanges, remoteNextPerCommitmentPoint: PublicKey, batchSize: Int)(implicit log: LoggingAdapter): (Commitment, CommitSig) = { // remote commitment will include all local proposed changes + remote acked changes val spec = CommitmentSpec.reduce(remoteCommit.spec, changes.remoteChanges.acked, changes.localChanges.proposed) - val fundingKey = channelKeys.fundingKey(fundingTxIndex) - val (remoteCommitTx, htlcTxs) = Commitment.makeRemoteTxs(params, params.remoteCommitParams, commitKeys, remoteCommit.index + 1, fundingKey, remoteFundingPubKey, commitInput, spec) + val fundingKey = localFundingKey(channelKeys) + val (remoteCommitTx, htlcTxs) = Commitment.makeRemoteTxs(params, remoteCommitParams, commitKeys, remoteCommit.index + 1, fundingKey, remoteFundingPubKey, commitInput(fundingKey), commitmentFormat, spec) val htlcSigs = htlcTxs.sortBy(_.input.outPoint.index).map(_.localSig(commitKeys)) // NB: IN/OUT htlcs are inverted because this is the remote commit @@ -664,7 +661,7 @@ case class Commitment(fundingTxIndex: Long, val tlvs = Set( if (batchSize > 1) Some(CommitSigTlv.BatchTlv(batchSize)) else None ).flatten[CommitSigTlv] - val commitSig = params.commitmentFormat match { + val commitSig = commitmentFormat match { case _: SegwitV0CommitmentFormat => val sig = remoteCommitTx.sign(fundingKey, remoteFundingPubKey).sig CommitSig(params.channelId, sig, htlcSigs.toList, TlvStream(tlvs)) @@ -684,9 +681,9 @@ case class Commitment(fundingTxIndex: Long, // we will reply to this sig with our old revocation hash preimage (at index) and our next revocation hash (at index + 1) // and will increment our index val localCommitIndex = localCommit.index + 1 - val fundingKey = channelKeys.fundingKey(fundingTxIndex) + val fundingKey = localFundingKey(channelKeys) val spec = CommitmentSpec.reduce(localCommit.spec, changes.localChanges.acked, changes.remoteChanges.proposed) - LocalCommit.fromCommitSig(params, commitKeys, fundingTxId, fundingKey, remoteFundingPubKey, commitInput, commit, localCommitIndex, spec).map { localCommit1 => + LocalCommit.fromCommitSig(params, localCommitParams, commitKeys, fundingTxId, fundingKey, remoteFundingPubKey, commitInput(fundingKey), commit, localCommitIndex, spec, commitmentFormat).map { localCommit1 => log.info(s"built local commit number=$localCommitIndex toLocalMsat=${spec.toLocal.toLong} toRemoteMsat=${spec.toRemote.toLong} htlc_in={} htlc_out={} feeratePerKw=${spec.commitTxFeerate} txid=${localCommit1.txId} fundingTxId=$fundingTxId", spec.htlcs.collect(DirectedHtlc.incoming).map(_.id).mkString(","), spec.htlcs.collect(DirectedHtlc.outgoing).map(_.id).mkString(",")) copy(localCommit = localCommit1) } @@ -694,9 +691,9 @@ case class Commitment(fundingTxIndex: Long, /** Return a fully signed commit tx, that can be published as-is. */ def fullySignedLocalCommitTx(params: ChannelParams, channelKeys: ChannelKeys): Transaction = { - val fundingKey = channelKeys.fundingKey(fundingTxIndex) + val fundingKey = localFundingKey(channelKeys) val commitKeys = localKeys(params, channelKeys) - val (unsignedCommitTx, _) = Commitment.makeLocalTxs(params, params.localCommitParams, commitKeys, localCommit.index, fundingKey, remoteFundingPubKey, localCommit.input, localCommit.spec) + val (unsignedCommitTx, _) = Commitment.makeLocalTxs(params, localCommitParams, commitKeys, localCommit.index, fundingKey, remoteFundingPubKey, commitInput(fundingKey), commitmentFormat, localCommit.spec) localCommit.remoteSig match { case remoteSig: ChannelSpendSignature.IndividualSignature => val localSig = unsignedCommitTx.sign(fundingKey, remoteFundingPubKey) @@ -707,14 +704,14 @@ case class Commitment(fundingTxIndex: Long, /** Return the HTLC transactions for our local commit and the corresponding remote signatures. */ def htlcTxs(params: ChannelParams, channelKeys: ChannelKeys): Seq[(UnsignedHtlcTx, ByteVector64)] = { - val fundingKey = channelKeys.fundingKey(fundingTxIndex) + val fundingKey = localFundingKey(channelKeys) val commitKeys = localKeys(params, channelKeys) htlcTxs(params, fundingKey, commitKeys) } /** Return the HTLC transactions for our local commit and the corresponding remote signatures. */ def htlcTxs(params: ChannelParams, fundingKey: PrivateKey, commitKeys: LocalCommitmentKeys): Seq[(UnsignedHtlcTx, ByteVector64)] = { - val (_, htlcTxs) = Commitment.makeLocalTxs(params, params.localCommitParams, commitKeys, localCommit.index, fundingKey, remoteFundingPubKey, localCommit.input, localCommit.spec) + val (_, htlcTxs) = Commitment.makeLocalTxs(params, localCommitParams, commitKeys, localCommit.index, fundingKey, remoteFundingPubKey, commitInput(fundingKey), commitmentFormat, localCommit.spec) htlcTxs.sortBy(_.input.outPoint.index).zip(localCommit.htlcRemoteSigs) } @@ -728,10 +725,11 @@ object Commitment { localFundingKey: PrivateKey, remoteFundingPubKey: PublicKey, commitmentInput: InputInfo, + commitmentFormat: CommitmentFormat, spec: CommitmentSpec): (CommitTx, Seq[UnsignedHtlcTx]) = { - val outputs = makeCommitTxOutputs(localFundingKey.publicKey, remoteFundingPubKey, commitKeys.publicKeys, channelParams.localParams.paysCommitTxFees, commitParams.dustLimit, commitParams.toSelfDelay, spec, channelParams.commitmentFormat) + val outputs = makeCommitTxOutputs(localFundingKey.publicKey, remoteFundingPubKey, commitKeys.publicKeys, channelParams.localParams.paysCommitTxFees, commitParams.dustLimit, commitParams.toSelfDelay, spec, commitmentFormat) val commitTx = makeCommitTx(commitmentInput, commitTxNumber, commitKeys.ourPaymentBasePoint, channelParams.remoteParams.paymentBasepoint, channelParams.localParams.isChannelOpener, outputs) - val htlcTxs = makeHtlcTxs(commitTx.tx, outputs, channelParams.commitmentFormat) + val htlcTxs = makeHtlcTxs(commitTx.tx, outputs, commitmentFormat) (commitTx, htlcTxs) } @@ -742,10 +740,11 @@ object Commitment { localFundingKey: PrivateKey, remoteFundingPubKey: PublicKey, commitmentInput: InputInfo, + commitmentFormat: CommitmentFormat, spec: CommitmentSpec): (CommitTx, Seq[UnsignedHtlcTx]) = { - val outputs = makeCommitTxOutputs(remoteFundingPubKey, localFundingKey.publicKey, commitKeys.publicKeys, !channelParams.localParams.paysCommitTxFees, commitParams.dustLimit, commitParams.toSelfDelay, spec, channelParams.commitmentFormat) + val outputs = makeCommitTxOutputs(remoteFundingPubKey, localFundingKey.publicKey, commitKeys.publicKeys, !channelParams.localParams.paysCommitTxFees, commitParams.dustLimit, commitParams.toSelfDelay, spec, commitmentFormat) val commitTx = makeCommitTx(commitmentInput, commitTxNumber, channelParams.remoteParams.paymentBasepoint, commitKeys.ourPaymentBasePoint, !channelParams.localParams.isChannelOpener, outputs) - val htlcTxs = makeHtlcTxs(commitTx.tx, outputs, channelParams.commitmentFormat) + val htlcTxs = makeHtlcTxs(commitTx.tx, outputs, commitmentFormat) (commitTx, htlcTxs) } } @@ -758,32 +757,31 @@ case class AnnouncedCommitment(commitment: Commitment, announcement: ChannelAnno } /** Subset of Commitments when we want to work with a single, specific commitment. */ -case class FullCommitment(channelParams: ChannelParams, changes: CommitmentChanges, - fundingTxIndex: Long, - firstRemoteCommitIndex: Long, - remoteFundingPubKey: PublicKey, - localFundingStatus: LocalFundingStatus, remoteFundingStatus: RemoteFundingStatus, - localCommit: LocalCommit, remoteCommit: RemoteCommit, nextRemoteCommit_opt: Option[NextRemoteCommit]) { +case class FullCommitment(channelParams: ChannelParams, changes: CommitmentChanges, commitment: Commitment) { val channelId: ByteVector32 = channelParams.channelId - val shortChannelId_opt: Option[RealShortChannelId] = localFundingStatus match { - case f: LocalFundingStatus.ConfirmedFundingTx => Some(f.shortChannelId) - case _ => None - } + val shortChannelId_opt: Option[RealShortChannelId] = commitment.shortChannelId_opt + val fundingTxIndex: Long = commitment.fundingTxIndex + val fundingInput: OutPoint = commitment.fundingInput + val fundingTxId: TxId = commitment.fundingTxId + val remoteFundingPubKey: PublicKey = commitment.remoteFundingPubKey + val localFundingStatus: LocalFundingStatus = commitment.localFundingStatus + val commitTxIds: CommitTxIds = commitment.commitTxIds val localChannelParams: LocalChannelParams = channelParams.localParams - val localCommitParams: CommitParams = channelParams.localCommitParams + val localCommitParams: CommitParams = commitment.localCommitParams + val localCommit: LocalCommit = commitment.localCommit val remoteChannelParams: RemoteChannelParams = channelParams.remoteParams - val remoteCommitParams: CommitParams = channelParams.remoteCommitParams - val commitInput: InputInfo = localCommit.input - val fundingTxId: TxId = commitInput.outPoint.txid - val commitTxIds: CommitTxIds = CommitTxIds(localCommit.txId, remoteCommit.txId, nextRemoteCommit_opt.map(_.commit.txId)) - val capacity: Satoshi = commitInput.txOut.amount - val commitmentFormat: CommitmentFormat = channelParams.commitmentFormat - val commitment: Commitment = Commitment(fundingTxIndex, firstRemoteCommitIndex, remoteFundingPubKey, localFundingStatus, remoteFundingStatus, localCommit, remoteCommit, nextRemoteCommit_opt) + val remoteCommitParams: CommitParams = commitment.remoteCommitParams + val remoteCommit: RemoteCommit = commitment.remoteCommit + val nextRemoteCommit_opt: Option[NextRemoteCommit] = commitment.nextRemoteCommit_opt + val commitmentFormat: CommitmentFormat = commitment.commitmentFormat + val capacity: Satoshi = commitment.fundingAmount def localKeys(channelKeys: ChannelKeys): LocalCommitmentKeys = commitment.localKeys(channelParams, channelKeys) def remoteKeys(channelKeys: ChannelKeys, remotePerCommitmentPoint: PublicKey): RemoteCommitmentKeys = commitment.remoteKeys(channelParams, channelKeys, remotePerCommitmentPoint) + def commitInput(channelKeys: ChannelKeys): InputInfo = commitment.commitInput(channelKeys) + def localChannelReserve: Satoshi = commitment.localChannelReserve(channelParams) def remoteChannelReserve: Satoshi = commitment.remoteChannelReserve(channelParams) @@ -851,13 +849,14 @@ case class Commitments(channelParams: ChannelParams, // While we have multiple active commitments, we use the most restrictive one. val capacity: Satoshi = active.map(_.capacity).min + val maxHtlcValueInFlight: MilliSatoshi = active.map(_.maxHtlcValueInFlight).min lazy val availableBalanceForSend: MilliSatoshi = active.map(_.availableBalanceForSend(channelParams, changes)).min lazy val availableBalanceForReceive: MilliSatoshi = active.map(_.availableBalanceForReceive(channelParams, changes)).min val all: Seq[Commitment] = active ++ inactive // We always use the last commitment that was created, to make sure we never go back in time. - val latest: FullCommitment = FullCommitment(channelParams, changes, active.head.fundingTxIndex, active.head.firstRemoteCommitIndex, active.head.remoteFundingPubKey, active.head.localFundingStatus, active.head.remoteFundingStatus, active.head.localCommit, active.head.remoteCommit, active.head.nextRemoteCommit_opt) + val latest: FullCommitment = FullCommitment(channelParams, changes, active.head) val lastLocalLocked_opt: Option[Commitment] = active.filter(_.localFundingStatus.isInstanceOf[LocalFundingStatus.Locked]).sortBy(_.fundingTxIndex).lastOption val lastRemoteLocked_opt: Option[Commitment] = active.filter(c => c.remoteFundingStatus == RemoteFundingStatus.Locked).sortBy(_.fundingTxIndex).lastOption @@ -897,7 +896,7 @@ case class Commitments(channelParams: ChannelParams, } // even if remote advertises support for 0 msat htlc, we limit ourselves to values strictly positive, hence the max(1 msat) - val htlcMinimum = channelParams.remoteCommitParams.htlcMinimum.max(1 msat) + val htlcMinimum = active.map(_.remoteCommitParams.htlcMinimum).max.max(1 msat) if (cmd.amount < htlcMinimum) { return Left(HtlcValueTooSmall(channelId, minimum = htlcMinimum, actual = cmd.amount)) } @@ -939,7 +938,7 @@ case class Commitments(channelParams: ChannelParams, } // we used to not enforce a strictly positive minimum, hence the max(1 msat) - val htlcMinimum = channelParams.localCommitParams.htlcMinimum.max(1 msat) + val htlcMinimum = active.map(_.localCommitParams.htlcMinimum).max.max(1 msat) if (add.amountMsat < htlcMinimum) { return Left(HtlcValueTooSmall(channelId, minimum = htlcMinimum, actual = add.amountMsat)) } @@ -1038,7 +1037,7 @@ case class Commitments(channelParams: ChannelParams, active.map(_.canSendFee(cmd.feeratePerKw, channelParams, changes1, feeConf)) .collectFirst { case Left(f) => Left(f) } .getOrElse { - Metrics.LocalFeeratePerByte.withTag(Tags.CommitmentFormat, channelParams.commitmentFormat.toString).record(FeeratePerByte(cmd.feeratePerKw).feerate.toLong) + Metrics.LocalFeeratePerByte.withTag(Tags.CommitmentFormat, active.head.commitmentFormat.toString).record(FeeratePerByte(cmd.feeratePerKw).feerate.toLong) Right(copy(changes = changes1), fee) } } @@ -1050,14 +1049,14 @@ case class Commitments(channelParams: ChannelParams, } else if (fee.feeratePerKw < FeeratePerKw.MinimumFeeratePerKw) { Left(FeerateTooSmall(channelId, remoteFeeratePerKw = fee.feeratePerKw)) } else { - val localFeeratePerKw = feeConf.getCommitmentFeerate(feerates, remoteNodeId, channelParams.commitmentFormat, active.head.capacity) + val localFeeratePerKw = feeConf.getCommitmentFeerate(feerates, remoteNodeId, active.head.commitmentFormat, active.head.capacity) log.info("remote feeratePerKw={}, local feeratePerKw={}, ratio={}", fee.feeratePerKw, localFeeratePerKw, fee.feeratePerKw.toLong.toDouble / localFeeratePerKw.toLong) // update_fee replace each other, so we can remove previous ones val changes1 = changes.copy(remoteChanges = changes.remoteChanges.copy(proposed = changes.remoteChanges.proposed.filterNot(_.isInstanceOf[UpdateFee]) :+ fee)) active.map(_.canReceiveFee(fee.feeratePerKw, channelParams, changes1, feerates, feeConf)) .collectFirst { case Left(f) => Left(f) } .getOrElse { - Metrics.RemoteFeeratePerByte.withTag(Tags.CommitmentFormat, channelParams.commitmentFormat.toString).record(FeeratePerByte(fee.feeratePerKw).feerate.toLong) + Metrics.RemoteFeeratePerByte.withTag(Tags.CommitmentFormat, active.head.commitmentFormat.toString).record(FeeratePerByte(fee.feeratePerKw).feerate.toLong) Right(copy(changes = changes1)) } } @@ -1067,8 +1066,10 @@ case class Commitments(channelParams: ChannelParams, remoteNextCommitInfo match { case Right(_) if !changes.localHasChanges => Left(CannotSignWithoutChanges(channelId)) case Right(remoteNextPerCommitmentPoint) => - val commitKeys = RemoteCommitmentKeys(channelParams, channelKeys, remoteNextPerCommitmentPoint) - val (active1, sigs) = active.map(_.sendCommit(channelParams, channelKeys, commitKeys, changes, remoteNextPerCommitmentPoint, active.size)).unzip + val (active1, sigs) = active.map(c => { + val commitKeys = RemoteCommitmentKeys(channelParams, channelKeys, remoteNextPerCommitmentPoint, c.commitmentFormat) + c.sendCommit(channelParams, channelKeys, commitKeys, changes, remoteNextPerCommitmentPoint, active.size) + }).unzip val commitments1 = copy( changes = changes.copy( localChanges = changes.localChanges.copy(proposed = Nil, signed = changes.localChanges.proposed), @@ -1092,9 +1093,9 @@ case class Commitments(channelParams: ChannelParams, case _: CommitSig if active.size > 1 => return Left(CommitSigCountMismatch(channelId, active.size, 1)) case commitSig: CommitSig => Seq(commitSig) } - val commitKeys = LocalCommitmentKeys(channelParams, channelKeys, localCommitIndex + 1) // Signatures are sent in order (most recent first), calling `zip` will drop trailing sigs that are for deactivated/pruned commitments. val active1 = active.zip(sigs).map { case (commitment, commit) => + val commitKeys = LocalCommitmentKeys(channelParams, channelKeys, localCommitIndex + 1, commitment.commitmentFormat) commitment.receiveCommit(channelParams, channelKeys, commitKeys, changes, commit) match { case Left(f) => return Left(f) case Right(commitment1) => commitment1 @@ -1159,21 +1160,21 @@ case class Commitments(channelParams: ChannelParams, case _ => true }) val localReduced = DustExposure.reduceForDustExposure(localSpecWithoutNewHtlcs, changes.localChanges.all, changes.remoteChanges.acked) - val localCommitDustExposure = DustExposure.computeExposure(localReduced, channelParams.localCommitParams.dustLimit, channelParams.commitmentFormat) + val localCommitDustExposure = active.map(c => DustExposure.computeExposure(localReduced, c.localCommitParams.dustLimit, c.commitmentFormat)).max val remoteReduced = DustExposure.reduceForDustExposure(remoteSpecWithoutNewHtlcs, changes.remoteChanges.acked, changes.localChanges.all) - val remoteCommitDustExposure = DustExposure.computeExposure(remoteReduced, channelParams.remoteCommitParams.dustLimit, channelParams.commitmentFormat) + val remoteCommitDustExposure = active.map(c => DustExposure.computeExposure(remoteReduced, c.remoteCommitParams.dustLimit, c.commitmentFormat)).max // we sort incoming htlcs by decreasing amount: we want to prioritize higher amounts. val sortedReceivedHtlcs = receivedHtlcs.sortBy(_.amountMsat).reverse DustExposure.filterBeforeForward( maxDustExposure, localReduced, - channelParams.localCommitParams.dustLimit, + active.map(_.localCommitParams.dustLimit).max, localCommitDustExposure, remoteReduced, - channelParams.remoteCommitParams.dustLimit, + active.map(_.remoteCommitParams.dustLimit).max, remoteCommitDustExposure, sortedReceivedHtlcs, - channelParams.commitmentFormat) + active.head.commitmentFormat) } val actions = acceptedHtlcs.map(add => PostRevocationAction.RelayHtlc(add)) ++ rejectedHtlcs.map(add => PostRevocationAction.RejectHtlc(add)) ++ @@ -1212,10 +1213,14 @@ case class Commitments(channelParams: ChannelParams, def validateSeed(channelKeys: ChannelKeys): Boolean = { active.forall { commitment => - val localFundingKey = channelKeys.fundingKey(commitment.fundingTxIndex).publicKey - val remoteFundingKey = commitment.remoteFundingPubKey - val redeemInfo = Helpers.Funding.makeFundingScript(localFundingKey, remoteFundingKey, channelParams.commitmentFormat) - commitment.commitInput.txOut.publicKeyScript == redeemInfo.pubkeyScript + commitment.localFundingStatus match { + // We ignore unconfirmed transactions for simplicity. + case _: LocalFundingStatus.UnconfirmedFundingTx => true + case tx: LocalFundingStatus.ConfirmedFundingTx => + val localFundingKey = commitment.localFundingKey(channelKeys).publicKey + val redeemInfo = Transactions.makeFundingScript(localFundingKey, commitment.remoteFundingPubKey, commitment.commitmentFormat) + tx.txOut.publicKeyScript == redeemInfo.pubkeyScript + } } } @@ -1353,7 +1358,7 @@ case class Commitments(channelParams: ChannelParams, * @param spendingTx A transaction that may spend a current or former funding tx */ def resolveCommitment(spendingTx: Transaction): Option[Commitment] = { - all.find(c => spendingTx.txIn.map(_.outPoint).contains(c.commitInput.outPoint)) + all.find(c => spendingTx.txIn.map(_.outPoint).contains(c.fundingInput)) } /** Find the corresponding commitment based on its short_channel_id (once funding transaction is confirmed). */ diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 2ee5f15280..14fce97f89 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -30,7 +30,6 @@ import fr.acinq.eclair.db.ChannelsDb import fr.acinq.eclair.payment.relay.Relayer.RelayFees import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.transactions.DirectedHtlc._ -import fr.acinq.eclair.transactions.Scripts._ import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.transactions._ import fr.acinq.eclair.wire.protocol._ @@ -273,7 +272,7 @@ object Helpers { for { script_opt <- extractShutdownScript(accept.temporaryChannelId, localFeatures, remoteFeatures, accept.upfrontShutdownScript_opt) - fundingScript = Funding.makeFundingScript(open.fundingPubkey, accept.fundingPubkey, channelType.commitmentFormat).pubkeyScript + fundingScript = Transactions.makeFundingScript(open.fundingPubkey, accept.fundingPubkey, channelType.commitmentFormat).pubkeyScript liquidityPurchase_opt <- LiquidityAds.validateRemoteFunding(open.requestFunding_opt, remoteNodeId, accept.temporaryChannelId, fundingScript, accept.fundingAmount, open.fundingFeerate, isChannelCreation = true, accept.willFund_opt) } yield { val channelFeatures = ChannelFeatures(channelType, localFeatures, remoteFeatures, open.channelFlags.announceChannel) @@ -368,17 +367,17 @@ object Helpers { def maxHtlcAmount(nodeParams: NodeParams, commitments: Commitments): MilliSatoshi = { if (!commitments.announceChannel) { // The channel is private, let's not change the channel update needlessly. - return commitments.channelParams.maxHtlcValueInFlight + return commitments.maxHtlcValueInFlight } for (balanceThreshold <- nodeParams.channelConf.balanceThresholds) { if (commitments.availableBalanceForSend <= balanceThreshold.available) { // Our maximum HTLC amount must always be greater than htlc_minimum_msat. - val allowedHtlcAmount = Seq(balanceThreshold.maxHtlcAmount.toMilliSatoshi, commitments.channelParams.localCommitParams.htlcMinimum, commitments.channelParams.remoteCommitParams.htlcMinimum).max + val allowedHtlcAmount = Seq(balanceThreshold.maxHtlcAmount.toMilliSatoshi, commitments.latest.localCommitParams.htlcMinimum, commitments.latest.remoteCommitParams.htlcMinimum).max // But it cannot exceed the channel's max_htlc_value_in_flight_msat. - return allowedHtlcAmount.min(commitments.channelParams.maxHtlcValueInFlight) + return allowedHtlcAmount.min(commitments.maxHtlcValueInFlight) } } - commitments.channelParams.maxHtlcValueInFlight + commitments.maxHtlcValueInFlight } def getRelayFees(nodeParams: NodeParams, remoteNodeId: PublicKey, announceChannel: Boolean): RelayFees = { @@ -388,37 +387,26 @@ object Helpers { object Funding { - def makeFundingScript(localFundingKey: PublicKey, remoteFundingKey: PublicKey, commitmentFormat: CommitmentFormat): RedeemInfo = { - commitmentFormat match { - case _: SegwitV0CommitmentFormat => RedeemInfo.P2wsh(Script.write(multiSig2of2(localFundingKey, remoteFundingKey))) - case _: SimpleTaprootChannelCommitmentFormat => RedeemInfo.TaprootKeyPath(Taproot.musig2Aggregate(localFundingKey, remoteFundingKey), None) - } - } - - def makeFundingInputInfo(fundingTxId: TxId, fundingTxOutputIndex: Int, fundingSatoshis: Satoshi, fundingPubkey1: PublicKey, fundingPubkey2: PublicKey, commitmentFormat: CommitmentFormat): InputInfo = { - val redeemInfo = makeFundingScript(fundingPubkey1, fundingPubkey2, commitmentFormat) - val fundingTxOut = TxOut(fundingSatoshis, redeemInfo.pubkeyScript) - InputInfo(OutPoint(fundingTxId, fundingTxOutputIndex), fundingTxOut, ByteVector.empty) - } - /** * Creates both sides' first commitment transaction. * * @return (localSpec, localTx, remoteSpec, remoteTx) */ - def makeFirstCommitTxs(params: ChannelParams, + def makeFirstCommitTxs(channelParams: ChannelParams, + localCommitParams: CommitParams, remoteCommitParams: CommitParams, localFundingAmount: Satoshi, remoteFundingAmount: Satoshi, localPushAmount: MilliSatoshi, remotePushAmount: MilliSatoshi, - commitTxFeerate: FeeratePerKw, + commitTxFeerate: FeeratePerKw, commitmentFormat: CommitmentFormat, fundingTxId: TxId, fundingTxOutputIndex: Int, localFundingKey: PrivateKey, remoteFundingPubKey: PublicKey, localCommitKeys: LocalCommitmentKeys, remoteCommitKeys: RemoteCommitmentKeys): Either[ChannelException, (CommitmentSpec, CommitTx, CommitmentSpec, CommitTx)] = { - makeCommitTxs(params, + makeCommitTxs(channelParams, localCommitParams, remoteCommitParams, fundingAmount = localFundingAmount + remoteFundingAmount, toLocal = localFundingAmount.toMilliSatoshi - localPushAmount + remotePushAmount, toRemote = remoteFundingAmount.toMilliSatoshi + localPushAmount - remotePushAmount, localHtlcs = Set.empty, commitTxFeerate, + commitmentFormat, fundingTxIndex = 0, fundingTxId, fundingTxOutputIndex, localFundingKey, remoteFundingPubKey, @@ -432,11 +420,14 @@ object Helpers { * This creates commitment transactions for both sides at an arbitrary `commitmentIndex` and with (optional) `htlc` * outputs. This function should only be used when commitments are synchronized (local and remote htlcs match). */ - def makeCommitTxs(params: ChannelParams, + def makeCommitTxs(channelParams: ChannelParams, + localCommitParams: CommitParams, + remoteCommitParams: CommitParams, fundingAmount: Satoshi, toLocal: MilliSatoshi, toRemote: MilliSatoshi, localHtlcs: Set[DirectedHtlc], commitTxFeerate: FeeratePerKw, + commitmentFormat: CommitmentFormat, fundingTxIndex: Long, fundingTxId: TxId, fundingTxOutputIndex: Int, localFundingKey: PrivateKey, remoteFundingPubKey: PublicKey, @@ -445,21 +436,21 @@ object Helpers { val localSpec = CommitmentSpec(localHtlcs, commitTxFeerate, toLocal = toLocal, toRemote = toRemote) val remoteSpec = CommitmentSpec(localHtlcs.map(_.opposite), commitTxFeerate, toLocal = toRemote, toRemote = toLocal) - if (!params.localParams.paysCommitTxFees) { + if (!channelParams.localParams.paysCommitTxFees) { // They are responsible for paying the commitment transaction fee: we need to make sure they can afford it! // Note that the reserve may not always be met: we could be using dual funding with a large funding amount on // our side and a small funding amount on their side. But we shouldn't care as long as they can pay the fees for // the commitment transaction. - val fees = commitTxTotalCost(params.remoteCommitParams.dustLimit, remoteSpec, params.commitmentFormat) + val fees = commitTxTotalCost(remoteCommitParams.dustLimit, remoteSpec, commitmentFormat) val missing = fees - toRemote.truncateToSatoshi if (missing > 0.sat) { - return Left(CannotAffordFirstCommitFees(params.channelId, missing = missing, fees = fees)) + return Left(CannotAffordFirstCommitFees(channelParams.channelId, missing = missing, fees = fees)) } } - val commitmentInput = makeFundingInputInfo(fundingTxId, fundingTxOutputIndex, fundingAmount, localFundingKey.publicKey, remoteFundingPubKey, params.commitmentFormat) - val (localCommitTx, _) = Commitment.makeLocalTxs(params, params.localCommitParams, localCommitKeys, localCommitmentIndex, localFundingKey, remoteFundingPubKey, commitmentInput, localSpec) - val (remoteCommitTx, htlcTxs) = Commitment.makeRemoteTxs(params, params.remoteCommitParams, remoteCommitKeys, remoteCommitmentIndex, localFundingKey, remoteFundingPubKey, commitmentInput, remoteSpec) + val commitmentInput = makeFundingInputInfo(fundingTxId, fundingTxOutputIndex, fundingAmount, localFundingKey.publicKey, remoteFundingPubKey, commitmentFormat) + val (localCommitTx, _) = Commitment.makeLocalTxs(channelParams, localCommitParams, localCommitKeys, localCommitmentIndex, localFundingKey, remoteFundingPubKey, commitmentInput, commitmentFormat, localSpec) + val (remoteCommitTx, htlcTxs) = Commitment.makeRemoteTxs(channelParams, remoteCommitParams, remoteCommitKeys, remoteCommitmentIndex, localFundingKey, remoteFundingPubKey, commitmentInput, commitmentFormat, remoteSpec) val sortedHtlcTxs = htlcTxs.sortBy(_.input.outPoint.index) Right(localSpec, localCommitTx, remoteSpec, remoteCommitTx, sortedHtlcTxs) } @@ -679,9 +670,9 @@ object Helpers { } } - def firstClosingFee(commitment: FullCommitment, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feerates: ClosingFeerates)(implicit log: LoggingAdapter): ClosingFees = { + def firstClosingFee(channelKeys: ChannelKeys, commitment: FullCommitment, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feerates: ClosingFeerates)(implicit log: LoggingAdapter): ClosingFees = { // this is just to estimate the weight, it depends on size of the pubkey scripts - val dummyClosingTx = ClosingTx.createUnsignedTx(commitment.commitInput, localScriptPubkey, remoteScriptPubkey, commitment.localChannelParams.paysClosingFees, 0 sat, 0 sat, commitment.localCommit.spec) + val dummyClosingTx = ClosingTx.createUnsignedTx(commitment.commitInput(channelKeys), localScriptPubkey, remoteScriptPubkey, commitment.localChannelParams.paysClosingFees, 0 sat, 0 sat, commitment.localCommit.spec) val dummyPubkey = commitment.remoteFundingPubKey val dummySig = ChannelSpendSignature.IndividualSignature(Transactions.PlaceHolderSig) val closingWeight = dummyClosingTx.aggregateSigs(dummyPubkey, dummyPubkey, dummySig, dummySig).weight() @@ -689,7 +680,7 @@ object Helpers { feerates.computeFees(closingWeight) } - def firstClosingFee(commitment: FullCommitment, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feerates: FeeratesPerKw, onChainFeeConf: OnChainFeeConf)(implicit log: LoggingAdapter): ClosingFees = { + def firstClosingFee(channelKeys: ChannelKeys, commitment: FullCommitment, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feerates: FeeratesPerKw, onChainFeeConf: OnChainFeeConf)(implicit log: LoggingAdapter): ClosingFees = { val requestedFeerate = onChainFeeConf.getClosingFeerate(feerates) val preferredFeerate = commitment.commitmentFormat match { case DefaultCommitmentFormat => @@ -700,15 +691,15 @@ object Helpers { // NB: we choose a minimum fee that ensures the tx will easily propagate while allowing low fees since we can // always use CPFP to speed up confirmation if necessary. val closingFeerates = ClosingFeerates(preferredFeerate, preferredFeerate.min(ConfirmationPriority.Slow.getFeerate(feerates)), preferredFeerate * 2) - firstClosingFee(commitment, localScriptPubkey, remoteScriptPubkey, closingFeerates) + firstClosingFee(channelKeys, commitment, localScriptPubkey, remoteScriptPubkey, closingFeerates) } def nextClosingFee(localClosingFee: Satoshi, remoteClosingFee: Satoshi): Satoshi = ((localClosingFee + remoteClosingFee) / 4) * 2 def makeFirstClosingTx(channelKeys: ChannelKeys, commitment: FullCommitment, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feerates: FeeratesPerKw, onChainFeeConf: OnChainFeeConf, closingFeerates_opt: Option[ClosingFeerates])(implicit log: LoggingAdapter): (ClosingTx, ClosingSigned) = { val closingFees = closingFeerates_opt match { - case Some(closingFeerates) => firstClosingFee(commitment, localScriptPubkey, remoteScriptPubkey, closingFeerates) - case None => firstClosingFee(commitment, localScriptPubkey, remoteScriptPubkey, feerates, onChainFeeConf) + case Some(closingFeerates) => firstClosingFee(channelKeys, commitment, localScriptPubkey, remoteScriptPubkey, closingFeerates) + case None => firstClosingFee(channelKeys, commitment, localScriptPubkey, remoteScriptPubkey, feerates, onChainFeeConf) } makeClosingTx(channelKeys, commitment, localScriptPubkey, remoteScriptPubkey, closingFees) } @@ -716,7 +707,7 @@ object Helpers { def makeClosingTx(channelKeys: ChannelKeys, commitment: FullCommitment, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, closingFees: ClosingFees)(implicit log: LoggingAdapter): (ClosingTx, ClosingSigned) = { log.debug("making closing tx with closing fee={} and commitments:\n{}", closingFees.preferred, commitment.specs2String) val dustLimit = commitment.localCommitParams.dustLimit.max(commitment.remoteCommitParams.dustLimit) - val closingTx = ClosingTx.createUnsignedTx(commitment.commitInput, localScriptPubkey, remoteScriptPubkey, commitment.localChannelParams.paysClosingFees, dustLimit, closingFees.preferred, commitment.localCommit.spec) + val closingTx = ClosingTx.createUnsignedTx(commitment.commitInput(channelKeys), localScriptPubkey, remoteScriptPubkey, commitment.localChannelParams.paysClosingFees, dustLimit, closingFees.preferred, commitment.localCommit.spec) val localClosingSig = closingTx.sign(channelKeys.fundingKey(commitment.fundingTxIndex), commitment.remoteFundingPubKey).sig val closingSigned = ClosingSigned(commitment.channelId, closingFees.preferred, localClosingSig, TlvStream(ClosingSignedTlv.FeeRange(closingFees.min, closingFees.max))) log.debug(s"signed closing txid=${closingTx.tx.txid} with closing fee=${closingSigned.feeSatoshis}") @@ -742,8 +733,9 @@ object Helpers { /** We are the closer: we sign closing transactions for which we pay the fees. */ def makeSimpleClosingTx(currentBlockHeight: BlockHeight, channelKeys: ChannelKeys, commitment: FullCommitment, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feerate: FeeratePerKw): Either[ChannelException, (ClosingTxs, ClosingComplete)] = { // We must convert the feerate to a fee: we must build dummy transactions to compute their weight. + val commitInput = commitment.commitInput(channelKeys) val closingFee = { - val dummyClosingTxs = Transactions.makeSimpleClosingTxs(commitment.commitInput, commitment.localCommit.spec, SimpleClosingTxFee.PaidByUs(0 sat), currentBlockHeight.toLong, localScriptPubkey, remoteScriptPubkey) + val dummyClosingTxs = Transactions.makeSimpleClosingTxs(commitInput, commitment.localCommit.spec, SimpleClosingTxFee.PaidByUs(0 sat), currentBlockHeight.toLong, localScriptPubkey, remoteScriptPubkey) dummyClosingTxs.preferred_opt match { case Some(dummyTx) => val dummyPubkey = commitment.remoteFundingPubKey @@ -754,7 +746,7 @@ object Helpers { } } // Now that we know the fee we're ready to pay, we can create our closing transactions. - val closingTxs = Transactions.makeSimpleClosingTxs(commitment.commitInput, commitment.localCommit.spec, closingFee, currentBlockHeight.toLong, localScriptPubkey, remoteScriptPubkey) + val closingTxs = Transactions.makeSimpleClosingTxs(commitInput, commitment.localCommit.spec, closingFee, currentBlockHeight.toLong, localScriptPubkey, remoteScriptPubkey) closingTxs.preferred_opt match { case Some(closingTx) if closingTx.fee > 0.sat => () case _ => return Left(CannotGenerateClosingTx(commitment.channelId)) @@ -776,7 +768,7 @@ object Helpers { */ def signSimpleClosingTx(channelKeys: ChannelKeys, commitment: FullCommitment, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, closingComplete: ClosingComplete): Either[ChannelException, (ClosingTx, ClosingSig)] = { val closingFee = SimpleClosingTxFee.PaidByThem(closingComplete.fees) - val closingTxs = Transactions.makeSimpleClosingTxs(commitment.commitInput, commitment.localCommit.spec, closingFee, closingComplete.lockTime, localScriptPubkey, remoteScriptPubkey) + val closingTxs = Transactions.makeSimpleClosingTxs(commitment.commitInput(channelKeys), commitment.localCommit.spec, closingFee, closingComplete.lockTime, localScriptPubkey, remoteScriptPubkey) // If our output isn't dust, they must provide a signature for a transaction that includes it. // Note that we're the closee, so we look for signatures including the closee output. (closingTxs.localAndRemote_opt, closingTxs.localOnly_opt) match { @@ -1087,7 +1079,7 @@ object Helpers { val fundingKey = channelKeys.fundingKey(commitment.fundingTxIndex) val commitKeys = commitment.remoteKeys(channelKeys, remoteCommit.remotePerCommitmentPoint) val outputs = makeRemoteCommitTxOutputs(channelKeys, commitKeys, commitment, remoteCommit) - val mainTx_opt = claimMainOutput(commitment.channelParams, commitKeys, commitTx, feerates, onChainFeeConf, finalScriptPubKey) + val mainTx_opt = claimMainOutput(commitKeys, commitTx, commitment.localCommitParams.dustLimit, commitment.commitmentFormat, feerates, onChainFeeConf, finalScriptPubKey) val (incomingHtlcs, htlcSuccessTxs) = claimIncomingHtlcOutputs(commitKeys, commitTx, outputs, commitment, remoteCommit, finalScriptPubKey) val (outgoingHtlcs, htlcTimeoutTxs) = claimOutgoingHtlcOutputs(commitKeys, commitTx, outputs, commitment, remoteCommit, finalScriptPubKey) val anchorOutput_opt = ClaimRemoteAnchorTx.findInput(commitTx, fundingKey, commitKeys, commitment.commitmentFormat).toOption @@ -1116,14 +1108,14 @@ object Helpers { } /** Claim our main output from the remote commitment transaction, if available. */ - def claimMainOutput(params: ChannelParams, commitKeys: RemoteCommitmentKeys, commitTx: Transaction, feerates: FeeratesPerKw, onChainFeeConf: OnChainFeeConf, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): Option[ClaimRemoteCommitMainOutputTx] = { + def claimMainOutput(commitKeys: RemoteCommitmentKeys, commitTx: Transaction, dustLimit: Satoshi, commitmentFormat: CommitmentFormat, feerates: FeeratesPerKw, onChainFeeConf: OnChainFeeConf, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): Option[ClaimRemoteCommitMainOutputTx] = { val feerate = onChainFeeConf.getClosingFeerate(feerates) - params.commitmentFormat match { + commitmentFormat match { case DefaultCommitmentFormat => withTxGenerationLog("remote-main") { - ClaimP2WPKHOutputTx.createUnsignedTx(commitKeys, commitTx, params.localCommitParams.dustLimit, finalScriptPubKey, feerate, params.commitmentFormat) + ClaimP2WPKHOutputTx.createUnsignedTx(commitKeys, commitTx, dustLimit, finalScriptPubKey, feerate, commitmentFormat) } case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => withTxGenerationLog("remote-main-delayed") { - ClaimRemoteDelayedOutputTx.createUnsignedTx(commitKeys, commitTx, params.localCommitParams.dustLimit, finalScriptPubKey, feerate, params.commitmentFormat) + ClaimRemoteDelayedOutputTx.createUnsignedTx(commitKeys, commitTx, dustLimit, finalScriptPubKey, feerate, commitmentFormat) } } } @@ -1278,11 +1270,10 @@ object Helpers { * When a revoked commitment transaction spending the funding tx is detected, we build a set of transactions that * will punish our peer by stealing all their funds. */ - def claimCommitTxOutputs(params: ChannelParams, channelKeys: ChannelKeys, commitTx: Transaction, commitmentNumber: Long, remotePerCommitmentSecret: PrivateKey, db: ChannelsDb, feerates: FeeratesPerKw, onChainFeeConf: OnChainFeeConf, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): (RevokedCommitPublished, SecondStageTransactions) = { - import params._ + def claimCommitTxOutputs(channelParams: ChannelParams, channelKeys: ChannelKeys, commitTx: Transaction, commitmentNumber: Long, remotePerCommitmentSecret: PrivateKey, toSelfDelay: CltvExpiryDelta, commitmentFormat: CommitmentFormat, db: ChannelsDb, dustLimit: Satoshi, feerates: FeeratesPerKw, onChainFeeConf: OnChainFeeConf, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): (RevokedCommitPublished, SecondStageTransactions) = { log.warning("a revoked commit has been published with commitmentNumber={}", commitmentNumber) - val commitKeys = RemoteCommitmentKeys(params, channelKeys, remotePerCommitmentSecret.publicKey) + val commitKeys = RemoteCommitmentKeys(channelParams, channelKeys, remotePerCommitmentSecret.publicKey, commitmentFormat) val revocationKey = channelKeys.revocationKey(remotePerCommitmentSecret) val feerateMain = onChainFeeConf.getClosingFeerate(feerates) @@ -1292,23 +1283,23 @@ object Helpers { // First we will claim our main output right away. val mainTx_opt = commitmentFormat match { case DefaultCommitmentFormat => withTxGenerationLog("remote-main") { - ClaimP2WPKHOutputTx.createUnsignedTx(commitKeys, commitTx, localCommitParams.dustLimit, finalScriptPubKey, feerateMain, commitmentFormat) + ClaimP2WPKHOutputTx.createUnsignedTx(commitKeys, commitTx, dustLimit, finalScriptPubKey, feerateMain, commitmentFormat) } case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => withTxGenerationLog("remote-main-delayed") { - ClaimRemoteDelayedOutputTx.createUnsignedTx(commitKeys, commitTx, localCommitParams.dustLimit, finalScriptPubKey, feerateMain, commitmentFormat) + ClaimRemoteDelayedOutputTx.createUnsignedTx(commitKeys, commitTx, dustLimit, finalScriptPubKey, feerateMain, commitmentFormat) } } // Then we punish them by stealing their main output. val mainPenaltyTx_opt = withTxGenerationLog("main-penalty") { - MainPenaltyTx.createUnsignedTx(commitKeys, revocationKey, commitTx, localCommitParams.dustLimit, finalScriptPubKey, remoteCommitParams.toSelfDelay, feeratePenalty, commitmentFormat) + MainPenaltyTx.createUnsignedTx(commitKeys, revocationKey, commitTx, dustLimit, finalScriptPubKey, toSelfDelay, feeratePenalty, commitmentFormat) } // We retrieve the historical information needed to rebuild htlc scripts. - val htlcInfos = db.listHtlcInfos(channelId, commitmentNumber) + val htlcInfos = db.listHtlcInfos(channelParams.channelId, commitmentNumber) log.info("got {} htlcs for commitmentNumber={}", htlcInfos.size, commitmentNumber) // And finally we steal the htlc outputs. - val htlcPenaltyTxs = HtlcPenaltyTx.createUnsignedTxs(commitKeys, revocationKey, commitTx, htlcInfos, localCommitParams.dustLimit, finalScriptPubKey, feeratePenalty, commitmentFormat) + val htlcPenaltyTxs = HtlcPenaltyTx.createUnsignedTxs(commitKeys, revocationKey, commitTx, htlcInfos, dustLimit, finalScriptPubKey, feeratePenalty, commitmentFormat) .flatMap(htlcPenaltyTx => withTxGenerationLog("htlc-penalty")(htlcPenaltyTx)) val rvk = RevokedCommitPublished( @@ -1336,18 +1327,18 @@ object Helpers { * NB: when anchor outputs is used, htlc transactions can be aggregated in a single transaction if they share the same * lockTime (thanks to the use of sighash_single | sighash_anyonecanpay), so we may need to claim multiple outputs. */ - def claimHtlcTxOutputs(params: ChannelParams, channelKeys: ChannelKeys, remotePerCommitmentSecrets: ShaChain, revokedCommitPublished: RevokedCommitPublished, htlcTx: Transaction, feerates: FeeratesPerKw, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): (RevokedCommitPublished, ThirdStageTransactions) = { + def claimHtlcTxOutputs(channelParams: ChannelParams, channelKeys: ChannelKeys, remotePerCommitmentSecrets: ShaChain, toSelfDelay: CltvExpiryDelta, commitmentFormat: CommitmentFormat, revokedCommitPublished: RevokedCommitPublished, htlcTx: Transaction, dustLimit: Satoshi, feerates: FeeratesPerKw, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): (RevokedCommitPublished, ThirdStageTransactions) = { // We published HTLC-penalty transactions for every HTLC output: this transaction may be ours, or it may be one // of their HTLC transactions that confirmed before our HTLC-penalty transaction. If it is spending an HTLC // output, we assume that it's an HTLC transaction published by our peer and try to create penalty transactions // that spend it, which will automatically be skipped if this was instead one of our HTLC-penalty transactions. val spendsHtlcOutput = htlcTx.txIn.exists(txIn => revokedCommitPublished.htlcOutputs.contains(txIn.outPoint)) if (spendsHtlcOutput) { - getRemotePerCommitmentSecret(params, channelKeys, remotePerCommitmentSecrets, revokedCommitPublished.commitTx).map { + getRemotePerCommitmentSecret(channelParams, channelKeys, remotePerCommitmentSecrets, revokedCommitPublished.commitTx).map { case (_, remotePerCommitmentSecret) => - val commitmentKeys = RemoteCommitmentKeys(params, channelKeys, remotePerCommitmentSecret.publicKey) + val commitmentKeys = RemoteCommitmentKeys(channelParams, channelKeys, remotePerCommitmentSecret.publicKey, commitmentFormat) val revocationKey = channelKeys.revocationKey(remotePerCommitmentSecret) - val penaltyTxs = claimHtlcTxOutputs(params, commitmentKeys, revocationKey, htlcTx, feerates, finalScriptPubKey) + val penaltyTxs = claimHtlcTxOutputs(commitmentKeys, revocationKey, toSelfDelay, commitmentFormat, htlcTx, dustLimit, feerates, finalScriptPubKey) val revokedCommitPublished1 = revokedCommitPublished.copy(htlcDelayedOutputs = revokedCommitPublished.htlcDelayedOutputs ++ penaltyTxs.map(_.input.outPoint)) val txs = ThirdStageTransactions(penaltyTxs) (revokedCommitPublished1, txs) @@ -1357,12 +1348,10 @@ object Helpers { } } - private def claimHtlcTxOutputs(params: ChannelParams, commitmentKeys: RemoteCommitmentKeys, revocationKey: PrivateKey, htlcTx: Transaction, feerates: FeeratesPerKw, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): Seq[ClaimHtlcDelayedOutputPenaltyTx] = { + private def claimHtlcTxOutputs(commitmentKeys: RemoteCommitmentKeys, revocationKey: PrivateKey, toSelfDelay: CltvExpiryDelta, commitmentFormat: CommitmentFormat, htlcTx: Transaction, dustLimit: Satoshi, feerates: FeeratesPerKw, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): Seq[ClaimHtlcDelayedOutputPenaltyTx] = { // We need to use a high fee when spending HTLC txs because after a delay they can also be spent by the counterparty. val feeratePenalty = feerates.fastest - val dustLimit = params.localCommitParams.dustLimit - val toSelfDelay = params.remoteCommitParams.toSelfDelay - ClaimHtlcDelayedOutputPenaltyTx.createUnsignedTxs(commitmentKeys, revocationKey, htlcTx, dustLimit, toSelfDelay, finalScriptPubKey, feeratePenalty, params.commitmentFormat).flatMap(penaltyTx => { + ClaimHtlcDelayedOutputPenaltyTx.createUnsignedTxs(commitmentKeys, revocationKey, htlcTx, dustLimit, toSelfDelay, finalScriptPubKey, feeratePenalty, commitmentFormat).flatMap(penaltyTx => { withTxGenerationLog("htlc-delayed-penalty")(penaltyTx) }) } @@ -1370,11 +1359,11 @@ object Helpers { /** * Claim the outputs of all 2nd-stage HTLC transactions that have been confirmed. */ - def claimHtlcTxsOutputs(params: ChannelParams, channelKeys: ChannelKeys, remotePerCommitmentSecret: PrivateKey, revokedCommitPublished: RevokedCommitPublished, feerates: FeeratesPerKw, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): ThirdStageTransactions = { - val commitmentKeys = RemoteCommitmentKeys(params, channelKeys, remotePerCommitmentSecret.publicKey) + def claimHtlcTxsOutputs(channelParams: ChannelParams, channelKeys: ChannelKeys, remotePerCommitmentSecret: PrivateKey, toSelfDelay: CltvExpiryDelta, commitmentFormat: CommitmentFormat, revokedCommitPublished: RevokedCommitPublished, dustLimit: Satoshi, feerates: FeeratesPerKw, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): ThirdStageTransactions = { + val commitmentKeys = RemoteCommitmentKeys(channelParams, channelKeys, remotePerCommitmentSecret.publicKey, commitmentFormat) val revocationKey = channelKeys.revocationKey(remotePerCommitmentSecret) val confirmedHtlcTxs = revokedCommitPublished.htlcOutputs.flatMap(htlcOutput => revokedCommitPublished.irrevocablySpent.get(htlcOutput)) - val penaltyTxs = confirmedHtlcTxs.flatMap(htlcTx => claimHtlcTxOutputs(params, commitmentKeys, revocationKey, htlcTx, feerates, finalScriptPubKey)) + val penaltyTxs = confirmedHtlcTxs.flatMap(htlcTx => claimHtlcTxOutputs(commitmentKeys, revocationKey, toSelfDelay, commitmentFormat, htlcTx, dustLimit, feerates, finalScriptPubKey)) ThirdStageTransactions(penaltyTxs.toSeq) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala index 86ab8a8cdc..6a3dd1ec71 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala @@ -396,9 +396,13 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall case Some(c: Closing.RevokedClose) => Closing.RevokedClose.getRemotePerCommitmentSecret(closing.commitments.channelParams, channelKeys, closing.commitments.remotePerCommitmentSecrets, c.revokedCommitPublished.commitTx).foreach { case (commitmentNumber, remotePerCommitmentSecret) => - val (_, secondStageTransactions) = Closing.RevokedClose.claimCommitTxOutputs(closing.commitments.channelParams, channelKeys, c.revokedCommitPublished.commitTx, commitmentNumber, remotePerCommitmentSecret, nodeParams.db.channels, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, closing.finalScriptPubKey) + // TODO: once we allow changing the commitment format or to_self_delay during a splice, those values may be incorrect. + val toSelfDelay = closing.commitments.latest.remoteCommitParams.toSelfDelay + val commitmentFormat = closing.commitments.latest.commitmentFormat + val dustLimit = closing.commitments.latest.localCommitParams.dustLimit + val (_, secondStageTransactions) = Closing.RevokedClose.claimCommitTxOutputs(closing.commitments.channelParams, channelKeys, c.revokedCommitPublished.commitTx, commitmentNumber, remotePerCommitmentSecret, toSelfDelay, commitmentFormat, nodeParams.db.channels, dustLimit, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, closing.finalScriptPubKey) doPublish(c.revokedCommitPublished, secondStageTransactions) - val thirdStageTransactions = Closing.RevokedClose.claimHtlcTxsOutputs(closing.commitments.channelParams, channelKeys, remotePerCommitmentSecret, c.revokedCommitPublished, nodeParams.currentBitcoinCoreFeerates, closing.finalScriptPubKey) + val thirdStageTransactions = Closing.RevokedClose.claimHtlcTxsOutputs(closing.commitments.channelParams, channelKeys, remotePerCommitmentSecret, toSelfDelay, commitmentFormat, c.revokedCommitPublished, dustLimit, nodeParams.currentBitcoinCoreFeerates, closing.finalScriptPubKey) doPublish(c.revokedCommitPublished, thirdStageTransactions) } case None => @@ -423,7 +427,11 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall closing.revokedCommitPublished.foreach(rvk => { Closing.RevokedClose.getRemotePerCommitmentSecret(closing.commitments.channelParams, channelKeys, closing.commitments.remotePerCommitmentSecrets, rvk.commitTx).foreach { case (commitmentNumber, remotePerCommitmentSecret) => - val (_, secondStageTransactions) = Closing.RevokedClose.claimCommitTxOutputs(closing.commitments.channelParams, channelKeys, rvk.commitTx, commitmentNumber, remotePerCommitmentSecret, nodeParams.db.channels, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, closing.finalScriptPubKey) + // TODO: once we allow changing the commitment format or to_self_delay during a splice, those values may be incorrect. + val toSelfDelay = closing.commitments.latest.remoteCommitParams.toSelfDelay + val commitmentFormat = closing.commitments.latest.commitmentFormat + val dustLimit = closing.commitments.latest.localCommitParams.dustLimit + val (_, secondStageTransactions) = Closing.RevokedClose.claimCommitTxOutputs(closing.commitments.channelParams, channelKeys, rvk.commitTx, commitmentNumber, remotePerCommitmentSecret, toSelfDelay, commitmentFormat, nodeParams.db.channels, dustLimit, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, closing.finalScriptPubKey) doPublish(rvk, secondStageTransactions) } }) @@ -620,11 +628,11 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall log.debug("sending a new sig, spec:\n{}", commitments1.latest.specs2String) val nextRemoteCommit = commitments1.latest.nextRemoteCommit_opt.get.commit val nextCommitNumber = nextRemoteCommit.index - // we persist htlc data in order to be able to claim htlc outputs in case a revoked tx is published by our - // counterparty, so only htlcs above remote's dust_limit matter - val trimmedHtlcs = Transactions.trimOfferedHtlcs(d.commitments.channelParams.remoteCommitParams.dustLimit, nextRemoteCommit.spec, commitments1.channelParams.commitmentFormat) ++ - Transactions.trimReceivedHtlcs(commitments1.channelParams.remoteCommitParams.dustLimit, nextRemoteCommit.spec, commitments1.channelParams.commitmentFormat) - trimmedHtlcs.map(_.add).foreach { htlc => + // We persist htlc data in order to be able to claim htlc outputs in case a revoked tx is published by our + // counterparty, so only htlcs above remote's dust_limit matter. + val trimmedOfferedHtlcs = d.commitments.active.flatMap(c => Transactions.trimOfferedHtlcs(c.remoteCommitParams.dustLimit, nextRemoteCommit.spec, c.commitmentFormat)).map(_.add).toSet + val trimmedReceivedHtlcs = d.commitments.active.flatMap(c => Transactions.trimReceivedHtlcs(c.remoteCommitParams.dustLimit, nextRemoteCommit.spec, c.commitmentFormat)).map(_.add).toSet + (trimmedOfferedHtlcs ++ trimmedReceivedHtlcs).foreach { htlc => log.debug(s"adding paymentHash=${htlc.paymentHash} cltvExpiry=${htlc.cltvExpiry} to htlcs db for commitNumber=$nextCommitNumber") nodeParams.db.channels.addHtlcInfo(d.channelId, nextCommitNumber, htlc.paymentHash, htlc.cltvExpiry) } @@ -1082,8 +1090,9 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall stay() using d.copy(spliceStatus = SpliceStatus.SpliceAborted) sending TxAbort(d.channelId, InvalidSpliceWithUnconfirmedTx(d.channelId, d.commitments.latest.fundingTxId).getMessage) } else { val parentCommitment = d.commitments.latest.commitment + val commitmentFormat = parentCommitment.commitmentFormat val localFundingPubKey = channelKeys.fundingKey(parentCommitment.fundingTxIndex + 1).publicKey - val fundingScript = Funding.makeFundingScript(localFundingPubKey, msg.fundingPubKey, d.commitments.channelParams.commitmentFormat).pubkeyScript + val fundingScript = Transactions.makeFundingScript(localFundingPubKey, msg.fundingPubKey, commitmentFormat).pubkeyScript LiquidityAds.validateRequest(nodeParams.privateKey, d.channelId, fundingScript, msg.feerate, isChannelCreation = false, msg.requestFunding_opt, nodeParams.liquidityAdsConfig.rates_opt, msg.useFeeCredit_opt) match { case Left(t) => log.warning("rejecting splice request with invalid liquidity ads: {}", t.getMessage) @@ -1103,11 +1112,12 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall isInitiator = false, localContribution = spliceAck.fundingContribution, remoteContribution = msg.fundingContribution, - sharedInput_opt = Some(Multisig2of2Input(parentCommitment)), + sharedInput_opt = Some(Multisig2of2Input(channelKeys, parentCommitment)), remoteFundingPubKey = msg.fundingPubKey, localOutputs = Nil, + commitmentFormat = commitmentFormat, lockTime = msg.lockTime, - dustLimit = d.commitments.channelParams.localCommitParams.dustLimit.max(d.commitments.channelParams.remoteCommitParams.dustLimit), + dustLimit = parentCommitment.localCommitParams.dustLimit.max(parentCommitment.remoteCommitParams.dustLimit), targetFeerate = msg.feerate, requireConfirmedInputs = RequireConfirmedInputs(forLocal = msg.requireConfirmedInputs, forRemote = spliceAck.requireConfirmedInputs) ) @@ -1116,6 +1126,8 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall sessionId, nodeParams, fundingParams, channelParams = d.commitments.channelParams, + localCommitParams = parentCommitment.localCommitParams, + remoteCommitParams = parentCommitment.remoteCommitParams, channelKeys = channelKeys, purpose = InteractiveTxBuilder.SpliceTx(parentCommitment, d.commitments.changes), localPushAmount = spliceAck.pushAmount, remotePushAmount = msg.pushAmount, @@ -1142,20 +1154,22 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall case SpliceStatus.SpliceRequested(cmd, spliceInit) => log.info("our peer accepted our splice request and will contribute {} to the funding transaction", msg.fundingContribution) val parentCommitment = d.commitments.latest.commitment + val commitmentFormat = parentCommitment.commitmentFormat val fundingParams = InteractiveTxParams( channelId = d.channelId, isInitiator = true, localContribution = spliceInit.fundingContribution, remoteContribution = msg.fundingContribution, - sharedInput_opt = Some(Multisig2of2Input(parentCommitment)), + sharedInput_opt = Some(Multisig2of2Input(channelKeys, parentCommitment)), remoteFundingPubKey = msg.fundingPubKey, localOutputs = cmd.spliceOutputs, + commitmentFormat = commitmentFormat, lockTime = spliceInit.lockTime, - dustLimit = d.commitments.channelParams.localCommitParams.dustLimit.max(d.commitments.channelParams.remoteCommitParams.dustLimit), + dustLimit = parentCommitment.localCommitParams.dustLimit.max(parentCommitment.remoteCommitParams.dustLimit), targetFeerate = spliceInit.feerate, requireConfirmedInputs = RequireConfirmedInputs(forLocal = msg.requireConfirmedInputs, forRemote = spliceInit.requireConfirmedInputs) ) - val fundingScript = Funding.makeFundingScript(spliceInit.fundingPubKey, msg.fundingPubKey, d.commitments.channelParams.commitmentFormat).pubkeyScript + val fundingScript = Transactions.makeFundingScript(spliceInit.fundingPubKey, msg.fundingPubKey, commitmentFormat).pubkeyScript LiquidityAds.validateRemoteFunding(spliceInit.requestFunding_opt, remoteNodeId, d.channelId, fundingScript, msg.fundingContribution, spliceInit.feerate, isChannelCreation = false, msg.willFund_opt) match { case Left(t) => log.info("rejecting splice attempt: invalid liquidity ads response ({})", t.getMessage) @@ -1167,6 +1181,8 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall sessionId, nodeParams, fundingParams, channelParams = d.commitments.channelParams, + localCommitParams = parentCommitment.localCommitParams, + remoteCommitParams = parentCommitment.remoteCommitParams, channelKeys = channelKeys, purpose = InteractiveTxBuilder.SpliceTx(parentCommitment, d.commitments.changes), localPushAmount = cmd.pushAmount, remotePushAmount = msg.pushAmount, @@ -1207,7 +1223,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall log.info("rejecting rbf attempt: last attempt was less than {} blocks ago", nodeParams.channelConf.remoteRbfLimits.attemptDeltaBlocks) stay() using d.copy(spliceStatus = SpliceStatus.SpliceAborted) sending TxAbort(d.channelId, InvalidRbfAttemptTooSoon(d.channelId, rbf.latestFundingTx.createdAt, rbf.latestFundingTx.createdAt + nodeParams.channelConf.remoteRbfLimits.attemptDeltaBlocks).getMessage) case Right(rbf) => - val fundingScript = d.commitments.latest.commitInput.txOut.publicKeyScript + val fundingScript = d.commitments.latest.commitInput(channelKeys).txOut.publicKeyScript LiquidityAds.validateRequest(nodeParams.privateKey, d.channelId, fundingScript, msg.feerate, isChannelCreation = false, msg.requestFunding_opt, nodeParams.liquidityAdsConfig.rates_opt, feeCreditUsed_opt = None) match { case Left(t) => log.warning("rejecting rbf request with invalid liquidity ads: {}", t.getMessage) @@ -1223,9 +1239,10 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall isInitiator = false, localContribution = fundingContribution, remoteContribution = msg.fundingContribution, - sharedInput_opt = Some(Multisig2of2Input(rbf.parentCommitment)), + sharedInput_opt = Some(Multisig2of2Input(channelKeys, rbf.parentCommitment)), remoteFundingPubKey = rbf.latestFundingTx.fundingParams.remoteFundingPubKey, localOutputs = rbf.latestFundingTx.fundingParams.localOutputs, + commitmentFormat = rbf.latestFundingTx.fundingParams.commitmentFormat, lockTime = msg.lockTime, dustLimit = rbf.latestFundingTx.fundingParams.dustLimit, targetFeerate = msg.feerate, @@ -1236,6 +1253,8 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall sessionId, nodeParams, fundingParams, channelParams = d.commitments.channelParams, + localCommitParams = rbf.parentCommitment.localCommitParams, + remoteCommitParams = rbf.parentCommitment.remoteCommitParams, channelKeys = channelKeys, purpose = rbf, localPushAmount = 0 msat, remotePushAmount = 0 msat, @@ -1264,7 +1283,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall case SpliceStatus.RbfRequested(cmd, txInitRbf) => getSpliceRbfContext(Some(cmd), d) match { case Right(rbf) => - val fundingScript = d.commitments.latest.commitInput.txOut.publicKeyScript + val fundingScript = d.commitments.latest.commitInput(channelKeys).txOut.publicKeyScript LiquidityAds.validateRemoteFunding(cmd.requestFunding_opt, remoteNodeId, d.channelId, fundingScript, msg.fundingContribution, txInitRbf.feerate, isChannelCreation = false, msg.willFund_opt) match { case Left(t) => log.info("rejecting rbf attempt: invalid liquidity ads response ({})", t.getMessage) @@ -1277,9 +1296,10 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall isInitiator = true, localContribution = txInitRbf.fundingContribution, remoteContribution = msg.fundingContribution, - sharedInput_opt = Some(Multisig2of2Input(rbf.parentCommitment)), + sharedInput_opt = Some(Multisig2of2Input(channelKeys, rbf.parentCommitment)), remoteFundingPubKey = rbf.latestFundingTx.fundingParams.remoteFundingPubKey, localOutputs = rbf.latestFundingTx.fundingParams.localOutputs, + commitmentFormat = rbf.latestFundingTx.fundingParams.commitmentFormat, lockTime = txInitRbf.lockTime, dustLimit = rbf.latestFundingTx.fundingParams.dustLimit, targetFeerate = txInitRbf.feerate, @@ -1290,6 +1310,8 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall sessionId, nodeParams, fundingParams, channelParams = d.commitments.channelParams, + localCommitParams = rbf.parentCommitment.localCommitParams, + remoteCommitParams = rbf.parentCommitment.remoteCommitParams, channelKeys = channelKeys, purpose = rbf, localPushAmount = 0 msat, remotePushAmount = 0 msat, @@ -1360,7 +1382,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall cmd_opt.foreach(cmd => cmd.replyTo ! RES_SPLICE(fundingTxIndex = signingSession.fundingTxIndex, signingSession.fundingTx.txId, signingSession.fundingParams.fundingAmount, signingSession.localCommit.fold(_.spec, _.spec).toLocal)) remoteCommitSig_opt.foreach(self ! _) liquidityPurchase_opt.collect { - case purchase if !signingSession.fundingParams.isInitiator => peer ! LiquidityPurchaseSigned(d.channelId, signingSession.fundingTx.txId, signingSession.fundingTxIndex, d.commitments.channelParams.remoteCommitParams.htlcMinimum, purchase) + case purchase if !signingSession.fundingParams.isInitiator => peer ! LiquidityPurchaseSigned(d.channelId, signingSession.fundingTx.txId, signingSession.fundingTxIndex, signingSession.remoteCommitParams.htlcMinimum, purchase) } val d1 = d.copy(spliceStatus = SpliceStatus.SpliceWaitingForSigs(signingSession)) stay() using d1 storing() sending commitSig @@ -1613,11 +1635,11 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall log.debug("sending a new sig, spec:\n{}", commitments1.latest.specs2String) val nextRemoteCommit = commitments1.latest.nextRemoteCommit_opt.get.commit val nextCommitNumber = nextRemoteCommit.index - // we persist htlc data in order to be able to claim htlc outputs in case a revoked tx is published by our - // counterparty, so only htlcs above remote's dust_limit matter - val trimmedHtlcs = Transactions.trimOfferedHtlcs(d.commitments.channelParams.remoteCommitParams.dustLimit, nextRemoteCommit.spec, d.commitments.channelParams.commitmentFormat) ++ - Transactions.trimReceivedHtlcs(d.commitments.channelParams.remoteCommitParams.dustLimit, nextRemoteCommit.spec, d.commitments.channelParams.commitmentFormat) - trimmedHtlcs.map(_.add).foreach { htlc => + // We persist htlc data in order to be able to claim htlc outputs in case a revoked tx is published by our + // counterparty, so only htlcs above remote's dust_limit matter. + val trimmedOfferedHtlcs = d.commitments.active.flatMap(c => Transactions.trimOfferedHtlcs(c.remoteCommitParams.dustLimit, nextRemoteCommit.spec, c.commitmentFormat)).map(_.add).toSet + val trimmedReceivedHtlcs = d.commitments.active.flatMap(c => Transactions.trimReceivedHtlcs(c.remoteCommitParams.dustLimit, nextRemoteCommit.spec, c.commitmentFormat)).map(_.add).toSet + (trimmedOfferedHtlcs ++ trimmedReceivedHtlcs).foreach { htlc => log.debug(s"adding paymentHash=${htlc.paymentHash} cltvExpiry=${htlc.cltvExpiry} to htlcs db for commitNumber=$nextCommitNumber") nodeParams.db.channels.addHtlcInfo(d.channelId, nextCommitNumber, htlc.paymentHash, htlc.cltvExpiry) } @@ -1774,7 +1796,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall case Some(ClosingSignedTlv.FeeRange(minFee, maxFee)) if !d.commitments.localChannelParams.paysClosingFees => // if we are not paying the closing fees and they proposed a fee range, we pick a value in that range and they should accept it without further negotiation // we don't care much about the closing fee since they're paying it (not us) and we can use CPFP if we want to speed up confirmation - val localClosingFees = MutualClose.firstClosingFee(d.commitments.latest, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.currentFeeratesForFundingClosing, nodeParams.onChainFeeConf) + val localClosingFees = MutualClose.firstClosingFee(channelKeys, d.commitments.latest, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.currentFeeratesForFundingClosing, nodeParams.onChainFeeConf) if (maxFee < localClosingFees.min) { log.warning("their highest closing fee is below our minimum fee: {} < {}", maxFee, localClosingFees.min) stay() sending Warning(d.channelId, s"closing fee range must not be below ${localClosingFees.min}") @@ -1801,7 +1823,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall val lastLocalClosingFee_opt = lastLocalClosingSigned_opt.map(_.localClosingSigned.feeSatoshis) val (closingTx, closingSigned) = { // if we are not the channel initiator and we were waiting for them to send their first closing_signed, we don't have a lastLocalClosingFee, so we compute a firstClosingFee - val localClosingFees = MutualClose.firstClosingFee(d.commitments.latest, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.currentFeeratesForFundingClosing, nodeParams.onChainFeeConf) + val localClosingFees = MutualClose.firstClosingFee(channelKeys, d.commitments.latest, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.currentFeeratesForFundingClosing, nodeParams.onChainFeeConf) val nextPreferredFee = MutualClose.nextClosingFee(lastLocalClosingFee_opt.getOrElse(localClosingFees.preferred), remoteClosingFee) MutualClose.makeClosingTx(channelKeys, d.commitments.latest, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, localClosingFees.copy(preferred = nextPreferredFee)) } @@ -2148,14 +2170,18 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall revokedCommitPublished = d.revokedCommitPublished.map(rvk => { // If the tx is one of our peer's HTLC txs, they were able to claim the output before us. // In that case, we immediately publish a penalty transaction spending their HTLC tx to steal their funds. - val (rvk1, penaltyTxs) = Closing.RevokedClose.claimHtlcTxOutputs(d.commitments.channelParams, channelKeys, d.commitments.remotePerCommitmentSecrets, rvk, tx, nodeParams.currentBitcoinCoreFeerates, d.finalScriptPubKey) + // TODO: once we allow changing the commitment format or to_self_delay during a splice, those values may be incorrect. + val toSelfDelay = d.commitments.latest.remoteCommitParams.toSelfDelay + val commitmentFormat = d.commitments.latest.commitmentFormat + val dustLimit = d.commitments.latest.localCommitParams.dustLimit + val (rvk1, penaltyTxs) = Closing.RevokedClose.claimHtlcTxOutputs(d.commitments.channelParams, channelKeys, d.commitments.remotePerCommitmentSecrets, toSelfDelay, commitmentFormat, rvk, tx, dustLimit, nodeParams.currentBitcoinCoreFeerates, d.finalScriptPubKey) doPublish(rvk1, penaltyTxs) Closing.updateIrrevocablySpent(rvk1, tx) }) ) // if the local commitment tx just got confirmed, let's send an event telling when we will get the main output refund if (d1.localCommitPublished.exists(_.commitTx.txid == tx.txid)) { - context.system.eventStream.publish(LocalCommitConfirmed(self, remoteNodeId, d.channelId, blockHeight + d.commitments.channelParams.localCommitParams.toSelfDelay.toInt)) + context.system.eventStream.publish(LocalCommitConfirmed(self, remoteNodeId, d.channelId, blockHeight + d.commitments.latest.localCommitParams.toSelfDelay.toInt)) } // if the local or remote commitment tx just got confirmed, we abandon anchor transactions that were created based // on the other commitment: they will never confirm so we must free their wallet inputs. @@ -2239,7 +2265,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall case Event(c: CMD_CLOSE, d: DATA_CLOSING) => handleCommandError(ClosingAlreadyInProgress(d.channelId), c) case Event(c: CMD_BUMP_FORCE_CLOSE_FEE, d: DATA_CLOSING) => - d.commitments.channelParams.commitmentFormat match { + d.commitments.latest.commitmentFormat match { case commitmentFormat: Transactions.AnchorOutputsCommitmentFormat => val commitment = d.commitments.latest val fundingKey = channelKeys.fundingKey(commitment.fundingTxIndex) @@ -2443,7 +2469,8 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall case Some(fundingTxId) if fundingTxId == d.signingSession.fundingTx.txId && channelReestablish.nextLocalCommitmentNumber == 0 => // They haven't received our commit_sig: we retransmit it, and will send our tx_signatures once we've received // their commit_sig or their tx_signatures (depending on who must send tx_signatures first). - val commitSig = d.signingSession.remoteCommit.sign(d.channelParams, channelKeys, d.signingSession.fundingTxIndex, d.signingSession.fundingParams.remoteFundingPubKey, d.signingSession.commitInput) + val fundingParams = d.signingSession.fundingParams + val commitSig = d.signingSession.remoteCommit.sign(d.channelParams, d.signingSession.remoteCommitParams, channelKeys, d.signingSession.fundingTxIndex, fundingParams.remoteFundingPubKey, d.signingSession.commitInput(channelKeys), fundingParams.commitmentFormat) goto(WAIT_FOR_DUAL_FUNDING_SIGNED) sending commitSig case _ => goto(WAIT_FOR_DUAL_FUNDING_SIGNED) } @@ -2456,7 +2483,8 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall if (channelReestablish.nextLocalCommitmentNumber == 0) { // They haven't received our commit_sig: we retransmit it. // We're also waiting for signatures from them, and will send our tx_signatures once we receive them. - val commitSig = signingSession.remoteCommit.sign(d.commitments.channelParams, channelKeys, signingSession.fundingTxIndex, signingSession.fundingParams.remoteFundingPubKey, signingSession.commitInput) + val fundingParams = signingSession.fundingParams + val commitSig = signingSession.remoteCommit.sign(d.commitments.channelParams, signingSession.remoteCommitParams, channelKeys, signingSession.fundingTxIndex, signingSession.fundingParams.remoteFundingPubKey, signingSession.commitInput(channelKeys), fundingParams.commitmentFormat) goto(WAIT_FOR_DUAL_FUNDING_CONFIRMED) sending commitSig } else { // They have already received our commit_sig, but we were waiting for them to send either commit_sig or @@ -2467,7 +2495,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall // We've already received their commit_sig and sent our tx_signatures. We retransmit our tx_signatures // and our commit_sig if they haven't received it already. if (channelReestablish.nextLocalCommitmentNumber == 0) { - val commitSig = d.commitments.latest.remoteCommit.sign(d.commitments.channelParams, channelKeys, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput) + val commitSig = d.commitments.latest.remoteCommit.sign(d.commitments.channelParams, d.commitments.latest.remoteCommitParams, channelKeys, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput(channelKeys), d.commitments.latest.commitmentFormat) goto(WAIT_FOR_DUAL_FUNDING_CONFIRMED) sending Seq(commitSig, d.latestFundingTx.sharedTx.localSigs) } else { goto(WAIT_FOR_DUAL_FUNDING_CONFIRMED) sending d.latestFundingTx.sharedTx.localSigs @@ -2495,7 +2523,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall d.commitments.latest.localFundingStatus.localSigs_opt match { case Some(txSigs) if channelReestablish.nextLocalCommitmentNumber == 0 => log.info("re-sending commit_sig and tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId) - val commitSig = d.commitments.latest.remoteCommit.sign(d.commitments.channelParams, channelKeys, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput) + val commitSig = d.commitments.latest.remoteCommit.sign(d.commitments.channelParams, d.commitments.latest.remoteCommitParams, channelKeys, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput(channelKeys), d.commitments.latest.commitmentFormat) goto(WAIT_FOR_DUAL_FUNDING_READY) sending Seq(commitSig, txSigs, channelReady) case Some(txSigs) => log.info("re-sending tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId) @@ -2556,7 +2584,8 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall // They haven't received our commit_sig: we retransmit it. // We're also waiting for signatures from them, and will send our tx_signatures once we receive them. log.info("re-sending commit_sig for splice attempt with fundingTxIndex={} fundingTxId={}", signingSession.fundingTxIndex, signingSession.fundingTx.txId) - val commitSig = signingSession.remoteCommit.sign(d.commitments.channelParams, channelKeys, signingSession.fundingTxIndex, signingSession.fundingParams.remoteFundingPubKey, signingSession.commitInput) + val fundingParams = signingSession.fundingParams + val commitSig = signingSession.remoteCommit.sign(d.commitments.channelParams, signingSession.remoteCommitParams, channelKeys, signingSession.fundingTxIndex, fundingParams.remoteFundingPubKey, signingSession.commitInput(channelKeys), fundingParams.commitmentFormat) sendQueue = sendQueue :+ commitSig } d.spliceStatus @@ -2567,7 +2596,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall // tx_signatures and our commit_sig if they haven't received it already. if (channelReestablish.nextLocalCommitmentNumber == d.commitments.remoteCommitIndex) { log.info("re-sending commit_sig and tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId) - val commitSig = d.commitments.latest.remoteCommit.sign(d.commitments.channelParams, channelKeys, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput) + val commitSig = d.commitments.latest.remoteCommit.sign(d.commitments.channelParams, d.commitments.latest.remoteCommitParams, channelKeys, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput(channelKeys), d.commitments.latest.commitmentFormat) sendQueue = sendQueue :+ commitSig :+ dfu.sharedTx.localSigs } else { log.info("re-sending tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId) @@ -2656,7 +2685,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall val shutdownInProgress = d.localShutdown.nonEmpty || d.remoteShutdown.nonEmpty if (d.commitments.localChannelParams.paysCommitTxFees && !shutdownInProgress) { val currentFeeratePerKw = d.commitments.latest.localCommit.spec.commitTxFeerate - val networkFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, d.commitments.channelParams.commitmentFormat, d.commitments.latest.capacity) + val networkFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, d.commitments.latest.commitmentFormat, d.commitments.latest.capacity) if (nodeParams.onChainFeeConf.shouldUpdateFee(currentFeeratePerKw, networkFeeratePerKw)) { self ! CMD_UPDATE_FEE(networkFeeratePerKw, commit = true) } @@ -2862,7 +2891,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall // slightly before us. In that case, the WatchConfirmed may trigger first, and it would be inefficient to let the // WatchPublished override our funding status: it will make us set a new WatchConfirmed that will instantly // trigger and rewrite the funding status again. - val alreadyConfirmed = d.commitments.active.map(_.localFundingStatus).collect { case f: LocalFundingStatus.ConfirmedFundingTx => f.tx }.exists(_.txid == w.tx.txid) + val alreadyConfirmed = d.commitments.active.exists(c => c.fundingTxId == w.tx.txid && c.localFundingStatus.isInstanceOf[LocalFundingStatus.ConfirmedFundingTx]) if (alreadyConfirmed) { stay() } else { @@ -3122,11 +3151,11 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall private def handleCurrentFeerate(c: CurrentFeerates, d: ChannelDataWithCommitments) = { val commitments = d.commitments.latest - val networkFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, d.commitments.channelParams.commitmentFormat, commitments.capacity) + val networkFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, d.commitments.latest.commitmentFormat, commitments.capacity) val currentFeeratePerKw = commitments.localCommit.spec.commitTxFeerate val shouldUpdateFee = d.commitments.localChannelParams.paysCommitTxFees && nodeParams.onChainFeeConf.shouldUpdateFee(currentFeeratePerKw, networkFeeratePerKw) val shouldClose = !d.commitments.localChannelParams.paysCommitTxFees && - nodeParams.onChainFeeConf.feerateToleranceFor(d.commitments.remoteNodeId).isProposedFeerateTooLow(d.commitments.channelParams.commitmentFormat, networkFeeratePerKw, currentFeeratePerKw) && + nodeParams.onChainFeeConf.feerateToleranceFor(d.commitments.remoteNodeId).isProposedFeerateTooLow(d.commitments.latest.commitmentFormat, networkFeeratePerKw, currentFeeratePerKw) && d.commitments.hasPendingOrProposedHtlcs // we close only if we have HTLCs potentially at risk if (shouldUpdateFee) { self ! CMD_UPDATE_FEE(networkFeeratePerKw, commit = true) @@ -3147,11 +3176,11 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall */ private def handleCurrentFeerateDisconnected(c: CurrentFeerates, d: ChannelDataWithCommitments) = { val commitments = d.commitments.latest - val networkFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, d.commitments.channelParams.commitmentFormat, commitments.capacity) + val networkFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, d.commitments.latest.commitmentFormat, commitments.capacity) val currentFeeratePerKw = commitments.localCommit.spec.commitTxFeerate // if the network fees are too high we risk to not be able to confirm our current commitment val shouldClose = networkFeeratePerKw > currentFeeratePerKw && - nodeParams.onChainFeeConf.feerateToleranceFor(d.commitments.remoteNodeId).isProposedFeerateTooLow(d.commitments.channelParams.commitmentFormat, networkFeeratePerKw, currentFeeratePerKw) && + nodeParams.onChainFeeConf.feerateToleranceFor(d.commitments.remoteNodeId).isProposedFeerateTooLow(d.commitments.latest.commitmentFormat, networkFeeratePerKw, currentFeeratePerKw) && d.commitments.hasPendingOrProposedHtlcs // we close only if we have HTLCs potentially at risk if (shouldClose) { if (nodeParams.onChainFeeConf.closeOnOfflineMismatch) { @@ -3340,12 +3369,12 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall val targetFeerate = nodeParams.onChainFeeConf.getFundingFeerate(nodeParams.currentFeeratesForFundingClosing) val fundingContribution = InteractiveTxFunder.computeSpliceContribution( isInitiator = true, - sharedInput = Multisig2of2Input(parentCommitment), + sharedInput = Multisig2of2Input(channelKeys, parentCommitment), spliceInAmount = cmd.additionalLocalFunding, spliceOut = cmd.spliceOutputs, targetFeerate = targetFeerate) val commitTxFees = if (d.commitments.localChannelParams.paysCommitTxFees) { - Transactions.commitTxTotalCost(d.commitments.channelParams.remoteCommitParams.dustLimit, parentCommitment.remoteCommit.spec, d.commitments.channelParams.commitmentFormat) + Transactions.commitTxTotalCost(parentCommitment.remoteCommitParams.dustLimit, parentCommitment.remoteCommit.spec, parentCommitment.commitmentFormat) } else { 0.sat } @@ -3375,7 +3404,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall // We use the same contribution as the previous splice attempt. val fundingContribution = rbf.latestFundingTx.fundingParams.localContribution val commitTxFees = if (d.commitments.localChannelParams.paysCommitTxFees) { - Transactions.commitTxTotalCost(d.commitments.channelParams.remoteCommitParams.dustLimit, rbf.parentCommitment.remoteCommit.spec, d.commitments.channelParams.commitmentFormat) + Transactions.commitTxTotalCost(rbf.parentCommitment.remoteCommitParams.dustLimit, rbf.parentCommitment.remoteCommit.spec, rbf.latestFundingTx.fundingParams.commitmentFormat) } else { 0.sat } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenDualFunded.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenDualFunded.scala index e89ae08750..dbc079cada 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenDualFunded.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenDualFunded.scala @@ -19,7 +19,6 @@ package fr.acinq.eclair.channel.fsm import akka.actor.typed.scaladsl.adapter.{ClassicActorContextOps, actorRefAdapter} import fr.acinq.bitcoin.scalacompat.SatoshiLong import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._ -import fr.acinq.eclair.channel.Helpers.Funding import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.fsm.Channel._ import fr.acinq.eclair.channel.fund.InteractiveTxBuilder.{FullySignedSharedTransaction, InteractiveTxParams, PartiallySignedSharedTransaction, RequireConfirmedInputs} @@ -27,6 +26,7 @@ import fr.acinq.eclair.channel.fund.{InteractiveTxBuilder, InteractiveTxSigningS import fr.acinq.eclair.channel.publish.TxPublisher.SetChannelId import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.io.Peer.{LiquidityPurchaseSigned, OpenChannelResponse} +import fr.acinq.eclair.transactions.Transactions import fr.acinq.eclair.wire.protocol._ import fr.acinq.eclair.{ToMilliSatoshiConversion, randomBytes32} @@ -140,19 +140,14 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { when(WAIT_FOR_OPEN_DUAL_FUNDED_CHANNEL)(handleExceptions { case Event(open: OpenDualFundedChannel, d: DATA_WAIT_FOR_OPEN_DUAL_FUNDED_CHANNEL) => val localFundingPubkey = channelKeys.fundingKey(fundingTxIndex = 0).publicKey - val fundingScript = Funding.makeFundingScript(localFundingPubkey, open.fundingPubkey, d.init.channelType.commitmentFormat).pubkeyScript + val fundingScript = Transactions.makeFundingScript(localFundingPubkey, open.fundingPubkey, d.init.channelType.commitmentFormat).pubkeyScript Helpers.validateParamsDualFundedNonInitiator(nodeParams, d.init.channelType, open, fundingScript, remoteNodeId, d.init.localChannelParams.initFeatures, d.init.remoteInit.features, d.init.fundingContribution_opt) match { case Left(t) => handleLocalError(t, d, Some(open)) case Right((channelFeatures, remoteShutdownScript, willFund_opt)) => context.system.eventStream.publish(ChannelCreated(self, peer, remoteNodeId, isOpener = false, open.temporaryChannelId, open.commitmentFeerate, Some(open.fundingFeerate))) val remoteChannelParams = RemoteChannelParams( nodeId = remoteNodeId, - dustLimit = open.dustLimit, - maxHtlcValueInFlightMsat = open.maxHtlcValueInFlightMsat, initialRequestedChannelReserve_opt = None, // channel reserve will be computed based on channel capacity - htlcMinimum = open.htlcMinimum, - toRemoteDelay = open.toSelfDelay, - maxAcceptedHtlcs = open.maxAcceptedHtlcs, revocationBasepoint = open.revocationBasepoint, paymentBasepoint = open.paymentBasepoint, delayedPaymentBasepoint = open.delayedPaymentBasepoint, @@ -162,6 +157,8 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { // We've exchanged open_channel2 and accept_channel2, we now know the final channelId. val channelId = Helpers.computeChannelId(open.revocationBasepoint, channelKeys.revocationBasePoint) val channelParams = ChannelParams(channelId, d.init.channelConfig, channelFeatures, d.init.localChannelParams, remoteChannelParams, open.channelFlags) + val localCommitParams = CommitParams(d.init.proposedCommitParams.localDustLimit, d.init.proposedCommitParams.localHtlcMinimum, d.init.proposedCommitParams.localMaxHtlcValueInFlight, d.init.proposedCommitParams.localMaxAcceptedHtlcs, open.toSelfDelay) + val remoteCommitParams = CommitParams(open.dustLimit, open.htlcMinimum, open.maxHtlcValueInFlightMsat, open.maxAcceptedHtlcs, d.init.proposedCommitParams.toRemoteDelay) val localAmount = d.init.fundingContribution_opt.map(_.fundingAmount).getOrElse(0 sat) val tlvs: Set[AcceptDualFundedChannelTlv] = Set( d.init.localChannelParams.upfrontShutdownScript_opt.map(scriptPubKey => ChannelTlv.UpfrontShutdownScriptTlv(scriptPubKey)), @@ -174,12 +171,12 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { val accept = AcceptDualFundedChannel( temporaryChannelId = open.temporaryChannelId, fundingAmount = localAmount, - dustLimit = d.init.proposedCommitParams.localDustLimit, - maxHtlcValueInFlightMsat = d.init.proposedCommitParams.localMaxHtlcValueInFlight, - htlcMinimum = d.init.proposedCommitParams.localHtlcMinimum, + dustLimit = localCommitParams.dustLimit, + maxHtlcValueInFlightMsat = localCommitParams.maxHtlcValueInFlight, + htlcMinimum = localCommitParams.htlcMinimum, minimumDepth = channelParams.minDepth(nodeParams.channelConf.minDepth).getOrElse(0).toLong, - toSelfDelay = d.init.proposedCommitParams.toRemoteDelay, - maxAcceptedHtlcs = d.init.proposedCommitParams.localMaxAcceptedHtlcs, + toSelfDelay = remoteCommitParams.toSelfDelay, + maxAcceptedHtlcs = localCommitParams.maxAcceptedHtlcs, fundingPubkey = localFundingPubkey, revocationBasepoint = channelKeys.revocationBasePoint, paymentBasepoint = d.init.localChannelParams.walletStaticPaymentBasepoint.getOrElse(channelKeys.paymentBasePoint), @@ -200,6 +197,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { sharedInput_opt = None, remoteFundingPubKey = open.fundingPubkey, localOutputs = Nil, + commitmentFormat = d.init.channelType.commitmentFormat, lockTime = open.lockTime, dustLimit = open.dustLimit.max(accept.dustLimit), targetFeerate = open.fundingFeerate, @@ -209,12 +207,12 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { val txBuilder = context.spawnAnonymous(InteractiveTxBuilder( randomBytes32(), nodeParams, fundingParams, - channelParams, channelKeys, purpose, + channelParams, localCommitParams, remoteCommitParams, channelKeys, purpose, localPushAmount = accept.pushAmount, remotePushAmount = open.pushAmount, willFund_opt.map(_.purchase), wallet)) txBuilder ! InteractiveTxBuilder.Start(self) - goto(WAIT_FOR_DUAL_FUNDING_CREATED) using DATA_WAIT_FOR_DUAL_FUNDING_CREATED(channelId, channelParams, open.secondPerCommitmentPoint, accept.pushAmount, open.pushAmount, txBuilder, deferred = None, replyTo_opt = None) sending accept + goto(WAIT_FOR_DUAL_FUNDING_CREATED) using DATA_WAIT_FOR_DUAL_FUNDING_CREATED(channelId, channelParams, localCommitParams, remoteCommitParams, open.secondPerCommitmentPoint, accept.pushAmount, open.pushAmount, txBuilder, deferred = None, replyTo_opt = None) sending accept } case Event(c: CloseCommand, d) => handleFastClose(c, d.channelId) @@ -238,12 +236,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { context.system.eventStream.publish(ChannelIdAssigned(self, remoteNodeId, accept.temporaryChannelId, channelId)) val remoteChannelParams = RemoteChannelParams( nodeId = remoteNodeId, - dustLimit = accept.dustLimit, - maxHtlcValueInFlightMsat = accept.maxHtlcValueInFlightMsat, initialRequestedChannelReserve_opt = None, // channel reserve will be computed based on channel capacity - htlcMinimum = accept.htlcMinimum, - toRemoteDelay = accept.toSelfDelay, - maxAcceptedHtlcs = accept.maxAcceptedHtlcs, revocationBasepoint = accept.revocationBasepoint, paymentBasepoint = accept.paymentBasepoint, delayedPaymentBasepoint = accept.delayedPaymentBasepoint, @@ -252,6 +245,8 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { upfrontShutdownScript_opt = remoteShutdownScript) // We start the interactive-tx funding protocol. val channelParams = ChannelParams(channelId, d.init.channelConfig, channelFeatures, d.init.localChannelParams, remoteChannelParams, d.lastSent.channelFlags) + val localCommitParams = CommitParams(d.init.proposedCommitParams.localDustLimit, d.init.proposedCommitParams.localHtlcMinimum, d.init.proposedCommitParams.localMaxHtlcValueInFlight, d.init.proposedCommitParams.localMaxAcceptedHtlcs, accept.toSelfDelay) + val remoteCommitParams = CommitParams(accept.dustLimit, accept.htlcMinimum, accept.maxHtlcValueInFlightMsat, accept.maxAcceptedHtlcs, d.init.proposedCommitParams.toRemoteDelay) val localAmount = d.lastSent.fundingAmount val remoteAmount = accept.fundingAmount val fundingParams = InteractiveTxParams( @@ -262,6 +257,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { sharedInput_opt = None, remoteFundingPubKey = accept.fundingPubkey, localOutputs = Nil, + commitmentFormat = d.init.channelType.commitmentFormat, lockTime = d.lastSent.lockTime, dustLimit = d.lastSent.dustLimit.max(accept.dustLimit), targetFeerate = d.lastSent.fundingFeerate, @@ -271,12 +267,12 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { val txBuilder = context.spawnAnonymous(InteractiveTxBuilder( randomBytes32(), nodeParams, fundingParams, - channelParams, channelKeys, purpose, + channelParams, localCommitParams, remoteCommitParams, channelKeys, purpose, localPushAmount = d.lastSent.pushAmount, remotePushAmount = accept.pushAmount, liquidityPurchase_opt = liquidityPurchase_opt, wallet)) txBuilder ! InteractiveTxBuilder.Start(self) - goto(WAIT_FOR_DUAL_FUNDING_CREATED) using DATA_WAIT_FOR_DUAL_FUNDING_CREATED(channelId, channelParams, accept.secondPerCommitmentPoint, d.lastSent.pushAmount, accept.pushAmount, txBuilder, deferred = None, replyTo_opt = Some(d.init.replyTo)) + goto(WAIT_FOR_DUAL_FUNDING_CREATED) using DATA_WAIT_FOR_DUAL_FUNDING_CREATED(channelId, channelParams, localCommitParams, remoteCommitParams, accept.secondPerCommitmentPoint, d.lastSent.pushAmount, accept.pushAmount, txBuilder, deferred = None, replyTo_opt = Some(d.init.replyTo)) } case Event(c: CloseCommand, d: DATA_WAIT_FOR_ACCEPT_DUAL_FUNDED_CHANNEL) => @@ -330,9 +326,9 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { d.deferred.foreach(self ! _) d.replyTo_opt.foreach(_ ! OpenChannelResponse.Created(d.channelId, status.fundingTx.txId, status.fundingTx.tx.localFees.truncateToSatoshi)) liquidityPurchase_opt.collect { - case purchase if !status.fundingParams.isInitiator => peer ! LiquidityPurchaseSigned(d.channelId, status.fundingTx.txId, status.fundingTxIndex, d.channelParams.remoteCommitParams.htlcMinimum, purchase) + case purchase if !status.fundingParams.isInitiator => peer ! LiquidityPurchaseSigned(d.channelId, status.fundingTx.txId, status.fundingTxIndex, d.remoteCommitParams.htlcMinimum, purchase) } - val d1 = DATA_WAIT_FOR_DUAL_FUNDING_SIGNED(d.channelParams, d.secondRemotePerCommitmentPoint, d.localPushAmount, d.remotePushAmount, status, None) + val d1 = DATA_WAIT_FOR_DUAL_FUNDING_SIGNED(d.channelParams, d.secondRemotePerCommitmentPoint, d.localPushAmount, d.remotePushAmount, status) goto(WAIT_FOR_DUAL_FUNDING_SIGNED) using d1 storing() sending commitSig case f: InteractiveTxBuilder.Failed => d.replyTo_opt.foreach(_ ! OpenChannelResponse.Rejected(f.cause.getMessage)) @@ -542,7 +538,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { log.info("rejecting rbf attempt: last attempt was less than {} blocks ago", nodeParams.channelConf.remoteRbfLimits.attemptDeltaBlocks) stay() using d.copy(status = DualFundingStatus.RbfAborted) sending TxAbort(d.channelId, InvalidRbfAttemptTooSoon(d.channelId, d.latestFundingTx.createdAt, d.latestFundingTx.createdAt + nodeParams.channelConf.remoteRbfLimits.attemptDeltaBlocks).getMessage) } else { - val fundingScript = d.commitments.latest.commitInput.txOut.publicKeyScript + val fundingScript = d.commitments.latest.commitInput(channelKeys).txOut.publicKeyScript LiquidityAds.validateRequest(nodeParams.privateKey, d.channelId, fundingScript, msg.feerate, isChannelCreation = true, msg.requestFunding_opt, nodeParams.liquidityAdsConfig.rates_opt, None) match { case Left(t) => log.warning("rejecting rbf attempt: invalid liquidity ads request ({})", t.getMessage) @@ -564,6 +560,8 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { randomBytes32(), nodeParams, fundingParams, channelParams = d.commitments.channelParams, + localCommitParams = d.commitments.active.head.localCommitParams, + remoteCommitParams = d.commitments.active.head.remoteCommitParams, channelKeys = channelKeys, purpose = InteractiveTxBuilder.FundingTxRbf(d.commitments.active.head, previousTransactions = d.allFundingTxs.map(_.sharedTx), feeBudget_opt = None), localPushAmount = d.localPushAmount, remotePushAmount = d.remotePushAmount, @@ -600,7 +598,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { lockTime = cmd.lockTime, targetFeerate = cmd.targetFeerate, ) - val fundingScript = d.commitments.latest.commitInput.txOut.publicKeyScript + val fundingScript = d.commitments.latest.commitInput(channelKeys).txOut.publicKeyScript LiquidityAds.validateRemoteFunding(cmd.requestFunding_opt, remoteNodeId, d.channelId, fundingScript, msg.fundingContribution, cmd.targetFeerate, isChannelCreation = true, msg.willFund_opt) match { case Left(t) => log.warning("rejecting rbf attempt: invalid liquidity ads response ({})", t.getMessage) @@ -612,6 +610,8 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { randomBytes32(), nodeParams, fundingParams, channelParams = d.commitments.channelParams, + localCommitParams = d.commitments.active.head.localCommitParams, + remoteCommitParams = d.commitments.active.head.remoteCommitParams, channelKeys = channelKeys, purpose = InteractiveTxBuilder.FundingTxRbf(d.commitments.active.head, previousTransactions = d.allFundingTxs.map(_.sharedTx), feeBudget_opt = Some(cmd.fundingFeeBudget)), localPushAmount = d.localPushAmount, remotePushAmount = d.remotePushAmount, @@ -695,7 +695,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { cmd_opt.foreach(cmd => cmd.replyTo ! RES_BUMP_FUNDING_FEE(rbfIndex = d.previousFundingTxs.length, signingSession.fundingTx.txId, signingSession.fundingTx.tx.localFees.truncateToSatoshi)) remoteCommitSig_opt.foreach(self ! _) liquidityPurchase_opt.collect { - case purchase if !signingSession.fundingParams.isInitiator => peer ! LiquidityPurchaseSigned(d.channelId, signingSession.fundingTx.txId, signingSession.fundingTxIndex, d.commitments.channelParams.remoteCommitParams.htlcMinimum, purchase) + case purchase if !signingSession.fundingParams.isInitiator => peer ! LiquidityPurchaseSigned(d.channelId, signingSession.fundingTx.txId, signingSession.fundingTxIndex, signingSession.remoteCommitParams.htlcMinimum, purchase) } val d1 = d.copy(status = DualFundingStatus.RbfWaitingForSigs(signingSession)) stay() using d1 storing() sending commitSig diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunded.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunded.scala index 4332b503d4..52948ca7b4 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunded.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunded.scala @@ -110,12 +110,7 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { context.system.eventStream.publish(ChannelCreated(self, peer, remoteNodeId, isOpener = false, open.temporaryChannelId, open.feeratePerKw, None)) val remoteChannelParams = RemoteChannelParams( nodeId = remoteNodeId, - dustLimit = open.dustLimitSatoshis, - maxHtlcValueInFlightMsat = open.maxHtlcValueInFlightMsat, initialRequestedChannelReserve_opt = Some(open.channelReserveSatoshis), // our peer requires us to always have at least that much satoshis in our balance - htlcMinimum = open.htlcMinimumMsat, - toRemoteDelay = open.toSelfDelay, - maxAcceptedHtlcs = open.maxAcceptedHtlcs, revocationBasepoint = open.revocationBasepoint, paymentBasepoint = open.paymentBasepoint, delayedPaymentBasepoint = open.delayedPaymentBasepoint, @@ -124,17 +119,19 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { upfrontShutdownScript_opt = remoteShutdownScript) val fundingPubkey = channelKeys.fundingKey(fundingTxIndex = 0).publicKey val channelParams = ChannelParams(d.initFundee.temporaryChannelId, d.initFundee.channelConfig, channelFeatures, d.initFundee.localChannelParams, remoteChannelParams, open.channelFlags) + val localCommitParams = CommitParams(d.initFundee.proposedCommitParams.localDustLimit, d.initFundee.proposedCommitParams.localHtlcMinimum, d.initFundee.proposedCommitParams.localMaxHtlcValueInFlight, d.initFundee.proposedCommitParams.localMaxAcceptedHtlcs, open.toSelfDelay) + val remoteCommitParams = CommitParams(open.dustLimitSatoshis, open.htlcMinimumMsat, open.maxHtlcValueInFlightMsat, open.maxAcceptedHtlcs, d.initFundee.proposedCommitParams.toRemoteDelay) // In order to allow TLV extensions and keep backwards-compatibility, we include an empty upfront_shutdown_script if this feature is not used. // See https://github.com/lightningnetwork/lightning-rfc/pull/714. val localShutdownScript = d.initFundee.localChannelParams.upfrontShutdownScript_opt.getOrElse(ByteVector.empty) val accept = AcceptChannel(temporaryChannelId = open.temporaryChannelId, - dustLimitSatoshis = d.initFundee.proposedCommitParams.localDustLimit, - maxHtlcValueInFlightMsat = d.initFundee.proposedCommitParams.localMaxHtlcValueInFlight, + dustLimitSatoshis = localCommitParams.dustLimit, + maxHtlcValueInFlightMsat = localCommitParams.maxHtlcValueInFlight, channelReserveSatoshis = d.initFundee.localChannelParams.initialRequestedChannelReserve_opt.get, minimumDepth = channelParams.minDepth(nodeParams.channelConf.minDepth).getOrElse(0).toLong, - htlcMinimumMsat = d.initFundee.proposedCommitParams.localHtlcMinimum, - toSelfDelay = d.initFundee.proposedCommitParams.toRemoteDelay, - maxAcceptedHtlcs = d.initFundee.proposedCommitParams.localMaxAcceptedHtlcs, + htlcMinimumMsat = localCommitParams.htlcMinimum, + toSelfDelay = remoteCommitParams.toSelfDelay, + maxAcceptedHtlcs = localCommitParams.maxAcceptedHtlcs, fundingPubkey = fundingPubkey, revocationBasepoint = channelKeys.revocationBasePoint, paymentBasepoint = d.initFundee.localChannelParams.walletStaticPaymentBasepoint.getOrElse(channelKeys.paymentBasePoint), @@ -145,7 +142,7 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { ChannelTlv.UpfrontShutdownScriptTlv(localShutdownScript), ChannelTlv.ChannelTypeTlv(d.initFundee.channelType) )) - goto(WAIT_FOR_FUNDING_CREATED) using DATA_WAIT_FOR_FUNDING_CREATED(channelParams, open.fundingSatoshis, open.pushMsat, open.feeratePerKw, open.fundingPubkey, open.firstPerCommitmentPoint) sending accept + goto(WAIT_FOR_FUNDING_CREATED) using DATA_WAIT_FOR_FUNDING_CREATED(channelParams, localCommitParams, remoteCommitParams, open.fundingSatoshis, open.pushMsat, open.feeratePerKw, open.fundingPubkey, open.firstPerCommitmentPoint) sending accept } case Event(c: CloseCommand, d) => handleFastClose(c, d.channelId) @@ -156,32 +153,29 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { }) when(WAIT_FOR_ACCEPT_CHANNEL)(handleExceptions { - case Event(accept: AcceptChannel, d@DATA_WAIT_FOR_ACCEPT_CHANNEL(init, open)) => - Helpers.validateParamsSingleFundedFunder(nodeParams, init.channelType, init.localChannelParams.initFeatures, init.remoteInit.features, open, accept) match { + case Event(accept: AcceptChannel, d: DATA_WAIT_FOR_ACCEPT_CHANNEL) => + Helpers.validateParamsSingleFundedFunder(nodeParams, d.initFunder.channelType, d.initFunder.localChannelParams.initFeatures, d.initFunder.remoteInit.features, d.lastSent, accept) match { case Left(t) => d.initFunder.replyTo ! OpenChannelResponse.Rejected(t.getMessage) handleLocalError(t, d, Some(accept)) case Right((channelFeatures, remoteShutdownScript)) => val remoteChannelParams = RemoteChannelParams( nodeId = remoteNodeId, - dustLimit = accept.dustLimitSatoshis, - maxHtlcValueInFlightMsat = accept.maxHtlcValueInFlightMsat, initialRequestedChannelReserve_opt = Some(accept.channelReserveSatoshis), // our peer requires us to always have at least that much satoshis in our balance - htlcMinimum = accept.htlcMinimumMsat, - toRemoteDelay = accept.toSelfDelay, - maxAcceptedHtlcs = accept.maxAcceptedHtlcs, revocationBasepoint = accept.revocationBasepoint, paymentBasepoint = accept.paymentBasepoint, delayedPaymentBasepoint = accept.delayedPaymentBasepoint, htlcBasepoint = accept.htlcBasepoint, - initFeatures = init.remoteInit.features, + initFeatures = d.initFunder.remoteInit.features, upfrontShutdownScript_opt = remoteShutdownScript) log.info("remote will use fundingMinDepth={}", accept.minimumDepth) val localFundingKey = channelKeys.fundingKey(fundingTxIndex = 0) val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(localFundingKey.publicKey, accept.fundingPubkey))) - wallet.makeFundingTx(fundingPubkeyScript, init.fundingAmount, init.fundingTxFeerate, init.fundingTxFeeBudget_opt).pipeTo(self) - val channelParams = ChannelParams(init.temporaryChannelId, init.channelConfig, channelFeatures, init.localChannelParams, remoteChannelParams, open.channelFlags) - goto(WAIT_FOR_FUNDING_INTERNAL) using DATA_WAIT_FOR_FUNDING_INTERNAL(channelParams, init.fundingAmount, init.pushAmount_opt.getOrElse(0 msat), init.commitTxFeerate, accept.fundingPubkey, accept.firstPerCommitmentPoint, d.initFunder.replyTo) + wallet.makeFundingTx(fundingPubkeyScript, d.initFunder.fundingAmount, d.initFunder.fundingTxFeerate, d.initFunder.fundingTxFeeBudget_opt).pipeTo(self) + val channelParams = ChannelParams(d.initFunder.temporaryChannelId, d.initFunder.channelConfig, channelFeatures, d.initFunder.localChannelParams, remoteChannelParams, d.lastSent.channelFlags) + val localCommitParams = CommitParams(d.initFunder.proposedCommitParams.localDustLimit, d.initFunder.proposedCommitParams.localHtlcMinimum, d.initFunder.proposedCommitParams.localMaxHtlcValueInFlight, d.initFunder.proposedCommitParams.localMaxAcceptedHtlcs, accept.toSelfDelay) + val remoteCommitParams = CommitParams(accept.dustLimitSatoshis, accept.htlcMinimumMsat, accept.maxHtlcValueInFlightMsat, accept.maxAcceptedHtlcs, d.initFunder.proposedCommitParams.toRemoteDelay) + goto(WAIT_FOR_FUNDING_INTERNAL) using DATA_WAIT_FOR_FUNDING_INTERNAL(channelParams, localCommitParams, remoteCommitParams, d.initFunder.fundingAmount, d.initFunder.pushAmount_opt.getOrElse(0 msat), d.initFunder.commitTxFeerate, accept.fundingPubkey, accept.firstPerCommitmentPoint, d.initFunder.replyTo) } case Event(c: CloseCommand, d: DATA_WAIT_FOR_ACCEPT_CHANNEL) => @@ -206,16 +200,17 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { val temporaryChannelId = d.channelParams.channelId // let's create the first commitment tx that spends the yet uncommitted funding tx val fundingKey = channelKeys.fundingKey(fundingTxIndex = 0) - val localCommitmentKeys = LocalCommitmentKeys(d.channelParams, channelKeys, localCommitIndex = 0) - val remoteCommitmentKeys = RemoteCommitmentKeys(d.channelParams, channelKeys, d.remoteFirstPerCommitmentPoint) - Funding.makeFirstCommitTxs(d.channelParams, localFundingAmount = d.fundingAmount, remoteFundingAmount = 0 sat, localPushAmount = d.pushAmount, remotePushAmount = 0 msat, d.commitTxFeerate, fundingTx.txid, fundingTxOutputIndex, fundingKey, d.remoteFundingPubKey, localCommitmentKeys, remoteCommitmentKeys) match { + val localCommitmentKeys = LocalCommitmentKeys(d.channelParams, channelKeys, localCommitIndex = 0, d.commitmentFormat) + val remoteCommitmentKeys = RemoteCommitmentKeys(d.channelParams, channelKeys, d.remoteFirstPerCommitmentPoint, d.commitmentFormat) + Funding.makeFirstCommitTxs(d.channelParams, d.localCommitParams, d.remoteCommitParams, localFundingAmount = d.fundingAmount, remoteFundingAmount = 0 sat, localPushAmount = d.pushAmount, remotePushAmount = 0 msat, d.commitTxFeerate, d.commitmentFormat, fundingTx.txid, fundingTxOutputIndex, fundingKey, d.remoteFundingPubKey, localCommitmentKeys, remoteCommitmentKeys) match { case Left(ex) => handleLocalError(ex, d, None) case Right((localSpec, localCommitTx, remoteSpec, remoteCommitTx)) => require(fundingTx.txOut(fundingTxOutputIndex).publicKeyScript == localCommitTx.input.txOut.publicKeyScript, s"pubkey script mismatch!") - val localSigOfRemoteTx = d.channelParams.commitmentFormat match { + val localSigOfRemoteTx = d.commitmentFormat match { case _: SegwitV0CommitmentFormat => remoteCommitTx.sign(fundingKey, d.remoteFundingPubKey).sig case _: SimpleTaprootChannelCommitmentFormat => ??? } + val remoteCommit = RemoteCommit(0, remoteSpec, remoteCommitTx.tx.txid, d.remoteFirstPerCommitmentPoint) // signature of their initial commitment tx that pays remote pushMsat val fundingCreated = FundingCreated( temporaryChannelId = temporaryChannelId, @@ -229,7 +224,7 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { txPublisher ! SetChannelId(remoteNodeId, channelId) context.system.eventStream.publish(ChannelIdAssigned(self, remoteNodeId, temporaryChannelId, channelId)) // NB: we don't send a ChannelSignatureSent for the first commit - goto(WAIT_FOR_FUNDING_SIGNED) using DATA_WAIT_FOR_FUNDING_SIGNED(channelParams1, d.remoteFundingPubKey, fundingTx, fundingTxFee, localSpec, localCommitTx, RemoteCommit(0, remoteSpec, remoteCommitTx.tx.txid, d.remoteFirstPerCommitmentPoint), fundingCreated, d.replyTo) sending fundingCreated + goto(WAIT_FOR_FUNDING_SIGNED) using DATA_WAIT_FOR_FUNDING_SIGNED(channelParams1, d.localCommitParams, d.remoteCommitParams, d.remoteFundingPubKey, fundingTx, fundingTxFee, localSpec, localCommitTx, remoteCommit, fundingCreated, d.replyTo) sending fundingCreated } case Event(Status.Failure(t), d: DATA_WAIT_FOR_FUNDING_INTERNAL) => @@ -258,17 +253,17 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { case Event(FundingCreated(_, fundingTxId, fundingTxOutputIndex, remoteSig, _), d: DATA_WAIT_FOR_FUNDING_CREATED) => val temporaryChannelId = d.channelParams.channelId val fundingKey = channelKeys.fundingKey(fundingTxIndex = 0) - val localCommitmentKeys = LocalCommitmentKeys(d.channelParams, channelKeys, localCommitIndex = 0) - val remoteCommitmentKeys = RemoteCommitmentKeys(d.channelParams, channelKeys, d.remoteFirstPerCommitmentPoint) + val localCommitmentKeys = LocalCommitmentKeys(d.channelParams, channelKeys, localCommitIndex = 0, d.commitmentFormat) + val remoteCommitmentKeys = RemoteCommitmentKeys(d.channelParams, channelKeys, d.remoteFirstPerCommitmentPoint, d.commitmentFormat) // they fund the channel with their funding tx, so the money is theirs (but we are paid pushMsat) - Funding.makeFirstCommitTxs(d.channelParams, localFundingAmount = 0 sat, remoteFundingAmount = d.fundingAmount, localPushAmount = 0 msat, remotePushAmount = d.pushAmount, d.commitTxFeerate, fundingTxId, fundingTxOutputIndex, fundingKey, d.remoteFundingPubKey, localCommitmentKeys, remoteCommitmentKeys) match { + Funding.makeFirstCommitTxs(d.channelParams, d.localCommitParams, d.remoteCommitParams, localFundingAmount = 0 sat, remoteFundingAmount = d.fundingAmount, localPushAmount = 0 msat, remotePushAmount = d.pushAmount, d.commitTxFeerate, d.commitmentFormat, fundingTxId, fundingTxOutputIndex, fundingKey, d.remoteFundingPubKey, localCommitmentKeys, remoteCommitmentKeys) match { case Left(ex) => handleLocalError(ex, d, None) case Right((localSpec, localCommitTx, remoteSpec, remoteCommitTx)) => // check remote signature validity localCommitTx.checkRemoteSig(fundingKey.publicKey, d.remoteFundingPubKey, ChannelSpendSignature.IndividualSignature(remoteSig)) match { case false => handleLocalError(InvalidCommitmentSignature(temporaryChannelId, fundingTxId, commitmentNumber = 0, localCommitTx.tx), d, None) case true => - val localSigOfRemoteTx = d.channelParams.commitmentFormat match { + val localSigOfRemoteTx = d.commitmentFormat match { case _: SegwitV0CommitmentFormat => remoteCommitTx.sign(fundingKey, d.remoteFundingPubKey).sig case _: SimpleTaprootChannelCommitmentFormat => ??? } @@ -280,10 +275,15 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { val commitment = Commitment( fundingTxIndex = 0, firstRemoteCommitIndex = 0, + fundingInput = localCommitTx.input.outPoint, + fundingAmount = localCommitTx.input.txOut.amount, remoteFundingPubKey = d.remoteFundingPubKey, localFundingStatus = SingleFundedUnconfirmedFundingTx(None), remoteFundingStatus = RemoteFundingStatus.NotLocked, - localCommit = LocalCommit(0, localSpec, localCommitTx.tx.txid, localCommitTx.input, ChannelSpendSignature.IndividualSignature(remoteSig), htlcRemoteSigs = Nil), + commitmentFormat = d.commitmentFormat, + localCommitParams = d.localCommitParams, + localCommit = LocalCommit(0, localSpec, localCommitTx.tx.txid, ChannelSpendSignature.IndividualSignature(remoteSig), htlcRemoteSigs = Nil), + remoteCommitParams = d.remoteCommitParams, remoteCommit = RemoteCommit(0, remoteSpec, remoteCommitTx.tx.txid, d.remoteFirstPerCommitmentPoint), nextRemoteCommit_opt = None) val commitments = Commitments( @@ -325,10 +325,15 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { val commitment = Commitment( fundingTxIndex = 0, firstRemoteCommitIndex = 0, + fundingInput = d.localCommitTx.input.outPoint, + fundingAmount = d.localCommitTx.input.txOut.amount, remoteFundingPubKey = d.remoteFundingPubKey, localFundingStatus = SingleFundedUnconfirmedFundingTx(Some(d.fundingTx)), remoteFundingStatus = RemoteFundingStatus.NotLocked, - localCommit = LocalCommit(0, d.localSpec, d.localCommitTx.tx.txid, d.localCommitTx.input, ChannelSpendSignature.IndividualSignature(remoteSig), htlcRemoteSigs = Nil), + commitmentFormat = d.commitmentFormat, + localCommitParams = d.localCommitParams, + localCommit = LocalCommit(0, d.localSpec, d.localCommitTx.tx.txid, ChannelSpendSignature.IndividualSignature(remoteSig), htlcRemoteSigs = Nil), + remoteCommitParams = d.remoteCommitParams, remoteCommit = d.remoteCommit, nextRemoteCommit_opt = None ) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/CommonFundingHandlers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/CommonFundingHandlers.scala index 007bf006ab..531e5b7e3d 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/CommonFundingHandlers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/CommonFundingHandlers.scala @@ -44,7 +44,7 @@ trait CommonFundingHandlers extends CommonHandlers { */ def watchFundingSpent(commitment: Commitment, additionalKnownSpendingTxs: Set[TxId], delay_opt: Option[FiniteDuration]): Unit = { val knownSpendingTxs = commitment.commitTxIds.txIds ++ additionalKnownSpendingTxs - val watch = WatchFundingSpent(self, commitment.commitInput.outPoint.txid, commitment.commitInput.outPoint.index.toInt, knownSpendingTxs) + val watch = WatchFundingSpent(self, commitment.fundingInput.txid, commitment.fundingInput.index.toInt, knownSpendingTxs) delay_opt match { case Some(delay) => context.system.scheduler.scheduleOnce(delay, blockchain.toClassic, watch) case None => blockchain ! watch @@ -84,8 +84,8 @@ trait CommonFundingHandlers extends CommonHandlers { context.system.eventStream.publish(TransactionConfirmed(d.channelId, remoteNodeId, w.tx)) d.commitments.all.find(_.fundingTxId == w.tx.txid) match { case Some(c) => - val scid = RealShortChannelId(w.blockHeight, w.txIndex, c.commitInput.outPoint.index.toInt) - val fundingStatus = ConfirmedFundingTx(w.tx, scid, d.commitments.localFundingSigs(w.tx.txid), d.commitments.liquidityPurchase(w.tx.txid)) + val scid = RealShortChannelId(w.blockHeight, w.txIndex, c.fundingInput.index.toInt) + val fundingStatus = ConfirmedFundingTx(w.tx.txOut(c.fundingInput.index.toInt), scid, d.commitments.localFundingSigs(w.tx.txid), d.commitments.liquidityPurchase(w.tx.txid)) // When a splice transaction confirms, it double-spends all the commitment transactions that only applied to the // previous funding transaction. Our peer cannot publish the corresponding revoked commitments anymore, so we can // clean-up the htlc data that we were storing for the matching penalty transactions. @@ -149,7 +149,7 @@ trait CommonFundingHandlers extends CommonHandlers { remoteNextCommitInfo = Right(channelReady.nextPerCommitmentPoint) ) peer ! ChannelReadyForPayments(self, remoteNodeId, commitments.channelId, fundingTxIndex = 0) - DATA_NORMAL(commitments1, aliases1, None, initialChannelUpdate, None, None, None, SpliceStatus.NoSplice) + DATA_NORMAL(commitments1, aliases1, None, initialChannelUpdate, SpliceStatus.NoSplice, None, None, None) } def delayEarlyAnnouncementSigs(remoteAnnSigs: AnnouncementSignatures): Unit = { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ErrorHandlers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ErrorHandlers.scala index cafade4e17..261e1cae3e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ErrorHandlers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ErrorHandlers.scala @@ -224,7 +224,7 @@ trait ErrorHandlers extends CommonHandlers { /** Publish 2nd-stage transactions for our local commitment. */ def doPublish(lcp: LocalCommitPublished, txs: Closing.LocalClose.SecondStageTransactions, commitment: FullCommitment): Unit = { - val publishCommitTx = PublishFinalTx(lcp.commitTx, commitment.commitInput.outPoint, "commit-tx", Closing.commitTxFee(commitment.commitInput, lcp.commitTx, commitment.localChannelParams.paysCommitTxFees), None) + val publishCommitTx = PublishFinalTx(lcp.commitTx, commitment.fundingInput, "commit-tx", Closing.commitTxFee(commitment.commitInput(channelKeys), lcp.commitTx, commitment.localChannelParams.paysCommitTxFees), None) val publishAnchorTx_opt = txs.anchorTx_opt match { case Some(anchorTx) if !lcp.isConfirmed => val confirmationTarget = Closing.confirmationTarget(commitment.localCommit, commitment.localCommitParams.dustLimit, commitment.commitmentFormat, nodeParams.onChainFeeConf) @@ -265,7 +265,7 @@ trait ErrorHandlers extends CommonHandlers { log.warning(s"they published their current commit in txid=${commitTx.txid}") require(commitTx.txid == commitments.remoteCommit.txId, "txid mismatch") val finalScriptPubKey = getOrGenerateFinalScriptPubKey(d) - context.system.eventStream.publish(TransactionPublished(d.channelId, remoteNodeId, commitTx, Closing.commitTxFee(commitments.commitInput, commitTx, d.commitments.localChannelParams.paysCommitTxFees), "remote-commit")) + context.system.eventStream.publish(TransactionPublished(d.channelId, remoteNodeId, commitTx, Closing.commitTxFee(commitments.commitInput(channelKeys), commitTx, d.commitments.localChannelParams.paysCommitTxFees), "remote-commit")) val (remoteCommitPublished, closingTxs) = Closing.RemoteClose.claimCommitTxOutputs(channelKeys, commitments, commitments.remoteCommit, commitTx, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, finalScriptPubKey) val nextData = d match { case closing: DATA_CLOSING => closing.copy(remoteCommitPublished = Some(remoteCommitPublished)) @@ -284,7 +284,7 @@ trait ErrorHandlers extends CommonHandlers { require(commitTx.txid == remoteCommit.txId, "txid mismatch") val finalScriptPubKey = getOrGenerateFinalScriptPubKey(d) - context.system.eventStream.publish(TransactionPublished(d.channelId, remoteNodeId, commitTx, Closing.commitTxFee(commitment.commitInput, commitTx, d.commitments.localChannelParams.paysCommitTxFees), "next-remote-commit")) + context.system.eventStream.publish(TransactionPublished(d.channelId, remoteNodeId, commitTx, Closing.commitTxFee(commitment.commitInput(channelKeys), commitTx, d.commitments.localChannelParams.paysCommitTxFees), "next-remote-commit")) val (remoteCommitPublished, closingTxs) = Closing.RemoteClose.claimCommitTxOutputs(channelKeys, commitment, remoteCommit, commitTx, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, finalScriptPubKey) val nextData = d match { case closing: DATA_CLOSING => closing.copy(nextRemoteCommitPublished = Some(remoteCommitPublished)) @@ -332,9 +332,13 @@ trait ErrorHandlers extends CommonHandlers { val finalScriptPubKey = getOrGenerateFinalScriptPubKey(d) Closing.RevokedClose.getRemotePerCommitmentSecret(d.commitments.channelParams, channelKeys, d.commitments.remotePerCommitmentSecrets, tx) match { case Some((commitmentNumber, remotePerCommitmentSecret)) => - val (revokedCommitPublished, closingTxs) = Closing.RevokedClose.claimCommitTxOutputs(d.commitments.channelParams, channelKeys, tx, commitmentNumber, remotePerCommitmentSecret, nodeParams.db.channels, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, finalScriptPubKey) + // TODO: once we allow changing the commitment format or to_self_delay during a splice, those values may be incorrect. + val toSelfDelay = commitment.remoteCommitParams.toSelfDelay + val commitmentFormat = commitment.commitmentFormat + val dustLimit = commitment.localCommitParams.dustLimit + val (revokedCommitPublished, closingTxs) = Closing.RevokedClose.claimCommitTxOutputs(d.commitments.channelParams, channelKeys, tx, commitmentNumber, remotePerCommitmentSecret, toSelfDelay, commitmentFormat, nodeParams.db.channels, dustLimit, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, finalScriptPubKey) log.warning("txid={} was a revoked commitment, publishing the penalty tx", tx.txid) - context.system.eventStream.publish(TransactionPublished(d.channelId, remoteNodeId, tx, Closing.commitTxFee(commitment.commitInput, tx, d.commitments.localChannelParams.paysCommitTxFees), "revoked-commit")) + context.system.eventStream.publish(TransactionPublished(d.channelId, remoteNodeId, tx, Closing.commitTxFee(commitment.commitInput(channelKeys), tx, d.commitments.localChannelParams.paysCommitTxFees), "revoked-commit")) val exc = FundingTxSpent(d.channelId, tx.txid) val error = Error(d.channelId, exc.getMessage) val nextData = d match { @@ -348,10 +352,10 @@ trait ErrorHandlers extends CommonHandlers { case None => d match { case d: DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT => log.warning("they published a future commit (because we asked them to) in txid={}", tx.txid) - context.system.eventStream.publish(TransactionPublished(d.channelId, remoteNodeId, tx, Closing.commitTxFee(d.commitments.latest.commitInput, tx, d.commitments.latest.localChannelParams.paysCommitTxFees), "future-remote-commit")) + context.system.eventStream.publish(TransactionPublished(d.channelId, remoteNodeId, tx, Closing.commitTxFee(d.commitments.latest.commitInput(channelKeys), tx, d.commitments.localChannelParams.paysCommitTxFees), "future-remote-commit")) val remotePerCommitmentPoint = d.remoteChannelReestablish.myCurrentPerCommitmentPoint val commitKeys = d.commitments.latest.remoteKeys(channelKeys, remotePerCommitmentPoint) - val mainTx_opt = Closing.RemoteClose.claimMainOutput(d.commitments.channelParams, commitKeys, tx, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, finalScriptPubKey) + val mainTx_opt = Closing.RemoteClose.claimMainOutput(commitKeys, tx, d.commitments.latest.localCommitParams.dustLimit, d.commitments.latest.commitmentFormat, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, finalScriptPubKey) mainTx_opt.foreach(tx => log.warning("publishing our recovery transaction: tx={}", tx.toString)) val remoteCommitPublished = RemoteCommitPublished( commitTx = tx, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxBuilder.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxBuilder.scala index a52d189dff..fd58c5654c 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxBuilder.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxBuilder.scala @@ -34,7 +34,7 @@ import fr.acinq.eclair.channel.fund.InteractiveTxBuilder.Output.Local import fr.acinq.eclair.channel.fund.InteractiveTxBuilder.Purpose import fr.acinq.eclair.channel.fund.InteractiveTxSigningSession.UnsignedLocalCommit import fr.acinq.eclair.crypto.keymanager.{ChannelKeys, LocalCommitmentKeys, RemoteCommitmentKeys} -import fr.acinq.eclair.transactions.Transactions.{InputInfo, SegwitV0CommitmentFormat, SimpleTaprootChannelCommitmentFormat} +import fr.acinq.eclair.transactions.Transactions.{CommitmentFormat, InputInfo, SegwitV0CommitmentFormat, SimpleTaprootChannelCommitmentFormat} import fr.acinq.eclair.transactions._ import fr.acinq.eclair.wire.protocol._ import fr.acinq.eclair.{BlockHeight, Logs, MilliSatoshi, MilliSatoshiLong, NodeParams, ToMilliSatoshiConversion, UInt64} @@ -118,8 +118,8 @@ object InteractiveTxBuilder { } object Multisig2of2Input { - def apply(commitment: Commitment): Multisig2of2Input = Multisig2of2Input( - info = commitment.commitInput, + def apply(channelKeys: ChannelKeys, commitment: Commitment): Multisig2of2Input = Multisig2of2Input( + info = commitment.commitInput(channelKeys), fundingTxIndex = commitment.fundingTxIndex, remoteFundingPubkey = commitment.remoteFundingPubKey ) @@ -145,6 +145,7 @@ object InteractiveTxBuilder { sharedInput_opt: Option[SharedFundingInput], remoteFundingPubKey: PublicKey, localOutputs: List[TxOut], + commitmentFormat: CommitmentFormat, lockTime: Long, dustLimit: Satoshi, targetFeerate: FeeratePerKw, @@ -388,6 +389,8 @@ object InteractiveTxBuilder { nodeParams: NodeParams, fundingParams: InteractiveTxParams, channelParams: ChannelParams, + localCommitParams: CommitParams, + remoteCommitParams: CommitParams, channelKeys: ChannelKeys, purpose: Purpose, localPushAmount: MilliSatoshi, @@ -425,7 +428,7 @@ object InteractiveTxBuilder { replyTo ! LocalFailure(InvalidLiquidityAdsPaymentType(channelParams.channelId, liquidityPurchase_opt.get.paymentDetails.paymentType, Set(LiquidityAds.PaymentType.FromChannelBalance, LiquidityAds.PaymentType.FromChannelBalanceForFutureHtlc))) Behaviors.stopped } else { - val actor = new InteractiveTxBuilder(replyTo, sessionId, nodeParams, channelParams, channelKeys, fundingParams, purpose, localPushAmount, remotePushAmount, liquidityPurchase_opt, wallet, stash, context) + val actor = new InteractiveTxBuilder(replyTo, sessionId, nodeParams, channelParams, localCommitParams, remoteCommitParams, channelKeys, fundingParams, purpose, localPushAmount, remotePushAmount, liquidityPurchase_opt, wallet, stash, context) actor.start() } case Abort => Behaviors.stopped @@ -444,6 +447,8 @@ private class InteractiveTxBuilder(replyTo: ActorRef[InteractiveTxBuilder.Respon sessionId: ByteVector32, nodeParams: NodeParams, channelParams: ChannelParams, + localCommitParams: CommitParams, + remoteCommitParams: CommitParams, channelKeys: ChannelKeys, fundingParams: InteractiveTxBuilder.InteractiveTxParams, purpose: Purpose, @@ -739,7 +744,7 @@ private class InteractiveTxBuilder(replyTo: ActorRef[InteractiveTxBuilder.Respon // operation, remote post-splice reserve may actually be worse than before, but that's not their fault. } else { // If remote removes funds from the channel, it must meet reserve requirements post-splice - val remoteReserve = channelParams.remoteChannelReserveForCapacity(fundingParams.fundingAmount, isSplice = true) + val remoteReserve = (fundingParams.fundingAmount / 100).max(fundingParams.dustLimit) if (sharedOutput.remoteAmount < remoteReserve) { log.warn("invalid interactive tx: peer takes too much funds out and falls below the channel reserve ({} < {})", sharedOutput.remoteAmount, remoteReserve) return Left(InvalidCompleteInteractiveTx(fundingParams.channelId)) @@ -833,14 +838,15 @@ private class InteractiveTxBuilder(replyTo: ActorRef[InteractiveTxBuilder.Respon val fundingTx = completeTx.buildUnsignedTx() val fundingOutputIndex = fundingTx.txOut.indexWhere(_.publicKeyScript == fundingPubkeyScript) val liquidityFee = fundingParams.liquidityFees(liquidityPurchase_opt) - val localCommitmentKeys = LocalCommitmentKeys(channelParams, channelKeys, purpose.localCommitIndex) - val remoteCommitmentKeys = RemoteCommitmentKeys(channelParams, channelKeys, purpose.remotePerCommitmentPoint) - Funding.makeCommitTxs(channelParams, + val localCommitmentKeys = LocalCommitmentKeys(channelParams, channelKeys, purpose.localCommitIndex, fundingParams.commitmentFormat) + val remoteCommitmentKeys = RemoteCommitmentKeys(channelParams, channelKeys, purpose.remotePerCommitmentPoint, fundingParams.commitmentFormat) + Funding.makeCommitTxs(channelParams, localCommitParams, remoteCommitParams, fundingAmount = fundingParams.fundingAmount, toLocal = completeTx.sharedOutput.localAmount - localPushAmount + remotePushAmount - liquidityFee, toRemote = completeTx.sharedOutput.remoteAmount - remotePushAmount + localPushAmount + liquidityFee, localHtlcs = purpose.localHtlcs, purpose.commitTxFeerate, + fundingParams.commitmentFormat, fundingTxIndex = purpose.fundingTxIndex, fundingTx.txid, fundingOutputIndex, localFundingKey, fundingParams.remoteFundingPubKey, @@ -851,16 +857,16 @@ private class InteractiveTxBuilder(replyTo: ActorRef[InteractiveTxBuilder.Respon unlockAndStop(completeTx) case Right((localSpec, localCommitTx, remoteSpec, remoteCommitTx, sortedHtlcTxs)) => require(fundingTx.txOut(fundingOutputIndex).publicKeyScript == localCommitTx.input.txOut.publicKeyScript, "pubkey script mismatch!") - channelParams.commitmentFormat match { + fundingParams.commitmentFormat match { case _: SegwitV0CommitmentFormat => val localSigOfRemoteTx = remoteCommitTx.sign(localFundingKey, fundingParams.remoteFundingPubKey).sig val htlcSignatures = sortedHtlcTxs.map(_.localSig(remoteCommitmentKeys)).toList val localCommitSig = CommitSig(fundingParams.channelId, localSigOfRemoteTx, htlcSignatures) - val localCommit = UnsignedLocalCommit(purpose.localCommitIndex, localSpec, localCommitTx.tx.txid, localCommitTx.input) + val localCommit = UnsignedLocalCommit(purpose.localCommitIndex, localSpec, localCommitTx.tx.txid) val remoteCommit = RemoteCommit(purpose.remoteCommitIndex, remoteSpec, remoteCommitTx.tx.txid, purpose.remotePerCommitmentPoint) signFundingTx(completeTx, localCommitSig, localCommit, remoteCommit) - case _: SimpleTaprootChannelCommitmentFormat => ??? - + case _: SimpleTaprootChannelCommitmentFormat => + ??? } } } @@ -897,7 +903,9 @@ private class InteractiveTxBuilder(replyTo: ActorRef[InteractiveTxBuilder.Respon fundingParams, purpose.fundingTxIndex, signedTx, + localCommitParams, Left(localCommit), + remoteCommitParams, remoteCommit, liquidityPurchase_opt.map(_.basicInfo(isBuyer = fundingParams.isInitiator)) ) @@ -1020,7 +1028,7 @@ object InteractiveTxSigningSession { // +-------+ +-------+ /** A local commitment for which we haven't received our peer's signatures. */ - case class UnsignedLocalCommit(index: Long, spec: CommitmentSpec, txId: TxId, input: InputInfo) + case class UnsignedLocalCommit(index: Long, spec: CommitmentSpec, txId: TxId) private def shouldSignFirst(isInitiator: Boolean, channelParams: ChannelParams, tx: SharedTransaction): Boolean = { val sharedAmountIn = tx.sharedInput_opt.map(_.txOut.amount).getOrElse(0 sat) @@ -1092,10 +1100,11 @@ object InteractiveTxSigningSession { case class WaitingForSigs(fundingParams: InteractiveTxParams, fundingTxIndex: Long, fundingTx: PartiallySignedSharedTransaction, + localCommitParams: CommitParams, localCommit: Either[UnsignedLocalCommit, LocalCommit], + remoteCommitParams: CommitParams, remoteCommit: RemoteCommit, liquidityPurchase_opt: Option[LiquidityAds.PurchaseBasicInfo]) extends InteractiveTxSigningSession { - val commitInput: InputInfo = localCommit.fold(_.input, _.input) val localCommitIndex: Long = localCommit.fold(_.index, _.index) // This value tells our peer whether we need them to retransmit their commit_sig on reconnection or not. val nextLocalCommitmentNumber: Long = localCommit match { @@ -1103,15 +1112,26 @@ object InteractiveTxSigningSession { case Right(commit) => commit.index + 1 } + def localFundingKey(channelKeys: ChannelKeys): PrivateKey = channelKeys.fundingKey(fundingTxIndex) + + def commitInput(fundingKey: PrivateKey): InputInfo = { + val fundingScript = Transactions.makeFundingScript(fundingKey.publicKey, fundingParams.remoteFundingPubKey, fundingParams.commitmentFormat).pubkeyScript + val fundingOutput = OutPoint(fundingTx.txId, fundingTx.tx.buildUnsignedTx().txOut.indexWhere(txOut => txOut.amount == fundingParams.fundingAmount && txOut.publicKeyScript == fundingScript)) + InputInfo(fundingOutput, TxOut(fundingParams.fundingAmount, fundingScript)) + } + + def commitInput(channelKeys: ChannelKeys): InputInfo = commitInput(localFundingKey(channelKeys)) + def receiveCommitSig(channelParams: ChannelParams, channelKeys: ChannelKeys, remoteCommitSig: CommitSig, currentBlockHeight: BlockHeight)(implicit log: LoggingAdapter): Either[ChannelException, InteractiveTxSigningSession] = { localCommit match { case Left(unsignedLocalCommit) => - val fundingKey = channelKeys.fundingKey(fundingTxIndex) - val commitKeys = LocalCommitmentKeys(channelParams, channelKeys, localCommitIndex) - LocalCommit.fromCommitSig(channelParams, commitKeys, fundingTx.txId, fundingKey, fundingParams.remoteFundingPubKey, commitInput, remoteCommitSig, localCommitIndex, unsignedLocalCommit.spec).map { signedLocalCommit => + val fundingKey = localFundingKey(channelKeys) + val commitKeys = LocalCommitmentKeys(channelParams, channelKeys, localCommitIndex, fundingParams.commitmentFormat) + val fundingOutput = commitInput(fundingKey) + LocalCommit.fromCommitSig(channelParams, localCommitParams, commitKeys, fundingTx.txId, fundingKey, fundingParams.remoteFundingPubKey, fundingOutput, remoteCommitSig, localCommitIndex, unsignedLocalCommit.spec, fundingParams.commitmentFormat).map { signedLocalCommit => if (shouldSignFirst(fundingParams.isInitiator, channelParams, fundingTx.tx)) { val fundingStatus = LocalFundingStatus.DualFundedUnconfirmedFundingTx(fundingTx, currentBlockHeight, fundingParams, liquidityPurchase_opt) - val commitment = Commitment(fundingTxIndex, remoteCommit.index, fundingParams.remoteFundingPubKey, fundingStatus, RemoteFundingStatus.NotLocked, signedLocalCommit, remoteCommit, None) + val commitment = Commitment(fundingTxIndex, remoteCommit.index, fundingOutput.outPoint, fundingParams.fundingAmount, fundingParams.remoteFundingPubKey, fundingStatus, RemoteFundingStatus.NotLocked, fundingParams.commitmentFormat, localCommitParams, signedLocalCommit, remoteCommitParams, remoteCommit, None) SendingSigs(fundingStatus, commitment, fundingTx.localSigs) } else { this.copy(localCommit = Right(signedLocalCommit)) @@ -1135,8 +1155,9 @@ object InteractiveTxSigningSession { Left(f) case Right(fullySignedTx) => log.info("interactive-tx fully signed with {} local inputs, {} remote inputs, {} local outputs and {} remote outputs", fullySignedTx.tx.localInputs.length, fullySignedTx.tx.remoteInputs.length, fullySignedTx.tx.localOutputs.length, fullySignedTx.tx.remoteOutputs.length) + val fundingOutput = commitInput(channelKeys) val fundingStatus = LocalFundingStatus.DualFundedUnconfirmedFundingTx(fullySignedTx, currentBlockHeight, fundingParams, liquidityPurchase_opt) - val commitment = Commitment(fundingTxIndex, remoteCommit.index, fundingParams.remoteFundingPubKey, fundingStatus, RemoteFundingStatus.NotLocked, signedLocalCommit, remoteCommit, None) + val commitment = Commitment(fundingTxIndex, remoteCommit.index, fundingOutput.outPoint, fundingParams.fundingAmount, fundingParams.remoteFundingPubKey, fundingStatus, RemoteFundingStatus.NotLocked, fundingParams.commitmentFormat, localCommitParams, signedLocalCommit, remoteCommitParams, remoteCommit, None) Right(SendingSigs(fundingStatus, commitment, fullySignedTx.localSigs)) } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/CommitmentKeys.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/CommitmentKeys.scala index 564a425592..36c879b4c8 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/CommitmentKeys.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/CommitmentKeys.scala @@ -19,7 +19,7 @@ package fr.acinq.eclair.crypto.keymanager import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} import fr.acinq.eclair.Features import fr.acinq.eclair.channel.ChannelParams -import fr.acinq.eclair.transactions.Transactions.{AnchorOutputsCommitmentFormat, DefaultCommitmentFormat, SimpleTaprootChannelCommitmentFormat} +import fr.acinq.eclair.transactions.Transactions.{AnchorOutputsCommitmentFormat, CommitmentFormat, DefaultCommitmentFormat, SimpleTaprootChannelCommitmentFormat} /** * Created by t-bast on 10/04/2025. @@ -67,14 +67,14 @@ case class LocalCommitmentKeys(ourDelayedPaymentKey: PrivateKey, } object LocalCommitmentKeys { - def apply(params: ChannelParams, channelKeys: ChannelKeys, localCommitIndex: Long): LocalCommitmentKeys = { + def apply(params: ChannelParams, channelKeys: ChannelKeys, localCommitIndex: Long, commitmentFormat: CommitmentFormat): LocalCommitmentKeys = { val localPerCommitmentPoint = channelKeys.commitmentPoint(localCommitIndex) LocalCommitmentKeys( ourDelayedPaymentKey = channelKeys.delayedPaymentKey(localPerCommitmentPoint), - theirPaymentPublicKey = params.commitmentFormat match { + theirPaymentPublicKey = commitmentFormat match { case DefaultCommitmentFormat if params.channelFeatures.hasFeature(Features.StaticRemoteKey) => params.remoteParams.paymentBasepoint case DefaultCommitmentFormat => ChannelKeys.remotePerCommitmentPublicKey(params.remoteParams.paymentBasepoint, localPerCommitmentPoint) - case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => params.remoteParams.paymentBasepoint + case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => params.remoteParams.paymentBasepoint }, ourPaymentBasePoint = params.localParams.walletStaticPaymentBasepoint.getOrElse(channelKeys.paymentBasePoint), ourHtlcKey = channelKeys.htlcKey(localPerCommitmentPoint), @@ -116,11 +116,11 @@ case class RemoteCommitmentKeys(ourPaymentKey: Either[PublicKey, PrivateKey], } object RemoteCommitmentKeys { - def apply(params: ChannelParams, channelKeys: ChannelKeys, remotePerCommitmentPoint: PublicKey): RemoteCommitmentKeys = { + def apply(params: ChannelParams, channelKeys: ChannelKeys, remotePerCommitmentPoint: PublicKey, commitmentFormat: CommitmentFormat): RemoteCommitmentKeys = { RemoteCommitmentKeys( ourPaymentKey = params.localParams.walletStaticPaymentBasepoint match { case Some(walletPublicKey) => Left(walletPublicKey) - case None => params.commitmentFormat match { + case None => commitmentFormat match { // Note that if we're using option_static_remotekey, a walletStaticPaymentBasepoint will be provided. case DefaultCommitmentFormat => Right(channelKeys.paymentKey(remotePerCommitmentPoint)) case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => Right(channelKeys.paymentBaseSecret) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/OpenChannelInterceptor.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/OpenChannelInterceptor.scala index d670ce61df..7a481db36c 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/OpenChannelInterceptor.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/OpenChannelInterceptor.scala @@ -77,16 +77,11 @@ object OpenChannelInterceptor { } } - def makeChannelParams(nodeParams: NodeParams, initFeatures: Features[InitFeature], upfrontShutdownScript_opt: Option[ByteVector], walletStaticPaymentBasepoint_opt: Option[PublicKey], isChannelOpener: Boolean, paysCommitTxFees: Boolean, dualFunded: Boolean, fundingAmount: Satoshi, unlimitedMaxHtlcValueInFlight: Boolean): LocalChannelParams = { + def makeChannelParams(nodeParams: NodeParams, initFeatures: Features[InitFeature], upfrontShutdownScript_opt: Option[ByteVector], walletStaticPaymentBasepoint_opt: Option[PublicKey], isChannelOpener: Boolean, paysCommitTxFees: Boolean, dualFunded: Boolean, fundingAmount: Satoshi): LocalChannelParams = { LocalChannelParams( nodeParams.nodeId, nodeParams.channelKeyManager.newFundingKeyPath(isChannelOpener), - dustLimit = nodeParams.channelConf.dustLimit, - maxHtlcValueInFlightMsat = nodeParams.channelConf.maxHtlcValueInFlight(fundingAmount, unlimitedMaxHtlcValueInFlight), - initialRequestedChannelReserve_opt = if (dualFunded) None else Some((fundingAmount * nodeParams.channelConf.reserveToFundingRatio).max(nodeParams.channelConf.dustLimit)), // BOLT #2: make sure that our reserve is above our dust limit - htlcMinimum = nodeParams.channelConf.htlcMinimum, - toRemoteDelay = nodeParams.channelConf.toRemoteDelay, // we choose their delay - maxAcceptedHtlcs = nodeParams.channelConf.maxAcceptedHtlcs, + initialRequestedChannelReserve_opt = if (dualFunded) None else Some((fundingAmount * nodeParams.channelConf.reserveToFundingRatio).max(nodeParams.channelConf.dustLimit)), // BOLT #2: make sure that our reserve is above our dust limit, isChannelOpener = isChannelOpener, paysCommitTxFees = paysCommitTxFees, upfrontShutdownScript_opt = upfrontShutdownScript_opt, @@ -128,7 +123,7 @@ private class OpenChannelInterceptor(peer: ActorRef[Any], val upfrontShutdownScript = Features.canUseFeature(request.localFeatures, request.remoteFeatures, Features.UpfrontShutdownScript) // If we're purchasing liquidity, we expect our peer to contribute at least the amount we're purchasing, otherwise we'll cancel the funding attempt. val expectedFundingAmount = request.open.fundingAmount + request.open.requestFunding_opt.map(_.requestedAmount).getOrElse(0 sat) - val localParams = createLocalParams(nodeParams, request.localFeatures, upfrontShutdownScript, channelType, isChannelOpener = true, paysCommitTxFees = true, dualFunded = dualFunded, expectedFundingAmount, request.open.disableMaxHtlcValueInFlight) + val localParams = createLocalParams(nodeParams, request.localFeatures, upfrontShutdownScript, channelType, isChannelOpener = true, paysCommitTxFees = true, dualFunded = dualFunded, expectedFundingAmount) peer ! Peer.SpawnChannelInitiator(request.replyTo, request.open, ChannelConfig.standard, channelType, localParams) waitForRequest() } @@ -161,8 +156,7 @@ private class OpenChannelInterceptor(peer: ActorRef[Any], isChannelOpener = false, paysCommitTxFees = nonInitiatorPaysCommitTxFees, dualFunded = dualFunded, - fundingAmount = request.fundingAmount, - disableMaxHtlcValueInFlight = false + fundingAmount = request.fundingAmount ) checkRateLimits(request, channelType, localParams) case Left(ex) => @@ -196,10 +190,7 @@ private class OpenChannelInterceptor(peer: ActorRef[Any], request.open.fold(_ => None, _.requestFunding_opt) match { case Some(requestFunding) if Features.canUseFeature(request.localFeatures, request.remoteFeatures, Features.OnTheFlyFunding) && localParams.paysCommitTxFees => val addFunding = LiquidityAds.AddFunding(requestFunding.requestedAmount, nodeParams.liquidityAdsConfig.rates_opt) - // Now that we know how much we'll contribute to the funding transaction, we update the maxHtlcValueInFlight. - val maxHtlcValueInFlight = Seq(localParams.maxHtlcValueInFlightMsat, nodeParams.channelConf.maxHtlcValueInFlight(request.fundingAmount + addFunding.fundingAmount, unlimited = false)).max - val localParams1 = localParams.copy(maxHtlcValueInFlightMsat = maxHtlcValueInFlight) - val accept = SpawnChannelNonInitiator(request.open, ChannelConfig.standard, channelType, Some(addFunding), localParams1, request.peerConnection.toClassic) + val accept = SpawnChannelNonInitiator(request.open, ChannelConfig.standard, channelType, Some(addFunding), localParams, request.peerConnection.toClassic) checkNoExistingChannel(request, accept) case _ => // We don't honor liquidity ads for new channels: node operators should use plugin for that. @@ -302,7 +293,7 @@ private class OpenChannelInterceptor(peer: ActorRef[Any], } } - private def createLocalParams(nodeParams: NodeParams, initFeatures: Features[InitFeature], upfrontShutdownScript: Boolean, channelType: SupportedChannelType, isChannelOpener: Boolean, paysCommitTxFees: Boolean, dualFunded: Boolean, fundingAmount: Satoshi, disableMaxHtlcValueInFlight: Boolean): LocalChannelParams = { + private def createLocalParams(nodeParams: NodeParams, initFeatures: Features[InitFeature], upfrontShutdownScript: Boolean, channelType: SupportedChannelType, isChannelOpener: Boolean, paysCommitTxFees: Boolean, dualFunded: Boolean, fundingAmount: Satoshi): LocalChannelParams = { makeChannelParams( nodeParams, initFeatures, // Note that if our bitcoin node is configured to use taproot, this will generate a taproot script. @@ -314,8 +305,7 @@ private class OpenChannelInterceptor(peer: ActorRef[Any], isChannelOpener = isChannelOpener, paysCommitTxFees = paysCommitTxFees, dualFunded = dualFunded, - fundingAmount, - disableMaxHtlcValueInFlight + fundingAmount ) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala index 30e343a3ea..36eaa37666 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala @@ -602,8 +602,8 @@ object OriginSerializer extends MinimalSerializer({ }) // @formatter:off -case class CommitmentJson(fundingTxIndex: Long, fundingTx: InputInfo, localFunding: LocalFundingStatus, remoteFunding: RemoteFundingStatus, localCommit: LocalCommit, remoteCommit: RemoteCommit, nextRemoteCommit: Option[RemoteCommit]) -object CommitmentSerializer extends ConvertClassSerializer[Commitment](c => CommitmentJson(c.fundingTxIndex, c.commitInput, c.localFundingStatus, c.remoteFundingStatus, c.localCommit, c.remoteCommit, c.nextRemoteCommit_opt.map(_.commit))) +case class CommitmentJson(fundingTxIndex: Long, fundingInput: OutPoint, fundingAmount: Satoshi, localFunding: LocalFundingStatus, remoteFunding: RemoteFundingStatus, commitmentFormat: String, localCommitParams: CommitParams, localCommit: LocalCommit, remoteCommitParams: CommitParams, remoteCommit: RemoteCommit, nextRemoteCommit: Option[RemoteCommit]) +object CommitmentSerializer extends ConvertClassSerializer[Commitment](c => CommitmentJson(c.fundingTxIndex, c.fundingInput, c.fundingAmount, c.localFundingStatus, c.remoteFundingStatus, c.commitmentFormat.toString, c.localCommitParams, c.localCommit, c.remoteCommitParams, c.remoteCommit, c.nextRemoteCommit_opt.map(_.commit))) // @formatter:on // @formatter:off diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/OnTheFlyFunding.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/OnTheFlyFunding.scala index 14b8226a3e..08549f8d82 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/OnTheFlyFunding.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/OnTheFlyFunding.scala @@ -301,7 +301,7 @@ object OnTheFlyFunding { private def relay(data: DATA_NORMAL): Behavior[Command] = { context.log.debug("relaying {} on-the-fly HTLCs that have been funded", cmd.proposed.size) - val htlcMinimum = data.commitments.channelParams.remoteCommitParams.htlcMinimum + val htlcMinimum = data.commitments.latest.remoteCommitParams.htlcMinimum val cmdAdapter = context.messageAdapter[CommandResponse[CMD_ADD_HTLC]](WrappedCommandResponse) val htlcSettledAdapter = context.messageAdapter[RES_ADD_SETTLED[Origin.Hot, HtlcResult]](WrappedHtlcSettled) cmd.proposed.foldLeft(cmd.status.remainingFees) { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/reputation/Reputation.scala b/eclair-core/src/main/scala/fr/acinq/eclair/reputation/Reputation.scala index aee51b7b59..3f4067a413 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/reputation/Reputation.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/reputation/Reputation.scala @@ -17,7 +17,7 @@ package fr.acinq.eclair.reputation import fr.acinq.bitcoin.scalacompat.ByteVector32 -import fr.acinq.eclair.channel.{ChannelJammingException, ChannelParams, Commitments, IncomingConfidenceTooLow, OutgoingConfidenceTooLow, TooManySmallHtlcs} +import fr.acinq.eclair.channel._ import fr.acinq.eclair.transactions.DirectedHtlc import fr.acinq.eclair.wire.protocol.UpdateAddHtlc import fr.acinq.eclair.{MilliSatoshi, TimestampMilli} @@ -109,8 +109,8 @@ case class Reputation(pastScores: Map[Int, PastScore], pending: Map[HtlcId, Pend } object Reputation { - val endorsementLevels = 8 - val maxEndorsement = endorsementLevels - 1 + private val endorsementLevels = 8 + val maxEndorsement: Int = endorsementLevels - 1 case class Config(enabled: Boolean, halfLife: FiniteDuration, maxRelayDuration: FiniteDuration, pendingMultiplier: Double) @@ -120,29 +120,29 @@ object Reputation { * @param incomingConfidence Confidence that the outgoing HTLC will succeed given the reputation of the incoming peer */ case class Score(incomingConfidence: Double, outgoingConfidence: Double) { - val endorsement = toEndorsement(incomingConfidence) + val endorsement: Int = toEndorsement(incomingConfidence) - def checkOutgoingChannelOccupancy(outgoingHtlcs: Seq[UpdateAddHtlc], params: ChannelParams): Either[ChannelJammingException, Unit] = { - val maxAcceptedHtlcs = Seq(params.localCommitParams.maxAcceptedHtlcs, params.remoteCommitParams.maxAcceptedHtlcs).min + def checkOutgoingChannelOccupancy(channelId: ByteVector32, commitment: Commitment, outgoingHtlcs: Seq[UpdateAddHtlc]): Either[ChannelJammingException, Unit] = { + val maxAcceptedHtlcs = Seq(commitment.localCommitParams.maxAcceptedHtlcs, commitment.remoteCommitParams.maxAcceptedHtlcs).min for ((amountMsat, i) <- outgoingHtlcs.map(_.amountMsat).sorted.zipWithIndex) { // We want to allow some small HTLCs but still keep slots for larger ones. // We never want to reject HTLCs of size above `maxHtlcAmount / maxAcceptedHtlcs` as too small because we want to allow filling all the slots with HTLCs of equal sizes. // We use exponentially spaced thresholds in between. - if (amountMsat.toLong < 1 || amountMsat.toLong.toDouble < math.pow(params.maxHtlcValueInFlight.toLong.toDouble / maxAcceptedHtlcs, i / maxAcceptedHtlcs)) { - return Left(TooManySmallHtlcs(params.channelId, number = i + 1, below = amountMsat)) + if (amountMsat.toLong < 1 || amountMsat.toLong.toDouble < math.pow(commitment.maxHtlcValueInFlight.toLong.toDouble / maxAcceptedHtlcs, i / maxAcceptedHtlcs)) { + return Left(TooManySmallHtlcs(channelId, number = i + 1, below = amountMsat)) } } val htlcValueInFlight = outgoingHtlcs.map(_.amountMsat).sum val slotsOccupancy = outgoingHtlcs.size.toDouble / maxAcceptedHtlcs - val valueOccupancy = htlcValueInFlight.toLong.toDouble / params.maxHtlcValueInFlight.toLong.toDouble + val valueOccupancy = htlcValueInFlight.toLong.toDouble / commitment.maxHtlcValueInFlight.toLong.toDouble val occupancy = slotsOccupancy max valueOccupancy // Because there are only 8 endorsement levels, the highest endorsement corresponds to a confidence between 87.5% and 100%. // So even for well-behaved peers setting the highest endorsement we still expect a confidence of less than 93.75%. // To compensate for that we add a tolerance of 10% that's also useful for nodes without history. if (incomingConfidence + 0.1 < occupancy) { - return Left(IncomingConfidenceTooLow(params.channelId, incomingConfidence, occupancy)) + return Left(IncomingConfidenceTooLow(channelId, incomingConfidence, occupancy)) } Right(()) @@ -157,7 +157,7 @@ object Reputation { } case object Score { - val max = Score(1.0, 1.0) + val max: Score = Score(1.0, 1.0) def fromEndorsement(endorsement: Int): Score = Score((endorsement + 0.5) / 8, 1.0) } @@ -167,9 +167,9 @@ object Reputation { def incomingOccupancy(commitments: Commitments): Double = { commitments.active.map(commitment => { val incomingHtlcs = commitment.localCommit.spec.htlcs.collect(DirectedHtlc.incoming) - val slotsOccupancy = incomingHtlcs.size.toDouble / (commitments.channelParams.localCommitParams.maxAcceptedHtlcs min commitments.channelParams.remoteCommitParams.maxAcceptedHtlcs) + val slotsOccupancy = commitments.active.map(c => incomingHtlcs.size.toDouble / (c.localCommitParams.maxAcceptedHtlcs min c.remoteCommitParams.maxAcceptedHtlcs)).max val htlcValueInFlight = incomingHtlcs.toSeq.map(_.amountMsat).sum - val valueOccupancy = htlcValueInFlight.toLong.toDouble / commitments.channelParams.maxHtlcValueInFlight.toLong.toDouble + val valueOccupancy = commitments.active.map(c => htlcValueInFlight.toLong.toDouble / c.maxHtlcValueInFlight.toLong.toDouble).max slotsOccupancy max valueOccupancy }).max } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala index cb6843053e..60d699c21d 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala @@ -115,6 +115,8 @@ object Transactions { override val htlcOfferedPenaltyWeight = 572 override val htlcReceivedPenaltyWeight = 577 override val claimHtlcPenaltyWeight = 484 + + override def toString: String = "legacy" } /** @@ -149,13 +151,17 @@ object Transactions { * Don't use this commitment format unless you know what you're doing! * See https://lists.linuxfoundation.org/pipermail/lightning-dev/2020-September/002796.html for details. */ - case object UnsafeLegacyAnchorOutputsCommitmentFormat extends AnchorOutputsCommitmentFormat + case object UnsafeLegacyAnchorOutputsCommitmentFormat extends AnchorOutputsCommitmentFormat { + override def toString: String = "unsafe_anchor_outputs" + } /** * This commitment format removes the fees from the pre-signed 2nd-stage htlc transactions to fix the fee inflating * attack against [[UnsafeLegacyAnchorOutputsCommitmentFormat]]. */ - case object ZeroFeeHtlcTxAnchorOutputsCommitmentFormat extends AnchorOutputsCommitmentFormat + case object ZeroFeeHtlcTxAnchorOutputsCommitmentFormat extends AnchorOutputsCommitmentFormat { + override def toString: String = "anchor_outputs" + } sealed trait TaprootCommitmentFormat extends CommitmentFormat @@ -180,13 +186,15 @@ object Transactions { override val claimHtlcPenaltyWeight = 396 } - case object LegacySimpleTaprootChannelCommitmentFormat extends SimpleTaprootChannelCommitmentFormat + case object LegacySimpleTaprootChannelCommitmentFormat extends SimpleTaprootChannelCommitmentFormat { + override def toString: String = "unsafe_simple_taproot" + } - case object ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat extends SimpleTaprootChannelCommitmentFormat + case object ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat extends SimpleTaprootChannelCommitmentFormat { + override def toString: String = "simple_taproot" + } - // TODO: we're currently keeping the now unused redeemScript to avoid a painful codec update. When creating v5 codecs - // (for taproot channels), don't forget to remove this field from the InputInfo class! - case class InputInfo(outPoint: OutPoint, txOut: TxOut, unusedRedeemScript: ByteVector) + case class InputInfo(outPoint: OutPoint, txOut: TxOut) // @formatter:off /** This trait contains redeem information necessary to spend different types of segwit inputs. */ @@ -598,7 +606,7 @@ object Transactions { outputIndex: Int, commitmentFormat: CommitmentFormat): UnsignedHtlcSuccessTx = { val htlc = output.htlc.add - val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), ByteVector.empty) + val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex)) val tx = Transaction( version = 2, txIn = TxIn(input.outPoint, ByteVector.empty, getHtlcTxInputSequence(commitmentFormat)) :: Nil, @@ -652,7 +660,7 @@ object Transactions { outputIndex: Int, commitmentFormat: CommitmentFormat): UnsignedHtlcTimeoutTx = { val htlc = output.htlc.add - val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), ByteVector.empty) + val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex)) val tx = Transaction( version = 2, txIn = TxIn(input.outPoint, ByteVector.empty, getHtlcTxInputSequence(commitmentFormat)) :: Nil, @@ -699,7 +707,7 @@ object Transactions { findPubKeyScriptIndex(htlcTx, pubkeyScript) match { case Left(skip) => Left(skip) case Right(outputIndex) => - val input = InputInfo(OutPoint(htlcTx, outputIndex), htlcTx.txOut(outputIndex), ByteVector.empty) + val input = InputInfo(OutPoint(htlcTx, outputIndex), htlcTx.txOut(outputIndex)) val amount = input.txOut.amount - weight2fee(feerate, commitmentFormat.htlcDelayedWeight) val tx = Transaction( version = 2, @@ -762,7 +770,7 @@ object Transactions { def findInput(commitTx: Transaction, outputs: Seq[CommitmentOutput], htlc: UpdateAddHtlc): Option[InputInfo] = { outputs.zipWithIndex.collectFirst { case (OutHtlc(outgoingHtlc, _, _), outputIndex) if outgoingHtlc.add.id == htlc.id => - InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), ByteVector.empty) + InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex)) } } @@ -822,7 +830,7 @@ object Transactions { def findInput(commitTx: Transaction, outputs: Seq[CommitmentOutput], htlc: UpdateAddHtlc): Option[InputInfo] = { outputs.zipWithIndex.collectFirst { case (InHtlc(incomingHtlc, _, _), outputIndex) if incomingHtlc.add.id == htlc.id => - InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), ByteVector.empty) + InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex)) } } @@ -903,7 +911,7 @@ object Transactions { def findInput(commitTx: Transaction, fundingKey: PrivateKey, commitKeys: LocalCommitmentKeys, commitmentFormat: CommitmentFormat): Either[TxGenerationSkipped, InputInfo] = { val pubKeyScript = redeemInfo(fundingKey.publicKey, commitKeys.publicKeys, commitmentFormat).pubkeyScript - findPubKeyScriptIndex(commitTx, pubKeyScript).map(outputIndex => InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), ByteVector.empty)) + findPubKeyScriptIndex(commitTx, pubKeyScript).map(outputIndex => InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex))) } def createUnsignedTx(fundingKey: PrivateKey, commitKeys: LocalCommitmentKeys, commitTx: Transaction, commitmentFormat: CommitmentFormat): Either[TxGenerationSkipped, ClaimLocalAnchorTx] = { @@ -940,7 +948,7 @@ object Transactions { def findInput(commitTx: Transaction, fundingKey: PrivateKey, commitKeys: RemoteCommitmentKeys, commitmentFormat: CommitmentFormat): Either[TxGenerationSkipped, InputInfo] = { val pubKeyScript = redeemInfo(fundingKey.publicKey, commitKeys.publicKeys, commitmentFormat).pubkeyScript - findPubKeyScriptIndex(commitTx, pubKeyScript).map(outputIndex => InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), ByteVector.empty)) + findPubKeyScriptIndex(commitTx, pubKeyScript).map(outputIndex => InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex))) } def createUnsignedTx(fundingKey: PrivateKey, commitKeys: RemoteCommitmentKeys, commitTx: Transaction, commitmentFormat: CommitmentFormat): Either[TxGenerationSkipped, ClaimRemoteAnchorTx] = { @@ -973,7 +981,7 @@ object Transactions { commitKeys.ourPaymentKey match { case Left(_) => Left(OutputAlreadyInWallet) case Right(_) => - val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), ByteVector.empty) + val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex)) val amount = input.txOut.amount - weight2fee(feerate, commitmentFormat.toRemoteWeight) val tx = Transaction( version = 2, @@ -1026,7 +1034,7 @@ object Transactions { commitKeys.ourPaymentKey match { case Left(_) => Left(OutputAlreadyInWallet) case Right(_) => - val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), ByteVector.empty) + val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex)) val amount = input.txOut.amount - weight2fee(feerate, commitmentFormat.toRemoteWeight) val tx = Transaction( version = 2, @@ -1075,7 +1083,7 @@ object Transactions { findPubKeyScriptIndex(commitTx, redeemInfo.pubkeyScript) match { case Left(skip) => Left(skip) case Right(outputIndex) => - val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), ByteVector.empty) + val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex)) val amount = input.txOut.amount - weight2fee(feerate, commitmentFormat.toLocalDelayedWeight) val tx = Transaction( version = 2, @@ -1123,7 +1131,7 @@ object Transactions { findPubKeyScriptIndex(commitTx, redeemInfo.pubkeyScript) match { case Left(skip) => Left(skip) case Right(outputIndex) => - val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), ByteVector.empty) + val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex)) val amount = input.txOut.amount - weight2fee(feerate, commitmentFormat.mainPenaltyWeight) val tx = Transaction( version = 2, @@ -1200,7 +1208,7 @@ object Transactions { localFinalScriptPubKey: ByteVector, feerate: FeeratePerKw, commitmentFormat: CommitmentFormat): Either[TxGenerationSkipped, HtlcPenaltyTx] = { - val input = InputInfo(OutPoint(commitTx, htlcOutputIndex), commitTx.txOut(htlcOutputIndex), ByteVector.empty) + val input = InputInfo(OutPoint(commitTx, htlcOutputIndex), commitTx.txOut(htlcOutputIndex)) val amount = input.txOut.amount - weight2fee(feerate, redeemDetails.weight) val tx = Transaction( version = 2, @@ -1252,7 +1260,7 @@ object Transactions { // Note that we check *all* outputs of the tx, because it could spend a batch of HTLC outputs from the commit tx. htlcTx.txOut.zipWithIndex.collect { case (txOut, outputIndex) if txOut.publicKeyScript == redeemInfo.pubkeyScript => - val input = InputInfo(OutPoint(htlcTx, outputIndex), htlcTx.txOut(outputIndex), ByteVector.empty) + val input = InputInfo(OutPoint(htlcTx, outputIndex), htlcTx.txOut(outputIndex)) val amount = input.txOut.amount - weight2fee(feerate, commitmentFormat.claimHtlcPenaltyWeight) val tx = Transaction( version = 2, @@ -1502,6 +1510,19 @@ object Transactions { } } + def makeFundingScript(localFundingKey: PublicKey, remoteFundingKey: PublicKey, commitmentFormat: CommitmentFormat): RedeemInfo = { + commitmentFormat match { + case _: SegwitV0CommitmentFormat => RedeemInfo.P2wsh(Script.write(multiSig2of2(localFundingKey, remoteFundingKey))) + case _: SimpleTaprootChannelCommitmentFormat => RedeemInfo.TaprootKeyPath(Taproot.musig2Aggregate(localFundingKey, remoteFundingKey), None) + } + } + + def makeFundingInputInfo(fundingTxId: TxId, fundingOutputIndex: Int, fundingAmount: Satoshi, localFundingKey: PublicKey, remoteFundingKey: PublicKey, commitmentFormat: CommitmentFormat): InputInfo = { + val redeemInfo = makeFundingScript(localFundingKey, remoteFundingKey, commitmentFormat) + val fundingTxOut = TxOut(fundingAmount, redeemInfo.pubkeyScript) + InputInfo(OutPoint(fundingTxId, fundingOutputIndex), fundingTxOut) + } + // @formatter:off /** We always create multiple versions of each closing transaction, where fees are either paid by us or by our peer. */ sealed trait SimpleClosingTxFee diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecs.scala index 779490fdab..b8f7b207aa 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecs.scala @@ -22,6 +22,7 @@ import fr.acinq.eclair.wire.internal.channel.version1.ChannelCodecs1 import fr.acinq.eclair.wire.internal.channel.version2.ChannelCodecs2 import fr.acinq.eclair.wire.internal.channel.version3.ChannelCodecs3 import fr.acinq.eclair.wire.internal.channel.version4.ChannelCodecs4 +import fr.acinq.eclair.wire.internal.channel.version5.ChannelCodecs5 import grizzled.slf4j.Logging import scodec.Codec import scodec.codecs.{byte, discriminated} @@ -67,7 +68,8 @@ object ChannelCodecs extends Logging { * More info here: https://github.com/scodec/scodec/issues/122 */ val channelDataCodec: Codec[PersistentChannelData] = discriminated[PersistentChannelData].by(byte) - .typecase(4, ChannelCodecs4.channelDataCodec) + .typecase(5, ChannelCodecs5.channelDataCodec) + .typecase(4, ChannelCodecs4.channelDataCodec.decodeOnly) .typecase(3, ChannelCodecs3.channelDataCodec.decodeOnly) .typecase(2, ChannelCodecs2.channelDataCodec.decodeOnly) .typecase(1, ChannelCodecs1.channelDataCodec.decodeOnly) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala index 8c2a81a619..1ecc72f104 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala @@ -57,7 +57,7 @@ private[channel] object ChannelCodecs0 { // field and don't support additional features which is why all bits are set to 0. ) - def localParamsCodec(channelVersion: ChannelTypes0.ChannelVersion): Codec[LocalChannelParams] = ( + def localParamsCodec(channelVersion: ChannelTypes0.ChannelVersion): Codec[ChannelTypes0.LocalParams] = ( ("nodeId" | publicKey) :: ("channelPath" | keyPathCodec) :: ("dustLimit" | satoshi) :: @@ -71,7 +71,7 @@ private[channel] object ChannelCodecs0 { ("walletStaticPaymentBasepoint" | optional(provide(channelVersion.paysDirectlyToWallet), publicKey)) :: ("features" | combinedFeaturesCodec)).map { case nodeId :: channelPath :: dustLimit :: maxHtlcValueInFlightMsat :: channelReserve :: htlcMinimum :: toSelfDelay :: maxAcceptedHtlcs :: isInitiator :: upfrontShutdownScript_opt :: walletStaticPaymentBasepoint :: features :: HNil => - LocalChannelParams(nodeId, channelPath, dustLimit, maxHtlcValueInFlightMsat, channelReserve, htlcMinimum, toSelfDelay, maxAcceptedHtlcs, isInitiator, isInitiator, upfrontShutdownScript_opt, walletStaticPaymentBasepoint, features) + ChannelTypes0.LocalParams(nodeId, channelPath, dustLimit, maxHtlcValueInFlightMsat, channelReserve, htlcMinimum, toSelfDelay, maxAcceptedHtlcs, isInitiator, isInitiator, upfrontShutdownScript_opt, walletStaticPaymentBasepoint, features) }.decodeOnly val remoteParamsCodec: Codec[ChannelTypes0.RemoteParams] = ( @@ -129,7 +129,7 @@ private[channel] object ChannelCodecs0 { ("outPoint" | outPointCodec) :: ("txOut" | txOutCodec) :: ("redeemScript" | varsizebinarydata)).map { - case outpoint :: txOut :: _ :: HNil => InputInfo(outpoint, txOut, ByteVector.empty) + case outpoint :: txOut :: _ :: HNil => InputInfo(outpoint, txOut) }.decodeOnly private val missingHtlcExpiry: Codec[CltvExpiry] = provide(CltvExpiry(0)) @@ -420,7 +420,7 @@ private[channel] object ChannelCodecs0 { ("closeStatus" | provide(Option.empty[CloseStatus]))).map { case commitments :: shortChannelId :: _ :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closingFeerates :: HNil => val aliases = ShortIdAliases(localAlias = Alias(shortChannelId.toLong), remoteAlias_opt = None) - DATA_NORMAL(commitments, aliases, channelAnnouncement, channelUpdate, localShutdown, remoteShutdown, closingFeerates, SpliceStatus.NoSplice) + DATA_NORMAL(commitments, aliases, channelAnnouncement, channelUpdate, SpliceStatus.NoSplice, localShutdown, remoteShutdown, closingFeerates) }.decodeOnly val DATA_NORMAL_10_Codec: Codec[DATA_NORMAL] = ( @@ -434,7 +434,7 @@ private[channel] object ChannelCodecs0 { ("closeStatus" | provide(Option.empty[CloseStatus]))).map { case commitments :: shortChannelId :: _ :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closingFeerates :: HNil => val aliases = ShortIdAliases(localAlias = Alias(shortChannelId.toLong), remoteAlias_opt = None) - DATA_NORMAL(commitments, aliases, channelAnnouncement, channelUpdate, localShutdown, remoteShutdown, closingFeerates, SpliceStatus.NoSplice) + DATA_NORMAL(commitments, aliases, channelAnnouncement, channelUpdate, SpliceStatus.NoSplice, localShutdown, remoteShutdown, closingFeerates) }.decodeOnly val DATA_SHUTDOWN_04_Codec: Codec[DATA_SHUTDOWN] = ( diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelTypes0.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelTypes0.scala index 1121805230..80d0a19b81 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelTypes0.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelTypes0.scala @@ -17,7 +17,7 @@ package fr.acinq.eclair.wire.internal.channel.version0 import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey -import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Crypto, OP_CHECKMULTISIG, OP_PUSHDATA, OutPoint, Satoshi, Script, ScriptWitness, Transaction, TxId, TxOut} +import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Crypto, DeterministicWallet, OP_CHECKMULTISIG, OP_PUSHDATA, OutPoint, Satoshi, Script, ScriptWitness, Transaction, TxId, TxOut} import fr.acinq.eclair.channel._ import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.transactions.CommitmentSpec @@ -95,7 +95,7 @@ private[channel] object ChannelTypes0 { * the raw transaction. It provides more information for auditing but is not used for business logic, so we can safely * put dummy values in the migration. */ - def migrateClosingTx(tx: Transaction): ClosingTx = ClosingTx(InputInfo(tx.txIn.head.outPoint, TxOut(Satoshi(0), Nil), ByteVector.empty), tx, None) + def migrateClosingTx(tx: Transaction): ClosingTx = ClosingTx(InputInfo(tx.txIn.head.outPoint, TxOut(Satoshi(0), Nil)), tx, None) case class HtlcTxAndSigs(txinfo: UnsignedHtlcTx, localSig: ByteVector64, remoteSig: ByteVector64) @@ -104,11 +104,11 @@ private[channel] object ChannelTypes0 { // Before version3, we stored fully signed local transactions (commit tx and htlc txs). It meant that someone gaining // access to the database could publish revoked commit txs, so we changed that to only store remote signatures. case class LocalCommit(index: Long, spec: CommitmentSpec, publishableTxs: PublishableTxs) { - def migrate(remoteFundingPubKey: PublicKey): channel.LocalCommit = { + def migrate(remoteFundingPubKey: PublicKey): (channel.LocalCommit, InputInfo) = { val remoteSig = extractRemoteSig(publishableTxs.commitTx, remoteFundingPubKey) val unsignedCommitTx = publishableTxs.commitTx.copy(tx = removeWitnesses(publishableTxs.commitTx.tx)) val htlcRemoteSigs = publishableTxs.htlcTxsAndSigs.map(_.remoteSig) - channel.LocalCommit(index, spec, unsignedCommitTx.tx.txid, unsignedCommitTx.input, remoteSig, htlcRemoteSigs) + (channel.LocalCommit(index, spec, unsignedCommitTx.tx.txid, remoteSig, htlcRemoteSigs), unsignedCommitTx.input) } private def extractRemoteSig(commitTx: CommitTx, remoteFundingPubKey: PublicKey): ChannelSpendSignature.IndividualSignature = { @@ -161,6 +161,31 @@ private[channel] object ChannelTypes0 { val ANCHOR_OUTPUTS = STATIC_REMOTEKEY | fromBit(USE_ANCHOR_OUTPUTS_BIT) // PUBKEY_KEYPATH + STATIC_REMOTEKEY + ANCHOR_OUTPUTS } + case class LocalParams(nodeId: PublicKey, + fundingKeyPath: DeterministicWallet.KeyPath, + dustLimit: Satoshi, + maxHtlcValueInFlightMsat: UInt64, + initialRequestedChannelReserve_opt: Option[Satoshi], + htlcMinimum: MilliSatoshi, + toSelfDelay: CltvExpiryDelta, + maxAcceptedHtlcs: Int, + isChannelOpener: Boolean, + paysCommitTxFees: Boolean, + upfrontShutdownScript_opt: Option[ByteVector], + walletStaticPaymentBasepoint: Option[PublicKey], + initFeatures: Features[InitFeature]) { + def migrate(): channel.LocalChannelParams = channel.LocalChannelParams( + nodeId = nodeId, + fundingKeyPath = fundingKeyPath, + initialRequestedChannelReserve_opt = initialRequestedChannelReserve_opt, + isChannelOpener = isChannelOpener, + paysCommitTxFees = paysCommitTxFees, + upfrontShutdownScript_opt = upfrontShutdownScript_opt, + walletStaticPaymentBasepoint = walletStaticPaymentBasepoint, + initFeatures = initFeatures, + ) + } + case class RemoteParams(nodeId: PublicKey, dustLimit: Satoshi, maxHtlcValueInFlightMsat: UInt64, // this is not MilliSatoshi because it can exceed the total amount of MilliSatoshi @@ -177,12 +202,7 @@ private[channel] object ChannelTypes0 { upfrontShutdownScript_opt: Option[ByteVector]) { def migrate(): channel.RemoteChannelParams = channel.RemoteChannelParams( nodeId = nodeId, - dustLimit = dustLimit, - maxHtlcValueInFlightMsat = maxHtlcValueInFlightMsat, initialRequestedChannelReserve_opt = requestedChannelReserve_opt, - htlcMinimum = htlcMinimum, - toRemoteDelay = toRemoteDelay, - maxAcceptedHtlcs = maxAcceptedHtlcs, revocationBasepoint = revocationBasepoint, paymentBasepoint = paymentBasepoint, delayedPaymentBasepoint = delayedPaymentBasepoint, @@ -195,7 +215,7 @@ private[channel] object ChannelTypes0 { case class WaitingForRevocation(nextRemoteCommit: RemoteCommit, sent: CommitSig, sentAfterLocalCommitIndex: Long) case class Commitments(channelVersion: ChannelVersion, - localParams: LocalChannelParams, remoteParams: RemoteParams, + localParams: LocalParams, remoteParams: RemoteParams, channelFlags: ChannelFlags, localCommit: LocalCommit, remoteCommit: RemoteCommit, localChanges: LocalChanges, remoteChanges: RemoteChanges, @@ -217,18 +237,29 @@ private[channel] object ChannelTypes0 { } else { ChannelFeatures() } + val (localCommit1, commitInput) = localCommit.migrate(remoteParams.fundingPubKey) + val localCommitParams = CommitParams(localParams.dustLimit, localParams.htlcMinimum, localParams.maxHtlcValueInFlightMsat, localParams.maxAcceptedHtlcs, remoteParams.toRemoteDelay) + val remoteCommitParams = CommitParams(remoteParams.dustLimit, remoteParams.htlcMinimum, remoteParams.maxHtlcValueInFlightMsat, remoteParams.maxAcceptedHtlcs, localParams.toSelfDelay) val commitment = Commitment( fundingTxIndex = 0, firstRemoteCommitIndex = 0, + fundingInput = commitInput.outPoint, + fundingAmount = commitInput.txOut.amount, remoteFundingPubKey = remoteParams.fundingPubKey, // We set an empty funding tx, even if it may be confirmed already (and the channel fully operational). We could // have set a specific Unknown status, but it would have forced us to keep it forever. We will retrieve the // funding tx when the channel is instantiated, and update the status (possibly immediately if it was confirmed). - LocalFundingStatus.SingleFundedUnconfirmedFundingTx(None), RemoteFundingStatus.Locked, - localCommit.migrate(remoteParams.fundingPubKey), remoteCommit, remoteNextCommitInfo.left.toOption.map(w => NextRemoteCommit(w.sent, w.nextRemoteCommit)) + localFundingStatus = LocalFundingStatus.SingleFundedUnconfirmedFundingTx(None), + remoteFundingStatus = RemoteFundingStatus.Locked, + commitmentFormat = channelFeatures.commitmentFormat, + localCommitParams = localCommitParams, + localCommit = localCommit1, + remoteCommitParams = remoteCommitParams, + remoteCommit = remoteCommit, + nextRemoteCommit_opt = remoteNextCommitInfo.left.toOption.map(w => NextRemoteCommit(w.sent, w.nextRemoteCommit)) ) channel.Commitments( - ChannelParams(channelId, channelConfig, channelFeatures, localParams, remoteParams.migrate(), channelFlags), + ChannelParams(channelId, channelConfig, channelFeatures, localParams.migrate(), remoteParams.migrate(), channelFlags), CommitmentChanges(localChanges, remoteChanges, localNextHtlcId, remoteNextHtlcId), Seq(commitment), inactive = Nil, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala index c25ef198ae..5dd5a8e200 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala @@ -44,7 +44,7 @@ private[channel] object ChannelCodecs1 { val channelVersionCodec: Codec[ChannelTypes0.ChannelVersion] = bits(ChannelTypes0.ChannelVersion.LENGTH_BITS).as[ChannelTypes0.ChannelVersion] - def localParamsCodec(channelVersion: ChannelTypes0.ChannelVersion): Codec[LocalChannelParams] = ( + def localParamsCodec(channelVersion: ChannelTypes0.ChannelVersion): Codec[ChannelTypes0.LocalParams] = ( ("nodeId" | publicKey) :: ("channelPath" | keyPathCodec) :: ("dustLimit" | satoshi) :: @@ -56,7 +56,7 @@ private[channel] object ChannelCodecs1 { ("isChannelOpener" | bool) :: ("paysCommitTxFees" | bool) :: ignore(6) :: ("upfrontShutdownScript_opt" | lengthDelimited(bytes).map(Option(_)).decodeOnly) :: ("walletStaticPaymentBasepoint" | optional(provide(channelVersion.paysDirectlyToWallet), publicKey)) :: - ("features" | combinedFeaturesCodec)).as[LocalChannelParams].decodeOnly + ("features" | combinedFeaturesCodec)).as[ChannelTypes0.LocalParams].decodeOnly val remoteParamsCodec: Codec[ChannelTypes0.RemoteParams] = ( ("nodeId" | publicKey) :: @@ -101,7 +101,7 @@ private[channel] object ChannelCodecs1 { ("outPoint" | outPointCodec) :: ("txOut" | txOutCodec) :: ("redeemScript" | lengthDelimited(bytes))).map { - case outpoint :: txOut :: _ :: HNil => InputInfo(outpoint, txOut, ByteVector.empty) + case outpoint :: txOut :: _ :: HNil => InputInfo(outpoint, txOut) }.decodeOnly private val missingHtlcExpiry: Codec[CltvExpiry] = provide(CltvExpiry(0)) @@ -277,7 +277,7 @@ private[channel] object ChannelCodecs1 { ("closeStatus" | provide(Option.empty[CloseStatus]))).map { case commitments :: shortChannelId :: _ :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closeStatus :: HNil => val aliases = ShortIdAliases(localAlias = Alias(shortChannelId.toLong), remoteAlias_opt = None) - DATA_NORMAL(commitments, aliases, channelAnnouncement, channelUpdate, localShutdown, remoteShutdown, closeStatus, SpliceStatus.NoSplice) + DATA_NORMAL(commitments, aliases, channelAnnouncement, channelUpdate, SpliceStatus.NoSplice, localShutdown, remoteShutdown, closeStatus) }.decodeOnly val DATA_SHUTDOWN_23_Codec: Codec[DATA_SHUTDOWN] = ( diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala index 77621252f8..bae787cd20 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala @@ -53,7 +53,7 @@ private[channel] object ChannelCodecs2 { val channelVersionCodec: Codec[ChannelTypes0.ChannelVersion] = bits(ChannelTypes0.ChannelVersion.LENGTH_BITS).as[ChannelTypes0.ChannelVersion] - def localParamsCodec(channelVersion: ChannelTypes0.ChannelVersion): Codec[LocalChannelParams] = ( + def localParamsCodec(channelVersion: ChannelTypes0.ChannelVersion): Codec[ChannelTypes0.LocalParams] = ( ("nodeId" | publicKey) :: ("channelPath" | keyPathCodec) :: ("dustLimit" | satoshi) :: @@ -65,7 +65,7 @@ private[channel] object ChannelCodecs2 { ("isChannelOpener" | bool) :: ("paysCommitTxFees" | bool) :: ignore(6) :: ("upfrontShutdownScript_opt" | lengthDelimited(bytes).map(Option(_)).decodeOnly) :: ("walletStaticPaymentBasepoint" | optional(provide(channelVersion.paysDirectlyToWallet), publicKey)) :: - ("features" | combinedFeaturesCodec)).as[LocalChannelParams] + ("features" | combinedFeaturesCodec)).as[ChannelTypes0.LocalParams] val remoteParamsCodec: Codec[ChannelTypes0.RemoteParams] = ( ("nodeId" | publicKey) :: @@ -105,7 +105,7 @@ private[channel] object ChannelCodecs2 { ("outPoint" | outPointCodec) :: ("txOut" | txOutCodec) :: ("redeemScript" | lengthDelimited(bytes))).map { - case outpoint :: txOut :: _ :: HNil => InputInfo(outpoint, txOut, ByteVector.empty) + case outpoint :: txOut :: _ :: HNil => InputInfo(outpoint, txOut) }.decodeOnly val outputInfoCodec: Codec[Long] = ( @@ -307,7 +307,7 @@ private[channel] object ChannelCodecs2 { ("closeStatus" | provide(Option.empty[CloseStatus]))).map { case commitments :: shortChannelId :: _ :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closeStatus :: HNil => val aliases = ShortIdAliases(localAlias = Alias(shortChannelId.toLong), remoteAlias_opt = None) - DATA_NORMAL(commitments, aliases, channelAnnouncement, channelUpdate, localShutdown, remoteShutdown, closeStatus, SpliceStatus.NoSplice) + DATA_NORMAL(commitments, aliases, channelAnnouncement, channelUpdate, SpliceStatus.NoSplice, localShutdown, remoteShutdown, closeStatus) }.decodeOnly val DATA_SHUTDOWN_03_Codec: Codec[DATA_SHUTDOWN] = ( diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala index e7138b4750..6f9d5289da 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala @@ -66,7 +66,7 @@ private[channel] object ChannelCodecs3 { (cf: ChannelFeatures) => Features(cf.features.map(f => f -> FeatureSupport.Mandatory).toMap).toByteVector // we encode features as mandatory, by convention ) - def localParamsCodec(channelFeatures: ChannelFeatures): Codec[LocalChannelParams] = ( + def localParamsCodec(channelFeatures: ChannelFeatures): Codec[ChannelTypes0.LocalParams] = ( ("nodeId" | publicKey) :: ("channelPath" | keyPathCodec) :: ("dustLimit" | satoshi) :: @@ -78,7 +78,7 @@ private[channel] object ChannelCodecs3 { ("isChannelOpener" | bool) :: ("paysCommitTxFees" | bool) :: ignore(6) :: ("upfrontShutdownScript_opt" | lengthDelimited(bytes).map(Option(_)).decodeOnly) :: ("walletStaticPaymentBasepoint" | optional(provide(channelFeatures.paysDirectlyToWallet), publicKey)) :: - ("features" | combinedFeaturesCodec)).as[LocalChannelParams] + ("features" | combinedFeaturesCodec)).as[ChannelTypes0.LocalParams] def remoteParamsCodec(channelFeatures: ChannelFeatures): Codec[ChannelTypes0.RemoteParams] = ( ("nodeId" | publicKey) :: @@ -118,7 +118,7 @@ private[channel] object ChannelCodecs3 { ("outPoint" | outPointCodec) :: ("txOut" | txOutCodec) :: ("redeemScript" | lengthDelimited(bytes))).map { - case outpoint :: txOut :: _ :: HNil => InputInfo(outpoint, txOut, ByteVector.empty) + case outpoint :: txOut :: _ :: HNil => InputInfo(outpoint, txOut) }.decodeOnly val outputInfoCodec: Codec[Long] = ( @@ -373,7 +373,7 @@ private[channel] object ChannelCodecs3 { ("closeStatus" | provide(Option.empty[CloseStatus]))).map { case commitments :: shortChannelId :: _ :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closeStatus :: HNil => val aliases = ShortIdAliases(localAlias = Alias(shortChannelId.toLong), remoteAlias_opt = None) - DATA_NORMAL(commitments, aliases, channelAnnouncement, channelUpdate, localShutdown, remoteShutdown, closeStatus, SpliceStatus.NoSplice) + DATA_NORMAL(commitments, aliases, channelAnnouncement, channelUpdate, SpliceStatus.NoSplice, localShutdown, remoteShutdown, closeStatus) }.decodeOnly val DATA_NORMAL_07_Codec: Codec[DATA_NORMAL] = ( @@ -387,7 +387,7 @@ private[channel] object ChannelCodecs3 { ("closeStatus" | closeStatusCompatCodec)).map { case commitments :: shortChannelId :: _ :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closeStatus :: HNil => val aliases = ShortIdAliases(localAlias = Alias(shortChannelId.toLong), remoteAlias_opt = None) - DATA_NORMAL(commitments, aliases, channelAnnouncement, channelUpdate, localShutdown, remoteShutdown, closeStatus, SpliceStatus.NoSplice) + DATA_NORMAL(commitments, aliases, channelAnnouncement, channelUpdate, SpliceStatus.NoSplice, localShutdown, remoteShutdown, closeStatus) }.decodeOnly val DATA_NORMAL_09_Codec: Codec[DATA_NORMAL] = ( @@ -400,7 +400,7 @@ private[channel] object ChannelCodecs3 { ("closeStatus" | closeStatusCompatCodec) :: ("spliceStatus" | provide[SpliceStatus](SpliceStatus.NoSplice))).map { case commitments :: shortIds :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closeStatus :: spliceStatus :: HNil => - DATA_NORMAL(commitments, shortIds, channelAnnouncement, channelUpdate, localShutdown, remoteShutdown, closeStatus, spliceStatus) + DATA_NORMAL(commitments, shortIds, channelAnnouncement, channelUpdate, spliceStatus, localShutdown, remoteShutdown, closeStatus) }.decodeOnly val DATA_SHUTDOWN_03_Codec: Codec[DATA_SHUTDOWN] = ( diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelTypes3.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelTypes3.scala index 3170b33aca..ab0c54b217 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelTypes3.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelTypes3.scala @@ -23,7 +23,7 @@ import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.fund.InteractiveTxSigningSession import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.transactions.CommitmentSpec -import fr.acinq.eclair.transactions.Transactions.{CommitTx, UnsignedHtlcTx} +import fr.acinq.eclair.transactions.Transactions.{CommitTx, InputInfo, UnsignedHtlcTx} import fr.acinq.eclair.wire.internal.channel.version0.ChannelTypes0 import fr.acinq.eclair.wire.protocol.CommitSig @@ -38,18 +38,21 @@ private[channel] object ChannelTypes3 { // Before version 4, we stored the unsigned commit tx and htlc txs in our local commit. // We changed that to only store the remote signatures and re-compute transactions on-the-fly when force-closing. case class LocalCommit(index: Long, spec: CommitmentSpec, commitTxAndRemoteSig: CommitTxAndRemoteSig, htlcTxsAndRemoteSigs: List[HtlcTxAndRemoteSig]) { - def migrate(): channel.LocalCommit = channel.LocalCommit(index, spec, commitTxAndRemoteSig.commitTx.tx.txid, commitTxAndRemoteSig.commitTx.input, commitTxAndRemoteSig.remoteSig, htlcTxsAndRemoteSigs.map(_.remoteSig)) + def migrate(): (channel.LocalCommit, InputInfo) = { + val localCommit = channel.LocalCommit(index, spec, commitTxAndRemoteSig.commitTx.tx.txid, commitTxAndRemoteSig.remoteSig, htlcTxsAndRemoteSigs.map(_.remoteSig)) + (localCommit, commitTxAndRemoteSig.commitTx.input) + } } case class UnsignedLocalCommit(index: Long, spec: CommitmentSpec, commitTx: CommitTx, htlcTxs: List[UnsignedHtlcTx]) { - def migrate(): InteractiveTxSigningSession.UnsignedLocalCommit = InteractiveTxSigningSession.UnsignedLocalCommit(index, spec, commitTx.tx.txid, commitTx.input) + def migrate(): InteractiveTxSigningSession.UnsignedLocalCommit = InteractiveTxSigningSession.UnsignedLocalCommit(index, spec, commitTx.tx.txid) } // Before version4, we didn't support multiple active commitments, which were later introduced by dual funding and splicing. case class Commitments(channelId: ByteVector32, channelConfig: ChannelConfig, channelFeatures: ChannelFeatures, - localParams: LocalChannelParams, remoteParams: ChannelTypes0.RemoteParams, + localParams: ChannelTypes0.LocalParams, remoteParams: ChannelTypes0.RemoteParams, channelFlags: ChannelFlags, localCommit: ChannelTypes3.LocalCommit, remoteCommit: RemoteCommit, localChanges: LocalChanges, remoteChanges: RemoteChanges, @@ -59,15 +62,30 @@ private[channel] object ChannelTypes3 { localFundingStatus: LocalFundingStatus, remoteFundingStatus: RemoteFundingStatus, remotePerCommitmentSecrets: ShaChain) { - def migrate(): channel.Commitments = channel.Commitments( - ChannelParams(channelId, channelConfig, channelFeatures, localParams, remoteParams.migrate(), channelFlags), - CommitmentChanges(localChanges, remoteChanges, localNextHtlcId, remoteNextHtlcId), - Seq(Commitment(fundingTxIndex = 0, firstRemoteCommitIndex = 0, remoteFundingPubKey = remoteParams.fundingPubKey, localFundingStatus, remoteFundingStatus, localCommit.migrate(), remoteCommit, remoteNextCommitInfo.left.toOption.map(w => NextRemoteCommit(w.sent, w.nextRemoteCommit)))), - inactive = Nil, - remoteNextCommitInfo.fold(w => Left(WaitForRev(w.sentAfterLocalCommitIndex)), remotePerCommitmentPoint => Right(remotePerCommitmentPoint)), - remotePerCommitmentSecrets, - originChannels - ) + def migrate(): channel.Commitments = { + val (localCommit1, commitInput) = localCommit.migrate() + val localCommitParams = CommitParams(localParams.dustLimit, localParams.htlcMinimum, localParams.maxHtlcValueInFlightMsat, localParams.maxAcceptedHtlcs, remoteParams.toRemoteDelay) + val remoteCommitParams = CommitParams(remoteParams.dustLimit, remoteParams.htlcMinimum, remoteParams.maxHtlcValueInFlightMsat, remoteParams.maxAcceptedHtlcs, localParams.toSelfDelay) + val commitment = Commitment( + fundingTxIndex = 0, firstRemoteCommitIndex = 0, + commitInput.outPoint, commitInput.txOut.amount, + remoteParams.fundingPubKey, + localFundingStatus, remoteFundingStatus, + channelFeatures.commitmentFormat, + localCommitParams, localCommit1, + remoteCommitParams, remoteCommit, + remoteNextCommitInfo.left.toOption.map(w => NextRemoteCommit(w.sent, w.nextRemoteCommit)) + ) + channel.Commitments( + ChannelParams(channelId, channelConfig, channelFeatures, localParams.migrate(), remoteParams.migrate(), channelFlags), + CommitmentChanges(localChanges, remoteChanges, localNextHtlcId, remoteNextHtlcId), + active = Seq(commitment), + inactive = Nil, + remoteNextCommitInfo.fold(w => Left(WaitForRev(w.sentAfterLocalCommitIndex)), remotePerCommitmentPoint => Right(remotePerCommitmentPoint)), + remotePerCommitmentSecrets, + originChannels + ) + } } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4.scala index d9ec9d267e..36472da796 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4.scala @@ -3,16 +3,15 @@ package fr.acinq.eclair.wire.internal.channel.version4 import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.scalacompat.DeterministicWallet.KeyPath import fr.acinq.bitcoin.scalacompat.{ByteVector32, OutPoint, SatoshiLong, ScriptWitness, Transaction, TxOut} -import fr.acinq.eclair.blockchain.fee.{ConfirmationPriority, ConfirmationTarget} -import fr.acinq.eclair.channel.LocalFundingStatus._ +import fr.acinq.eclair.blockchain.fee.{ConfirmationPriority, ConfirmationTarget, FeeratePerKw} import fr.acinq.eclair.channel._ +import fr.acinq.eclair.channel.fund.InteractiveTxBuilder import fr.acinq.eclair.channel.fund.InteractiveTxBuilder.{FullySignedSharedTransaction, PartiallySignedSharedTransaction} -import fr.acinq.eclair.channel.fund.InteractiveTxSigningSession.UnsignedLocalCommit -import fr.acinq.eclair.channel.fund.{InteractiveTxBuilder, InteractiveTxSigningSession} import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.crypto.keymanager.{LocalCommitmentKeys, RemoteCommitmentKeys} import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.transactions.{CommitmentSpec, DirectedHtlc, IncomingHtlc, OutgoingHtlc} +import fr.acinq.eclair.wire.internal.channel.version0.ChannelTypes0 import fr.acinq.eclair.wire.internal.channel.version2.ChannelTypes2 import fr.acinq.eclair.wire.internal.channel.version3.ChannelTypes3 import fr.acinq.eclair.wire.protocol.CommonCodecs._ @@ -53,7 +52,7 @@ private[channel] object ChannelCodecs4 { (cf: ChannelFeatures) => Features(cf.features.map(f => f -> FeatureSupport.Mandatory).toMap).toByteVector // we encode features as mandatory, by convention ) - def localParamsCodec(channelFeatures: ChannelFeatures): Codec[LocalChannelParams] = ( + def localParamsCodec(channelFeatures: ChannelFeatures): Codec[ChannelTypes0.LocalParams] = ( ("nodeId" | publicKey) :: ("channelPath" | keyPathCodec) :: ("dustLimit" | satoshi) :: @@ -66,9 +65,9 @@ private[channel] object ChannelCodecs4 { ("isChannelOpener" | bool) :: ("paysCommitTxFees" | bool) :: ignore(6) :: ("upfrontShutdownScript_opt" | optional(bool8, lengthDelimited(bytes))) :: ("walletStaticPaymentBasepoint" | optional(provide(channelFeatures.paysDirectlyToWallet), publicKey)) :: - ("features" | combinedFeaturesCodec)).as[LocalChannelParams] + ("features" | combinedFeaturesCodec)).as[ChannelTypes0.LocalParams] - def remoteParamsCodec(channelFeatures: ChannelFeatures): Codec[RemoteChannelParams] = ( + def remoteParamsCodec(channelFeatures: ChannelFeatures): Codec[ChannelTypes4.RemoteParams] = ( ("nodeId" | publicKey) :: ("dustLimit" | satoshi) :: ("maxHtlcValueInFlightMsat" | uint64) :: @@ -81,7 +80,7 @@ private[channel] object ChannelCodecs4 { ("delayedPaymentBasepoint" | publicKey) :: ("htlcBasepoint" | publicKey) :: ("features" | combinedFeaturesCodec) :: - ("shutdownScript" | optional(bool8, lengthDelimited(bytes)))).as[RemoteChannelParams] + ("shutdownScript" | optional(bool8, lengthDelimited(bytes)))).as[ChannelTypes4.RemoteParams] def setCodec[T](codec: Codec[T]): Codec[Set[T]] = listOfN(uint16, codec).xmap(_.toSet, _.toList) @@ -113,11 +112,12 @@ private[channel] object ChannelCodecs4 { val txCodec: Codec[Transaction] = lengthDelimited(bytes.xmap(d => Transaction.read(d.toArray), d => Transaction.write(d))) - // all v4-encoded channels use segwit inputs, support for taproot inputs will be added later in v5 codecs val inputInfoCodec: Codec[InputInfo] = ( ("outPoint" | outPointCodec) :: ("txOut" | txOutCodec) :: - ("redeemScript" | lengthDelimited(bytes))).as[InputInfo] + ("redeemScript" | lengthDelimited(bytes))).map { + case outpoint :: txOut :: _ :: HNil => InputInfo(outpoint, txOut) + }.decodeOnly val outputInfoCodec: Codec[Long] = ( ("index" | uint32) :: @@ -287,7 +287,7 @@ private[channel] object ChannelCodecs4 { private val requireConfirmedInputsCodec: Codec[InteractiveTxBuilder.RequireConfirmedInputs] = (("forLocal" | bool8) :: ("forRemote" | bool8)).as[InteractiveTxBuilder.RequireConfirmedInputs] - private val fundingParamsCodec: Codec[InteractiveTxBuilder.InteractiveTxParams] = ( + private val fundingParamsCodec: Codec[ChannelTypes4.InteractiveTxParams] = ( ("channelId" | bytes32) :: ("isInitiator" | bool8) :: ("localContribution" | satoshiSigned) :: @@ -298,7 +298,7 @@ private[channel] object ChannelCodecs4 { ("lockTime" | uint32) :: ("dustLimit" | satoshi) :: ("targetFeerate" | feeratePerKw) :: - ("requireConfirmedInputs" | requireConfirmedInputsCodec)).as[InteractiveTxBuilder.InteractiveTxParams] + ("requireConfirmedInputs" | requireConfirmedInputsCodec)).as[ChannelTypes4.InteractiveTxParams] // This codec was used by a first prototype version of splicing that only worked without HTLCs. private val sharedInteractiveTxInputWithoutHtlcsCodec: Codec[InteractiveTxBuilder.Input.Shared] = ( @@ -423,26 +423,26 @@ private[channel] object ChannelCodecs4 { ("amount" | satoshi) :: ("fees" | liquidityFeesCodec)).as[LiquidityAds.PurchaseBasicInfo] - private val dualFundedUnconfirmedFundingTxWithoutLiquidityPurchaseCodec: Codec[DualFundedUnconfirmedFundingTx] = ( + private val dualFundedUnconfirmedFundingTxWithoutLiquidityPurchaseCodec: Codec[ChannelTypes4.DualFundedUnconfirmedFundingTx] = ( ("sharedTx" | signedSharedTransactionCodec) :: ("createdAt" | blockHeight) :: ("fundingParams" | fundingParamsCodec) :: - ("liquidityPurchase" | provide(Option.empty[LiquidityAds.PurchaseBasicInfo]))).as[DualFundedUnconfirmedFundingTx].xmap( + ("liquidityPurchase" | provide(Option.empty[LiquidityAds.PurchaseBasicInfo]))).as[ChannelTypes4.DualFundedUnconfirmedFundingTx].xmap( dfu => fillSharedInputScript(dfu), dfu => dfu ) - private val dualFundedUnconfirmedFundingTxCodec: Codec[DualFundedUnconfirmedFundingTx] = ( + private val dualFundedUnconfirmedFundingTxCodec: Codec[ChannelTypes4.DualFundedUnconfirmedFundingTx] = ( ("sharedTx" | signedSharedTransactionCodec) :: ("createdAt" | blockHeight) :: ("fundingParams" | fundingParamsCodec) :: - ("liquidityPurchase" | optional(bool8, liquidityPurchaseCodec))).as[DualFundedUnconfirmedFundingTx].xmap( + ("liquidityPurchase" | optional(bool8, liquidityPurchaseCodec))).as[ChannelTypes4.DualFundedUnconfirmedFundingTx].xmap( dfu => fillSharedInputScript(dfu), dfu => dfu ) // When decoding interactive-tx from older codecs, we fill the shared input publicKeyScript if necessary. - private def fillSharedInputScript(dfu: DualFundedUnconfirmedFundingTx): DualFundedUnconfirmedFundingTx = { + private def fillSharedInputScript(dfu: ChannelTypes4.DualFundedUnconfirmedFundingTx): ChannelTypes4.DualFundedUnconfirmedFundingTx = { (dfu.sharedTx.tx.sharedInput_opt, dfu.fundingParams.sharedInput_opt) match { case (Some(sharedTxInput), Some(sharedFundingParamsInput)) if sharedTxInput.publicKeyScript.isEmpty => val sharedTxInput1 = sharedTxInput.copy(publicKeyScript = sharedFundingParamsInput.info.txOut.publicKeyScript) @@ -456,30 +456,30 @@ private[channel] object ChannelCodecs4 { } } - val fundingTxStatusCodec: Codec[LocalFundingStatus] = discriminated[LocalFundingStatus].by(uint8) - .typecase(0x0a, (txCodec :: realshortchannelid :: optional(bool8, lengthDelimited(txSignaturesCodec)) :: optional(bool8, liquidityPurchaseCodec)).as[ConfirmedFundingTx]) - .typecase(0x01, optional(bool8, txCodec).as[SingleFundedUnconfirmedFundingTx]) + val fundingTxStatusCodec: Codec[ChannelTypes4.LocalFundingStatus] = discriminated[ChannelTypes4.LocalFundingStatus].by(uint8) + .typecase(0x0a, (txCodec :: realshortchannelid :: optional(bool8, lengthDelimited(txSignaturesCodec)) :: optional(bool8, liquidityPurchaseCodec)).as[ChannelTypes4.ConfirmedFundingTx]) + .typecase(0x01, optional(bool8, txCodec).as[ChannelTypes4.SingleFundedUnconfirmedFundingTx]) .typecase(0x07, dualFundedUnconfirmedFundingTxCodec) - .typecase(0x08, (txCodec :: optional(bool8, lengthDelimited(txSignaturesCodec)) :: optional(bool8, liquidityPurchaseCodec)).as[ZeroconfPublishedFundingTx]) - .typecase(0x09, (txCodec :: provide(RealShortChannelId(0)) :: optional(bool8, lengthDelimited(txSignaturesCodec)) :: optional(bool8, liquidityPurchaseCodec)).as[ConfirmedFundingTx]) + .typecase(0x08, (txCodec :: optional(bool8, lengthDelimited(txSignaturesCodec)) :: optional(bool8, liquidityPurchaseCodec)).as[ChannelTypes4.ZeroconfPublishedFundingTx]) + .typecase(0x09, (txCodec :: provide(RealShortChannelId(0)) :: optional(bool8, lengthDelimited(txSignaturesCodec)) :: optional(bool8, liquidityPurchaseCodec)).as[ChannelTypes4.ConfirmedFundingTx]) .typecase(0x02, dualFundedUnconfirmedFundingTxWithoutLiquidityPurchaseCodec) - .typecase(0x05, (txCodec :: optional(bool8, lengthDelimited(txSignaturesCodec)) :: provide(Option.empty[LiquidityAds.PurchaseBasicInfo])).as[ZeroconfPublishedFundingTx]) - .typecase(0x06, (txCodec :: provide(RealShortChannelId(0)) :: optional(bool8, lengthDelimited(txSignaturesCodec)) :: provide(Option.empty[LiquidityAds.PurchaseBasicInfo])).as[ConfirmedFundingTx]) - .typecase(0x03, (txCodec :: provide(Option.empty[TxSignatures]) :: provide(Option.empty[LiquidityAds.PurchaseBasicInfo])).as[ZeroconfPublishedFundingTx]) - .typecase(0x04, (txCodec :: provide(RealShortChannelId(0)) :: provide(Option.empty[TxSignatures]) :: provide(Option.empty[LiquidityAds.PurchaseBasicInfo])).as[ConfirmedFundingTx]) + .typecase(0x05, (txCodec :: optional(bool8, lengthDelimited(txSignaturesCodec)) :: provide(Option.empty[LiquidityAds.PurchaseBasicInfo])).as[ChannelTypes4.ZeroconfPublishedFundingTx]) + .typecase(0x06, (txCodec :: provide(RealShortChannelId(0)) :: optional(bool8, lengthDelimited(txSignaturesCodec)) :: provide(Option.empty[LiquidityAds.PurchaseBasicInfo])).as[ChannelTypes4.ConfirmedFundingTx]) + .typecase(0x03, (txCodec :: provide(Option.empty[TxSignatures]) :: provide(Option.empty[LiquidityAds.PurchaseBasicInfo])).as[ChannelTypes4.ZeroconfPublishedFundingTx]) + .typecase(0x04, (txCodec :: provide(RealShortChannelId(0)) :: provide(Option.empty[TxSignatures]) :: provide(Option.empty[LiquidityAds.PurchaseBasicInfo])).as[ChannelTypes4.ConfirmedFundingTx]) val remoteFundingStatusCodec: Codec[RemoteFundingStatus] = discriminated[RemoteFundingStatus].by(uint8) .typecase(0x01, provide(RemoteFundingStatus.NotLocked)) .typecase(0x02, provide(RemoteFundingStatus.Locked)) - val paramsCodec: Codec[ChannelParams] = ( + val paramsCodec: Codec[ChannelTypes4.ChannelParams] = ( ("channelId" | bytes32) :: ("channelConfig" | channelConfigCodec) :: (("channelFeatures" | channelFeaturesCodec) >>:~ { channelFeatures => ("localParams" | localParamsCodec(channelFeatures)) :: ("remoteParams" | remoteParamsCodec(channelFeatures)) :: ("channelFlags" | channelflags) - })).as[ChannelParams] + })).as[ChannelTypes4.ChannelParams] val waitForRevCodec: Codec[WaitForRev] = ("sentAfterLocalCommitIndex" | uint64overflow).as[WaitForRev] @@ -489,19 +489,22 @@ private[channel] object ChannelCodecs4 { ("localNextHtlcId" | uint64overflow) :: ("remoteNextHtlcId" | uint64overflow)).as[CommitmentChanges] - private def localCommitWithTxsCodec(commitmentSpecCodec: Codec[CommitmentSpec]): Codec[LocalCommit] = ( + private def localCommitWithTxsCodec(commitmentSpecCodec: Codec[CommitmentSpec]): Codec[ChannelTypes4.LocalCommit] = ( ("index" | uint64overflow) :: ("spec" | commitmentSpecCodec) :: ("commitTxAndRemoteSig" | commitTxAndRemoteSigCodec) :: - ("htlcTxsAndRemoteSigs" | listOfN(uint16, htlcTxsAndRemoteSigsCodec))).as[ChannelTypes3.LocalCommit].decodeOnly.map[LocalCommit](_.migrate()).decodeOnly + ("htlcTxsAndRemoteSigs" | listOfN(uint16, htlcTxsAndRemoteSigsCodec))).map { + case index :: spec :: commitTxAndRemoteSig :: htlcTxsAndRemoteSigs :: HNil => + ChannelTypes4.LocalCommit(index, spec, commitTxAndRemoteSig.commitTx.tx.txid, commitTxAndRemoteSig.commitTx.input, commitTxAndRemoteSig.remoteSig, htlcTxsAndRemoteSigs.map(_.remoteSig)) + }.decodeOnly - private def localCommitCodec(commitmentSpecCodec: Codec[CommitmentSpec]): Codec[LocalCommit] = ( + private def localCommitCodec(commitmentSpecCodec: Codec[CommitmentSpec]): Codec[ChannelTypes4.LocalCommit] = ( ("index" | uint64overflow) :: ("spec" | commitmentSpecCodec) :: ("txId" | txId) :: ("input" | inputInfoCodec) :: ("remoteSig" | channelSpendSignatureCodec) :: - ("htlcRemoteSigs" | listOfN(uint16, bytes64))).as[LocalCommit] + ("htlcRemoteSigs" | listOfN(uint16, bytes64))).as[ChannelTypes4.LocalCommit] private def remoteCommitCodec(commitmentSpecCodec: Codec[CommitmentSpec]): Codec[RemoteCommit] = ( ("index" | uint64overflow) :: @@ -513,7 +516,7 @@ private[channel] object ChannelCodecs4 { ("sig" | lengthDelimited(commitSigCodec)) :: ("commit" | remoteCommitCodec(commitmentSpecCodec))).as[NextRemoteCommit] - private def commitmentCodecWithoutFirstRemoteCommitIndex(htlcs: Set[DirectedHtlc]): Codec[Commitment] = ( + private def commitmentCodecWithoutFirstRemoteCommitIndex(htlcs: Set[DirectedHtlc]): Codec[ChannelTypes4.Commitment] = ( ("fundingTxIndex" | uint32) :: ("firstRemoteCommitIndex" | provide(0L)) :: ("fundingPubKey" | publicKey) :: @@ -521,9 +524,9 @@ private[channel] object ChannelCodecs4 { ("remoteFundingStatus" | remoteFundingStatusCodec) :: ("localCommit" | localCommitWithTxsCodec(minimalCommitmentSpecCodec(htlcs))) :: ("remoteCommit" | remoteCommitCodec(minimalCommitmentSpecCodec(htlcs.map(_.opposite)))) :: - ("nextRemoteCommit_opt" | optional(bool8, nextRemoteCommitCodec(minimalCommitmentSpecCodec(htlcs.map(_.opposite)))))).as[Commitment] + ("nextRemoteCommit_opt" | optional(bool8, nextRemoteCommitCodec(minimalCommitmentSpecCodec(htlcs.map(_.opposite)))))).as[ChannelTypes4.Commitment] - private def commitmentCodecWithLocalTxs(htlcs: Set[DirectedHtlc]): Codec[Commitment] = ( + private def commitmentCodecWithLocalTxs(htlcs: Set[DirectedHtlc]): Codec[ChannelTypes4.Commitment] = ( ("fundingTxIndex" | uint32) :: ("firstRemoteCommitIndex" | uint64overflow) :: ("fundingPubKey" | publicKey) :: @@ -531,9 +534,9 @@ private[channel] object ChannelCodecs4 { ("remoteFundingStatus" | remoteFundingStatusCodec) :: ("localCommit" | localCommitWithTxsCodec(minimalCommitmentSpecCodec(htlcs))) :: ("remoteCommit" | remoteCommitCodec(minimalCommitmentSpecCodec(htlcs.map(_.opposite)))) :: - ("nextRemoteCommit_opt" | optional(bool8, nextRemoteCommitCodec(minimalCommitmentSpecCodec(htlcs.map(_.opposite)))))).as[Commitment] + ("nextRemoteCommit_opt" | optional(bool8, nextRemoteCommitCodec(minimalCommitmentSpecCodec(htlcs.map(_.opposite)))))).as[ChannelTypes4.Commitment] - private def commitmentCodec(htlcs: Set[DirectedHtlc]): Codec[Commitment] = ( + private def commitmentCodec(htlcs: Set[DirectedHtlc]): Codec[ChannelTypes4.Commitment] = ( ("fundingTxIndex" | uint32) :: ("firstRemoteCommitIndex" | uint64overflow) :: ("fundingPubKey" | publicKey) :: @@ -541,7 +544,7 @@ private[channel] object ChannelCodecs4 { ("remoteFundingStatus" | remoteFundingStatusCodec) :: ("localCommit" | localCommitCodec(minimalCommitmentSpecCodec(htlcs))) :: ("remoteCommit" | remoteCommitCodec(minimalCommitmentSpecCodec(htlcs.map(_.opposite)))) :: - ("nextRemoteCommit_opt" | optional(bool8, nextRemoteCommitCodec(minimalCommitmentSpecCodec(htlcs.map(_.opposite)))))).as[Commitment] + ("nextRemoteCommit_opt" | optional(bool8, nextRemoteCommitCodec(minimalCommitmentSpecCodec(htlcs.map(_.opposite)))))).as[ChannelTypes4.Commitment] /** * When multiple commitments are active, htlcs are shared between all of these commitments. @@ -549,49 +552,26 @@ private[channel] object ChannelCodecs4 { * The resulting htlc set size is thus between 1,4 MB and 64 MB, which can be pretty large. * To avoid writing that htlc set multiple times to disk, we encode it separately. */ - case class EncodedCommitments(channelParams: ChannelParams, + case class EncodedCommitments(channelParams: ChannelTypes4.ChannelParams, changes: CommitmentChanges, // The direction we use is from our local point of view. htlcs: Set[DirectedHtlc], - active: List[Commitment], - inactive: List[Commitment], + active: List[ChannelTypes4.Commitment], + inactive: List[ChannelTypes4.Commitment], remoteNextCommitInfo: Either[WaitForRev, PublicKey], remotePerCommitmentSecrets: ShaChain, originChannels: Map[Long, Origin], remoteChannelData_opt: Option[ByteVector]) { def toCommitments: Commitments = { Commitments( - channelParams = channelParams, + channelParams = channelParams.migrate(), changes = changes, - active = active, - inactive = inactive, - remoteNextCommitInfo, - remotePerCommitmentSecrets, - originChannels, - remoteChannelData_opt - ) - } - } - - object EncodedCommitments { - def apply(commitments: Commitments): EncodedCommitments = { - // The direction we use is from our local point of view: we use sets, which deduplicates htlcs that are in both - // local and remote commitments. - // All active commitments have the same htlc set, but each inactive commitment may have a distinct htlc set - val commitmentsSet = (commitments.active.head +: commitments.inactive).toSet - val htlcs = commitmentsSet.flatMap(_.localCommit.spec.htlcs) ++ - commitmentsSet.flatMap(_.remoteCommit.spec.htlcs.map(_.opposite)) ++ - commitmentsSet.flatMap(_.nextRemoteCommit_opt.toList.flatMap(_.commit.spec.htlcs.map(_.opposite))) - EncodedCommitments( - channelParams = commitments.channelParams, - changes = commitments.changes, - htlcs = htlcs, - active = commitments.active.toList, - inactive = commitments.inactive.toList, - remoteNextCommitInfo = commitments.remoteNextCommitInfo, - remotePerCommitmentSecrets = commitments.remotePerCommitmentSecrets, - originChannels = commitments.originChannels, - remoteChannelData_opt = commitments.remoteChannelData_opt + active = active.map(_.migrate(channelParams)), + inactive = inactive.map(_.migrate(channelParams)), + remoteNextCommitInfo = remoteNextCommitInfo, + remotePerCommitmentSecrets = remotePerCommitmentSecrets, + originChannels = originChannels, + remoteChannelData_opt = remoteChannelData_opt ) } } @@ -606,10 +586,7 @@ private[channel] object ChannelCodecs4 { ("remotePerCommitmentSecrets" | byteAligned(ShaChain.shaChainCodec)) :: ("originChannels" | originsMapCodec) :: ("remoteChannelData_opt" | optional(bool8, varsizebinarydata)) - })).as[EncodedCommitments].xmap( - encoded => encoded.toCommitments, - commitments => EncodedCommitments(commitments) - ) + })).as[EncodedCommitments].map(_.toCommitments).decodeOnly val commitmentsCodecWithLocalTxs: Codec[Commitments] = ( ("params" | paramsCodec) :: @@ -621,10 +598,7 @@ private[channel] object ChannelCodecs4 { ("remotePerCommitmentSecrets" | byteAligned(ShaChain.shaChainCodec)) :: ("originChannels" | originsMapCodec) :: ("remoteChannelData_opt" | optional(bool8, varsizebinarydata)) - })).as[EncodedCommitments].xmap( - encoded => encoded.toCommitments, - commitments => EncodedCommitments(commitments) - ) + })).as[EncodedCommitments].map(_.toCommitments).decodeOnly val commitmentsCodec: Codec[Commitments] = ( ("params" | paramsCodec) :: @@ -636,10 +610,7 @@ private[channel] object ChannelCodecs4 { ("remotePerCommitmentSecrets" | byteAligned(ShaChain.shaChainCodec)) :: ("originChannels" | originsMapCodec) :: ("remoteChannelData_opt" | optional(bool8, varsizebinarydata)) - })).as[EncodedCommitments].xmap( - encoded => encoded.toCommitments, - commitments => EncodedCommitments(commitments) - ) + })).as[EncodedCommitments].map(_.toCommitments).decodeOnly val versionedCommitmentsCodec: Codec[Commitments] = discriminated[Commitments].by(uint8) .typecase(0x02, commitmentsCodec) @@ -724,57 +695,59 @@ private[channel] object ChannelCodecs4 { // We don't bother removing the duplication across HTLCs: this is a short-lived state during which the channel // cannot be used for payments. - private val (interactiveTxWaitingForSigsWithoutLiquidityPurchaseCodec, interactiveTxWaitingForSigsWithTxsCodec, interactiveTxWaitingForSigsCodec): (Codec[InteractiveTxSigningSession.WaitingForSigs], Codec[InteractiveTxSigningSession.WaitingForSigs], Codec[InteractiveTxSigningSession.WaitingForSigs]) = { - val unsignedLocalCommitWithTxsCodec: Codec[UnsignedLocalCommit] = ( + private val (interactiveTxWaitingForSigsWithoutLiquidityPurchaseCodec, interactiveTxWaitingForSigsWithTxsCodec, interactiveTxWaitingForSigsCodec): (Codec[ChannelTypes4.WaitingForSigs], Codec[ChannelTypes4.WaitingForSigs], Codec[ChannelTypes4.WaitingForSigs]) = { + val unsignedLocalCommitWithTxsCodec: Codec[ChannelTypes4.UnsignedLocalCommit] = ( ("index" | uint64overflow) :: ("spec" | commitmentSpecCodec) :: ("commitTx" | commitTxCodec) :: - ("htlcTxs" | listOfN(uint16, htlcTxCodec))).as[ChannelTypes3.UnsignedLocalCommit].decodeOnly.map[UnsignedLocalCommit](_.migrate()).decodeOnly + ("htlcTxs" | listOfN(uint16, htlcTxCodec))).map { + case index :: spec :: commitTx :: _ :: HNil => ChannelTypes4.UnsignedLocalCommit(index, spec, commitTx.tx.txid, commitTx.input) + }.decodeOnly - val unsignedLocalCommitCodec: Codec[UnsignedLocalCommit] = ( + val unsignedLocalCommitCodec: Codec[ChannelTypes4.UnsignedLocalCommit] = ( ("index" | uint64overflow) :: ("spec" | commitmentSpecCodec) :: ("txId" | txId) :: - ("input" | inputInfoCodec)).as[UnsignedLocalCommit] + ("input" | inputInfoCodec)).as[ChannelTypes4.UnsignedLocalCommit] - val waitingForSigsWithoutLiquidityPurchaseCodec: Codec[InteractiveTxSigningSession.WaitingForSigs] = ( + val waitingForSigsWithoutLiquidityPurchaseCodec: Codec[ChannelTypes4.WaitingForSigs] = ( ("fundingParams" | fundingParamsCodec) :: ("fundingTxIndex" | uint32) :: ("fundingTx" | partiallySignedSharedTransactionCodec) :: ("localCommit" | either(bool8, unsignedLocalCommitWithTxsCodec, localCommitWithTxsCodec(commitmentSpecCodec))) :: ("remoteCommit" | remoteCommitCodec(commitmentSpecCodec)) :: - ("liquidityPurchase" | provide(Option.empty[LiquidityAds.PurchaseBasicInfo]))).as[InteractiveTxSigningSession.WaitingForSigs] + ("liquidityPurchase" | provide(Option.empty[LiquidityAds.PurchaseBasicInfo]))).as[ChannelTypes4.WaitingForSigs] - val waitingForSigsWithTxsCodec: Codec[InteractiveTxSigningSession.WaitingForSigs] = ( + val waitingForSigsWithTxsCodec: Codec[ChannelTypes4.WaitingForSigs] = ( ("fundingParams" | fundingParamsCodec) :: ("fundingTxIndex" | uint32) :: ("fundingTx" | partiallySignedSharedTransactionCodec) :: ("localCommit" | either(bool8, unsignedLocalCommitWithTxsCodec, localCommitWithTxsCodec(commitmentSpecCodec))) :: ("remoteCommit" | remoteCommitCodec(commitmentSpecCodec)) :: - ("liquidityPurchase" | optional(bool8, liquidityPurchaseCodec))).as[InteractiveTxSigningSession.WaitingForSigs] + ("liquidityPurchase" | optional(bool8, liquidityPurchaseCodec))).as[ChannelTypes4.WaitingForSigs] - val waitingForSigsCodec: Codec[InteractiveTxSigningSession.WaitingForSigs] = ( + val waitingForSigsCodec: Codec[ChannelTypes4.WaitingForSigs] = ( ("fundingParams" | fundingParamsCodec) :: ("fundingTxIndex" | uint32) :: ("fundingTx" | partiallySignedSharedTransactionCodec) :: ("localCommit" | either(bool8, unsignedLocalCommitCodec, localCommitCodec(commitmentSpecCodec))) :: ("remoteCommit" | remoteCommitCodec(commitmentSpecCodec)) :: - ("liquidityPurchase" | optional(bool8, liquidityPurchaseCodec))).as[InteractiveTxSigningSession.WaitingForSigs] + ("liquidityPurchase" | optional(bool8, liquidityPurchaseCodec))).as[ChannelTypes4.WaitingForSigs] (waitingForSigsWithoutLiquidityPurchaseCodec, waitingForSigsWithTxsCodec, waitingForSigsCodec) } - val dualFundingStatusCodec: Codec[DualFundingStatus] = discriminated[DualFundingStatus].by(uint8) + def dualFundingStatusCodec(commitments: Commitments): Codec[DualFundingStatus] = discriminated[DualFundingStatus].by(uint8) .\(0x01) { case status: DualFundingStatus if !status.isInstanceOf[DualFundingStatus.RbfWaitingForSigs] => DualFundingStatus.WaitingForConfirmations }(provide(DualFundingStatus.WaitingForConfirmations)) - .\(0x04) { case status: DualFundingStatus.RbfWaitingForSigs => status }(interactiveTxWaitingForSigsCodec.as[DualFundingStatus.RbfWaitingForSigs]) - .\(0x03) { case status: DualFundingStatus.RbfWaitingForSigs => status }(interactiveTxWaitingForSigsWithTxsCodec.as[DualFundingStatus.RbfWaitingForSigs]) - .\(0x02) { case status: DualFundingStatus.RbfWaitingForSigs => status }(interactiveTxWaitingForSigsWithoutLiquidityPurchaseCodec.as[DualFundingStatus.RbfWaitingForSigs]) + .\(0x04) { case status: DualFundingStatus.RbfWaitingForSigs => status }(interactiveTxWaitingForSigsCodec.map(_.migrate(commitments)).decodeOnly.as[DualFundingStatus.RbfWaitingForSigs]) + .\(0x03) { case status: DualFundingStatus.RbfWaitingForSigs => status }(interactiveTxWaitingForSigsWithTxsCodec.map(_.migrate(commitments)).decodeOnly.as[DualFundingStatus.RbfWaitingForSigs]) + .\(0x02) { case status: DualFundingStatus.RbfWaitingForSigs => status }(interactiveTxWaitingForSigsWithoutLiquidityPurchaseCodec.map(_.migrate(commitments)).decodeOnly.as[DualFundingStatus.RbfWaitingForSigs]) - val spliceStatusCodec: Codec[SpliceStatus] = discriminated[SpliceStatus].by(uint8) + def spliceStatusCodec(commitments: Commitments): Codec[SpliceStatus] = discriminated[SpliceStatus].by(uint8) .\(0x01) { case status: SpliceStatus if !status.isInstanceOf[SpliceStatus.SpliceWaitingForSigs] => SpliceStatus.NoSplice }(provide(SpliceStatus.NoSplice)) - .\(0x04) { case status: SpliceStatus.SpliceWaitingForSigs => status }(interactiveTxWaitingForSigsCodec.as[channel.SpliceStatus.SpliceWaitingForSigs]) - .\(0x03) { case status: SpliceStatus.SpliceWaitingForSigs => status }(interactiveTxWaitingForSigsWithTxsCodec.as[channel.SpliceStatus.SpliceWaitingForSigs]) - .\(0x02) { case status: SpliceStatus.SpliceWaitingForSigs => status }(interactiveTxWaitingForSigsWithoutLiquidityPurchaseCodec.as[channel.SpliceStatus.SpliceWaitingForSigs]) + .\(0x04) { case status: SpliceStatus.SpliceWaitingForSigs => status }(interactiveTxWaitingForSigsCodec.map(_.migrate(commitments)).decodeOnly.as[channel.SpliceStatus.SpliceWaitingForSigs]) + .\(0x03) { case status: SpliceStatus.SpliceWaitingForSigs => status }(interactiveTxWaitingForSigsWithTxsCodec.map(_.migrate(commitments)).decodeOnly.as[channel.SpliceStatus.SpliceWaitingForSigs]) + .\(0x02) { case status: SpliceStatus.SpliceWaitingForSigs => status }(interactiveTxWaitingForSigsWithoutLiquidityPurchaseCodec.map(_.migrate(commitments)).decodeOnly.as[channel.SpliceStatus.SpliceWaitingForSigs]) private val shortids: Codec[ChannelTypes4.ShortIds] = ( ("real_opt" | optional(bool8, realshortchannelid)) :: @@ -812,7 +785,10 @@ private[channel] object ChannelCodecs4 { ("localPushAmount" | millisatoshi) :: ("remotePushAmount" | millisatoshi) :: ("status" | interactiveTxWaitingForSigsWithoutLiquidityPurchaseCodec) :: - ("remoteChannelData_opt" | optional(bool8, varsizebinarydata))).as[DATA_WAIT_FOR_DUAL_FUNDING_SIGNED] + ("remoteChannelData_opt" | optional(bool8, varsizebinarydata))).map { + case channelParams :: secondRemotePerCommitmentPoint :: localPushAmount :: remotePushAmount :: status :: _ :: HNil => + DATA_WAIT_FOR_DUAL_FUNDING_SIGNED(channelParams.migrate(), secondRemotePerCommitmentPoint, localPushAmount, remotePushAmount, status.migrate(channelParams)) + }.decodeOnly val DATA_WAIT_FOR_DUAL_FUNDING_SIGNED_13_Codec: Codec[DATA_WAIT_FOR_DUAL_FUNDING_SIGNED] = ( ("channelParams" | paramsCodec) :: @@ -820,7 +796,10 @@ private[channel] object ChannelCodecs4 { ("localPushAmount" | millisatoshi) :: ("remotePushAmount" | millisatoshi) :: ("status" | interactiveTxWaitingForSigsWithTxsCodec) :: - ("remoteChannelData_opt" | optional(bool8, varsizebinarydata))).as[DATA_WAIT_FOR_DUAL_FUNDING_SIGNED] + ("remoteChannelData_opt" | optional(bool8, varsizebinarydata))).map { + case channelParams :: secondRemotePerCommitmentPoint :: localPushAmount :: remotePushAmount :: status :: _ :: HNil => + DATA_WAIT_FOR_DUAL_FUNDING_SIGNED(channelParams.migrate(), secondRemotePerCommitmentPoint, localPushAmount, remotePushAmount, status.migrate(channelParams)) + }.decodeOnly val DATA_WAIT_FOR_DUAL_FUNDING_SIGNED_1c_Codec: Codec[DATA_WAIT_FOR_DUAL_FUNDING_SIGNED] = ( ("channelParams" | paramsCodec) :: @@ -828,25 +807,30 @@ private[channel] object ChannelCodecs4 { ("localPushAmount" | millisatoshi) :: ("remotePushAmount" | millisatoshi) :: ("status" | interactiveTxWaitingForSigsCodec) :: - ("remoteChannelData_opt" | optional(bool8, varsizebinarydata))).as[DATA_WAIT_FOR_DUAL_FUNDING_SIGNED] + ("remoteChannelData_opt" | optional(bool8, varsizebinarydata))).map { + case channelParams :: secondRemotePerCommitmentPoint :: localPushAmount :: remotePushAmount :: status :: _ :: HNil => + DATA_WAIT_FOR_DUAL_FUNDING_SIGNED(channelParams.migrate(), secondRemotePerCommitmentPoint, localPushAmount, remotePushAmount, status.migrate(channelParams)) + }.decodeOnly val DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED_02_Codec: Codec[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED] = ( - ("commitments" | commitmentsCodecWithoutFirstRemoteCommitIndex) :: + ("commitments" | commitmentsCodecWithoutFirstRemoteCommitIndex) >>:~ { commitments => ("localPushAmount" | millisatoshi) :: - ("remotePushAmount" | millisatoshi) :: - ("waitingSince" | blockHeight) :: - ("lastChecked" | blockHeight) :: - ("status" | dualFundingStatusCodec) :: - ("deferred" | optional(bool8, lengthDelimited(channelReadyCodec)))).as[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED] + ("remotePushAmount" | millisatoshi) :: + ("waitingSince" | blockHeight) :: + ("lastChecked" | blockHeight) :: + ("status" | dualFundingStatusCodec(commitments)) :: + ("deferred" | optional(bool8, lengthDelimited(channelReadyCodec))) + }).as[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED] val DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED_0c_Codec: Codec[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED] = ( - ("commitments" | versionedCommitmentsCodec) :: + ("commitments" | versionedCommitmentsCodec) >>:~ { commitments => ("localPushAmount" | millisatoshi) :: - ("remotePushAmount" | millisatoshi) :: - ("waitingSince" | blockHeight) :: - ("lastChecked" | blockHeight) :: - ("status" | dualFundingStatusCodec) :: - ("deferred" | optional(bool8, lengthDelimited(channelReadyCodec)))).as[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED] + ("remotePushAmount" | millisatoshi) :: + ("waitingSince" | blockHeight) :: + ("lastChecked" | blockHeight) :: + ("status" | dualFundingStatusCodec(commitments)) :: + ("deferred" | optional(bool8, lengthDelimited(channelReadyCodec))) + }).as[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED] val DATA_WAIT_FOR_DUAL_FUNDING_READY_03_Codec: Codec[DATA_WAIT_FOR_DUAL_FUNDING_READY] = ( ("commitments" | commitmentsCodecWithoutFirstRemoteCommitIndex) :: @@ -861,45 +845,55 @@ private[channel] object ChannelCodecs4 { ("aliases" | aliases)).as[DATA_WAIT_FOR_DUAL_FUNDING_READY] val DATA_NORMAL_04_Codec: Codec[DATA_NORMAL] = ( - ("commitments" | commitmentsCodecWithoutFirstRemoteCommitIndex) :: + ("commitments" | commitmentsCodecWithoutFirstRemoteCommitIndex) >>:~ { commitments => ("shortids" | shortids) :: - ("channelAnnouncement" | optional(bool8, lengthDelimited(channelAnnouncementCodec))) :: - ("channelUpdate" | lengthDelimited(channelUpdateCodec)) :: - ("localShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: - ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: - ("closingFeerates" | optional(bool8, closingFeeratesCodec)) :: - ("spliceStatus" | spliceStatusCodec)).as[ChannelTypes4.DATA_NORMAL_0e].map(_.migrate()).decodeOnly + ("channelAnnouncement" | optional(bool8, lengthDelimited(channelAnnouncementCodec))) :: + ("channelUpdate" | lengthDelimited(channelUpdateCodec)) :: + ("localShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: + ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: + ("closingFeerates" | optional(bool8, closingFeeratesCodec)) :: + ("spliceStatus" | spliceStatusCodec(commitments)) + }).as[ChannelTypes4.DATA_NORMAL_0e].map(_.migrate()).decodeOnly val DATA_NORMAL_0e_Codec: Codec[DATA_NORMAL] = ( - ("commitments" | versionedCommitmentsCodec) :: + ("commitments" | versionedCommitmentsCodec) >>:~ { commitments => ("shortids" | shortids) :: - ("channelAnnouncement" | optional(bool8, lengthDelimited(channelAnnouncementCodec))) :: - ("channelUpdate" | lengthDelimited(channelUpdateCodec)) :: - ("localShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: - ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: - ("closingFeerates" | optional(bool8, closingFeeratesCodec)) :: - ("spliceStatus" | spliceStatusCodec)).as[ChannelTypes4.DATA_NORMAL_0e].map(_.migrate()).decodeOnly + ("channelAnnouncement" | optional(bool8, lengthDelimited(channelAnnouncementCodec))) :: + ("channelUpdate" | lengthDelimited(channelUpdateCodec)) :: + ("localShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: + ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: + ("closingFeerates" | optional(bool8, closingFeeratesCodec)) :: + ("spliceStatus" | spliceStatusCodec(commitments)) + }).as[ChannelTypes4.DATA_NORMAL_0e].map(_.migrate()).decodeOnly val DATA_NORMAL_14_Codec: Codec[DATA_NORMAL] = ( - ("commitments" | versionedCommitmentsCodec) :: + ("commitments" | versionedCommitmentsCodec) >>:~ { commitments => ("aliases" | aliases) :: - ("channelAnnouncement" | optional(bool8, lengthDelimited(channelAnnouncementCodec))) :: - ("channelUpdate" | lengthDelimited(channelUpdateCodec)) :: - ("localShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: - ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: - // If there are closing fees defined we consider ourselves to be the closing initiator. - ("closingFeerates" | optional(bool8, closingFeeratesCodec).map[Option[CloseStatus]](feerates_opt => Some(CloseStatus.Initiator(feerates_opt))).decodeOnly) :: - ("spliceStatus" | spliceStatusCodec)).as[DATA_NORMAL] + ("channelAnnouncement" | optional(bool8, lengthDelimited(channelAnnouncementCodec))) :: + ("channelUpdate" | lengthDelimited(channelUpdateCodec)) :: + ("localShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: + ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: + // If there are closing fees defined we consider ourselves to be the closing initiator. + ("closingFeerates" | optional(bool8, closingFeeratesCodec).map[Option[CloseStatus]](feerates_opt => Some(CloseStatus.Initiator(feerates_opt))).decodeOnly) :: + ("spliceStatus" | spliceStatusCodec(commitments)) + }).map { + case commitments :: aliases :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closeStatus :: spliceStatus :: HNil => + DATA_NORMAL(commitments, aliases, channelAnnouncement, channelUpdate, spliceStatus, localShutdown, remoteShutdown, closeStatus) + }.decodeOnly val DATA_NORMAL_18_Codec: Codec[DATA_NORMAL] = ( - ("commitments" | versionedCommitmentsCodec) :: + ("commitments" | versionedCommitmentsCodec) >>:~ { commitments => ("aliases" | aliases) :: - ("channelAnnouncement" | optional(bool8, lengthDelimited(channelAnnouncementCodec))) :: - ("channelUpdate" | lengthDelimited(channelUpdateCodec)) :: - ("localShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: - ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: - ("closeStatus" | optional(bool8, closeStatusCodec)) :: - ("spliceStatus" | spliceStatusCodec)).as[DATA_NORMAL] + ("channelAnnouncement" | optional(bool8, lengthDelimited(channelAnnouncementCodec))) :: + ("channelUpdate" | lengthDelimited(channelUpdateCodec)) :: + ("localShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: + ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: + ("closeStatus" | optional(bool8, closeStatusCodec)) :: + ("spliceStatus" | spliceStatusCodec(commitments)) + }).map { + case commitments :: aliases :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closeStatus :: spliceStatus :: HNil => + DATA_NORMAL(commitments, aliases, channelAnnouncement, channelUpdate, spliceStatus, localShutdown, remoteShutdown, closeStatus) + }.decodeOnly val DATA_SHUTDOWN_05_Codec: Codec[DATA_SHUTDOWN] = ( ("commitments" | commitmentsCodecWithoutFirstRemoteCommitIndex) :: @@ -966,7 +960,8 @@ private[channel] object ChannelCodecs4 { ("remoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec_07)) :: ("nextRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec_07)) :: ("futureRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec_07)) :: - ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec_07))).as[DATA_CLOSING] + ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec_07)) :: + ("maxClosingFeerate" | provide(Option.empty[FeeratePerKw]))).as[DATA_CLOSING] val DATA_CLOSING_11_Codec: Codec[DATA_CLOSING] = ( ("commitments" | versionedCommitmentsCodec) :: @@ -978,7 +973,8 @@ private[channel] object ChannelCodecs4 { ("remoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec_07)) :: ("nextRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec_07)) :: ("futureRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec_07)) :: - ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec_07))).as[DATA_CLOSING] + ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec_07)) :: + ("maxClosingFeerate" | provide(Option.empty[FeeratePerKw]))).as[DATA_CLOSING] val DATA_CLOSING_1a_Codec: Codec[DATA_CLOSING] = ( ("commitments" | versionedCommitmentsCodec) :: @@ -990,7 +986,8 @@ private[channel] object ChannelCodecs4 { ("remoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec_07)) :: ("nextRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec_07)) :: ("futureRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec_07)) :: - ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec_1a))).as[DATA_CLOSING] + ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec_1a)) :: + ("maxClosingFeerate" | provide(Option.empty[FeeratePerKw]))).as[DATA_CLOSING] val DATA_CLOSING_1b_Codec: Codec[DATA_CLOSING] = ( ("commitments" | versionedCommitmentsCodec) :: @@ -1002,7 +999,8 @@ private[channel] object ChannelCodecs4 { ("remoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec)) :: ("nextRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec)) :: ("futureRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec)) :: - ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec))).as[DATA_CLOSING] + ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec)) :: + ("maxClosingFeerate" | provide(Option.empty[FeeratePerKw]))).as[DATA_CLOSING] val DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_08_Codec: Codec[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT] = ( ("commitments" | commitmentsCodecWithoutFirstRemoteCommitIndex) :: diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelTypes4.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelTypes4.scala index 55973eef7a..92d33c111b 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelTypes4.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelTypes4.scala @@ -16,68 +16,241 @@ package fr.acinq.eclair.wire.internal.channel.version4 -import fr.acinq.eclair.channel.LocalFundingStatus.ConfirmedFundingTx -import fr.acinq.eclair.channel._ -import fr.acinq.eclair.wire.protocol.{ChannelAnnouncement, ChannelUpdate, Shutdown} -import fr.acinq.eclair.{Alias, RealShortChannelId} +import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey +import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Satoshi, Transaction, TxId, TxOut} +import fr.acinq.eclair.blockchain.fee.FeeratePerKw +import fr.acinq.eclair.channel.fund.InteractiveTxBuilder.SignedSharedTransaction +import fr.acinq.eclair.channel.fund.{InteractiveTxBuilder, InteractiveTxSigningSession} +import fr.acinq.eclair.crypto.ShaChain +import fr.acinq.eclair.transactions.CommitmentSpec +import fr.acinq.eclair.transactions.Transactions.{CommitmentFormat, InputInfo} +import fr.acinq.eclair.wire.internal.channel.version0.ChannelTypes0 +import fr.acinq.eclair.wire.protocol.{ChannelAnnouncement, ChannelUpdate, LiquidityAds, Shutdown, TxSignatures} +import fr.acinq.eclair.{Alias, BlockHeight, CltvExpiryDelta, Features, InitFeature, MilliSatoshi, RealShortChannelId, UInt64, channel} +import scodec.bits.ByteVector private[channel] object ChannelTypes4 { // We moved the real scid inside each commitment object when adding DATA_NORMAL_14_Codec. case class ShortIds(real_opt: Option[RealShortChannelId], localAlias: Alias, remoteAlias_opt: Option[Alias]) + // We split remote params into separate channel params and commitment params when moving to channel codecs v5. + case class RemoteParams(nodeId: PublicKey, + dustLimit: Satoshi, + maxHtlcValueInFlightMsat: UInt64, + initialRequestedChannelReserve_opt: Option[Satoshi], + htlcMinimum: MilliSatoshi, + toSelfDelay: CltvExpiryDelta, + maxAcceptedHtlcs: Int, + revocationBasepoint: PublicKey, + paymentBasepoint: PublicKey, + delayedPaymentBasepoint: PublicKey, + htlcBasepoint: PublicKey, + initFeatures: Features[InitFeature], + upfrontShutdownScript_opt: Option[ByteVector]) { + def migrate(): channel.RemoteChannelParams = channel.RemoteChannelParams( + nodeId = nodeId, + initialRequestedChannelReserve_opt = initialRequestedChannelReserve_opt, + revocationBasepoint = revocationBasepoint, + paymentBasepoint = paymentBasepoint, + delayedPaymentBasepoint = delayedPaymentBasepoint, + htlcBasepoint = htlcBasepoint, + initFeatures = initFeatures, + upfrontShutdownScript_opt = upfrontShutdownScript_opt, + ) + } + + case class ChannelParams(channelId: ByteVector32, + channelConfig: channel.ChannelConfig, + channelFeatures: channel.ChannelFeatures, + localParams: ChannelTypes0.LocalParams, remoteParams: RemoteParams, + channelFlags: channel.ChannelFlags) { + def migrate(): channel.ChannelParams = channel.ChannelParams(channelId, channelConfig, channelFeatures, localParams.migrate(), remoteParams.migrate(), channelFlags) + + def localCommitParams(): channel.CommitParams = channel.CommitParams(localParams.dustLimit, localParams.htlcMinimum, localParams.maxHtlcValueInFlightMsat, localParams.maxAcceptedHtlcs, remoteParams.toSelfDelay) + + def remoteCommitParams(): channel.CommitParams = channel.CommitParams(remoteParams.dustLimit, remoteParams.htlcMinimum, remoteParams.maxHtlcValueInFlightMsat, remoteParams.maxAcceptedHtlcs, localParams.toSelfDelay) + } + + // We added the commitment format when moving to channel codecs v5. + case class InteractiveTxParams(channelId: ByteVector32, + isInitiator: Boolean, + localContribution: Satoshi, + remoteContribution: Satoshi, + sharedInput_opt: Option[InteractiveTxBuilder.SharedFundingInput], + remoteFundingPubKey: PublicKey, + localOutputs: List[TxOut], + lockTime: Long, + dustLimit: Satoshi, + targetFeerate: FeeratePerKw, + requireConfirmedInputs: InteractiveTxBuilder.RequireConfirmedInputs) { + def migrate(commitmentFormat: CommitmentFormat): InteractiveTxBuilder.InteractiveTxParams = InteractiveTxBuilder.InteractiveTxParams( + channelId = channelId, + isInitiator = isInitiator, + localContribution = localContribution, + remoteContribution = remoteContribution, + sharedInput_opt = sharedInput_opt, + remoteFundingPubKey = remoteFundingPubKey, + localOutputs = localOutputs, + commitmentFormat = commitmentFormat, + lockTime = lockTime, + dustLimit = dustLimit, + targetFeerate = targetFeerate, + requireConfirmedInputs = requireConfirmedInputs, + ) + } + + // We removed the signed transaction when confirmed to save space when moving to channel codecs v5. + sealed trait LocalFundingStatus { + def migrate(commitmentFormat: CommitmentFormat): channel.LocalFundingStatus + } + + case class SingleFundedUnconfirmedFundingTx(signedTx_opt: Option[Transaction]) extends LocalFundingStatus { + override def migrate(commitmentFormat: CommitmentFormat): channel.LocalFundingStatus.SingleFundedUnconfirmedFundingTx = channel.LocalFundingStatus.SingleFundedUnconfirmedFundingTx(signedTx_opt) + } + + case class DualFundedUnconfirmedFundingTx(sharedTx: SignedSharedTransaction, createdAt: BlockHeight, fundingParams: InteractiveTxParams, liquidityPurchase_opt: Option[LiquidityAds.PurchaseBasicInfo]) extends LocalFundingStatus { + override def migrate(commitmentFormat: CommitmentFormat): channel.LocalFundingStatus.DualFundedUnconfirmedFundingTx = channel.LocalFundingStatus.DualFundedUnconfirmedFundingTx(sharedTx, createdAt, fundingParams.migrate(commitmentFormat), liquidityPurchase_opt) + } + + case class ZeroconfPublishedFundingTx(tx: Transaction, localSigs_opt: Option[TxSignatures], liquidityPurchase_opt: Option[LiquidityAds.PurchaseBasicInfo]) extends LocalFundingStatus { + override def migrate(commitmentFormat: CommitmentFormat): channel.LocalFundingStatus.ZeroconfPublishedFundingTx = channel.LocalFundingStatus.ZeroconfPublishedFundingTx(tx, localSigs_opt, liquidityPurchase_opt) + } + + case class ConfirmedFundingTx(tx: Transaction, shortChannelId: RealShortChannelId, localSigs_opt: Option[TxSignatures], liquidityPurchase_opt: Option[LiquidityAds.PurchaseBasicInfo]) extends LocalFundingStatus { + override def migrate(commitmentFormat: CommitmentFormat): channel.LocalFundingStatus.ConfirmedFundingTx = { + val txOut = tx.txOut(shortChannelId.outputIndex) + channel.LocalFundingStatus.ConfirmedFundingTx(txOut, shortChannelId, localSigs_opt, liquidityPurchase_opt) + } + } + + // We move the input to the Commitment class instead of the LocalCommit when moving to channel codecs v5. + case class LocalCommit(index: Long, spec: CommitmentSpec, txId: TxId, input: InputInfo, remoteSig: channel.ChannelSpendSignature, htlcRemoteSigs: List[ByteVector64]) { + def migrate(): channel.LocalCommit = channel.LocalCommit(index, spec, txId, remoteSig, htlcRemoteSigs) + } + + case class Commitment(fundingTxIndex: Long, + firstRemoteCommitIndex: Long, + remoteFundingPubKey: PublicKey, + localFundingStatus: LocalFundingStatus, remoteFundingStatus: channel.RemoteFundingStatus, + localCommit: LocalCommit, remoteCommit: channel.RemoteCommit, nextRemoteCommit_opt: Option[channel.NextRemoteCommit]) { + def migrate(params: ChannelParams): channel.Commitment = channel.Commitment( + fundingTxIndex = fundingTxIndex, + firstRemoteCommitIndex = firstRemoteCommitIndex, + fundingInput = localCommit.input.outPoint, + fundingAmount = localCommit.input.txOut.amount, + remoteFundingPubKey = remoteFundingPubKey, + localFundingStatus = localFundingStatus.migrate(params.channelFeatures.commitmentFormat), + remoteFundingStatus = remoteFundingStatus, + commitmentFormat = params.channelFeatures.commitmentFormat, + localCommitParams = params.localCommitParams(), + localCommit = localCommit.migrate(), + remoteCommitParams = params.remoteCommitParams(), + remoteCommit = remoteCommit, + nextRemoteCommit_opt = nextRemoteCommit_opt + ) + } + + case class Commitments(params: ChannelParams, + changes: channel.CommitmentChanges, + active: Seq[Commitment], + inactive: Seq[Commitment] = Nil, + remoteNextCommitInfo: Either[channel.WaitForRev, PublicKey], + remotePerCommitmentSecrets: ShaChain, + originChannels: Map[Long, channel.Origin], + remoteChannelData_opt: Option[ByteVector]) + + case class UnsignedLocalCommit(index: Long, spec: CommitmentSpec, txId: TxId, input: InputInfo) { + def migrate(): InteractiveTxSigningSession.UnsignedLocalCommit = InteractiveTxSigningSession.UnsignedLocalCommit(index, spec, txId) + } + + case class WaitingForSigs(fundingParams: InteractiveTxParams, + fundingTxIndex: Long, + fundingTx: InteractiveTxBuilder.PartiallySignedSharedTransaction, + localCommit: Either[UnsignedLocalCommit, LocalCommit], + remoteCommit: channel.RemoteCommit, + liquidityPurchase_opt: Option[LiquidityAds.PurchaseBasicInfo]) { + def migrate(params: ChannelParams): InteractiveTxSigningSession.WaitingForSigs = InteractiveTxSigningSession.WaitingForSigs( + fundingParams = fundingParams.migrate(params.channelFeatures.commitmentFormat), + fundingTxIndex = fundingTxIndex, + fundingTx = fundingTx, + localCommitParams = params.localCommitParams(), + localCommit = localCommit match { + case Left(unsigned) => Left(unsigned.migrate()) + case Right(signed) => Right(signed.migrate()) + }, + remoteCommitParams = params.remoteCommitParams(), + remoteCommit = remoteCommit, + liquidityPurchase_opt = liquidityPurchase_opt, + ) + + def migrate(commitments: channel.Commitments): InteractiveTxSigningSession.WaitingForSigs = InteractiveTxSigningSession.WaitingForSigs( + fundingParams = fundingParams.migrate(commitments.latest.commitmentFormat), + fundingTxIndex = fundingTxIndex, + fundingTx = fundingTx, + localCommitParams = commitments.latest.localCommitParams, + localCommit = localCommit match { + case Left(unsigned) => Left(unsigned.migrate()) + case Right(signed) => Right(signed.migrate()) + }, + remoteCommitParams = commitments.latest.remoteCommitParams, + remoteCommit = remoteCommit, + liquidityPurchase_opt = liquidityPurchase_opt, + ) + } + // We moved the channel_announcement inside each commitment object when adding DATA_NORMAL_14_Codec. - case class DATA_NORMAL_0e(commitments: Commitments, + case class DATA_NORMAL_0e(commitments: channel.Commitments, shortIds: ShortIds, channelAnnouncement: Option[ChannelAnnouncement], channelUpdate: ChannelUpdate, localShutdown: Option[Shutdown], remoteShutdown: Option[Shutdown], - closingFeerates: Option[ClosingFeerates], - spliceStatus: SpliceStatus) { - def migrate(): DATA_NORMAL = { + closingFeerates: Option[channel.ClosingFeerates], + spliceStatus: channel.SpliceStatus) { + def migrate(): channel.DATA_NORMAL = { val commitments1 = commitments.copy( active = commitments.active.map(c => setScidIfMatches(c, shortIds)), inactive = commitments.inactive.map(c => setScidIfMatches(c, shortIds)), ) - val aliases = ShortIdAliases(shortIds.localAlias, shortIds.remoteAlias_opt) + val aliases = channel.ShortIdAliases(shortIds.localAlias, shortIds.remoteAlias_opt) val closeStatus_opt = if (localShutdown.nonEmpty) { - Some(CloseStatus.Initiator(closingFeerates)) + Some(channel.CloseStatus.Initiator(closingFeerates)) } else if (remoteShutdown.nonEmpty) { - Some(CloseStatus.NonInitiator(closingFeerates)) + Some(channel.CloseStatus.NonInitiator(closingFeerates)) } else None - DATA_NORMAL(commitments1, aliases, channelAnnouncement, channelUpdate, localShutdown, remoteShutdown, closeStatus_opt, spliceStatus) + channel.DATA_NORMAL(commitments1, aliases, channelAnnouncement, channelUpdate, spliceStatus, localShutdown, remoteShutdown, closeStatus_opt) } } - case class DATA_WAIT_FOR_CHANNEL_READY_0b(commitments: Commitments, shortIds: ShortIds) { - def migrate(): DATA_WAIT_FOR_CHANNEL_READY = { + case class DATA_WAIT_FOR_CHANNEL_READY_0b(commitments: channel.Commitments, shortIds: ShortIds) { + def migrate(): channel.DATA_WAIT_FOR_CHANNEL_READY = { val commitments1 = commitments.copy( active = commitments.active.map(c => setScidIfMatches(c, shortIds)), inactive = commitments.inactive.map(c => setScidIfMatches(c, shortIds)), ) - val aliases = ShortIdAliases(shortIds.localAlias, shortIds.remoteAlias_opt) - DATA_WAIT_FOR_CHANNEL_READY(commitments1, aliases) + val aliases = channel.ShortIdAliases(shortIds.localAlias, shortIds.remoteAlias_opt) + channel.DATA_WAIT_FOR_CHANNEL_READY(commitments1, aliases) } } - case class DATA_WAIT_FOR_DUAL_FUNDING_READY_0d(commitments: Commitments, shortIds: ShortIds) { - def migrate(): DATA_WAIT_FOR_DUAL_FUNDING_READY = { + case class DATA_WAIT_FOR_DUAL_FUNDING_READY_0d(commitments: channel.Commitments, shortIds: ShortIds) { + def migrate(): channel.DATA_WAIT_FOR_DUAL_FUNDING_READY = { val commitments1 = commitments.copy( active = commitments.active.map(c => setScidIfMatches(c, shortIds)), inactive = commitments.inactive.map(c => setScidIfMatches(c, shortIds)), ) - val aliases = ShortIdAliases(shortIds.localAlias, shortIds.remoteAlias_opt) - DATA_WAIT_FOR_DUAL_FUNDING_READY(commitments1, aliases) + val aliases = channel.ShortIdAliases(shortIds.localAlias, shortIds.remoteAlias_opt) + channel.DATA_WAIT_FOR_DUAL_FUNDING_READY(commitments1, aliases) } } - private def setScidIfMatches(c: Commitment, shortIds: ShortIds): Commitment = { + private def setScidIfMatches(c: channel.Commitment, shortIds: ShortIds): channel.Commitment = { c.localFundingStatus match { // We didn't support splicing on public channels in this version: the scid (if available) is for the initial // funding transaction. For private channels we don't care about the real scid, it will be set correctly after // the next splice. - case f: ConfirmedFundingTx if c.fundingTxIndex == 0 => + case f: channel.LocalFundingStatus.ConfirmedFundingTx if c.fundingTxIndex == 0 => val scid = shortIds.real_opt.getOrElse(f.shortChannelId) c.copy(localFundingStatus = f.copy(shortChannelId = scid)) case _ => c diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5.scala new file mode 100644 index 0000000000..c30d1c3de4 --- /dev/null +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5.scala @@ -0,0 +1,510 @@ +/* + * Copyright 2025 ACINQ SAS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package fr.acinq.eclair.wire.internal.channel.version5 + +import fr.acinq.bitcoin.scalacompat.DeterministicWallet.KeyPath +import fr.acinq.bitcoin.scalacompat.{OutPoint, ScriptWitness, Transaction, TxOut} +import fr.acinq.eclair.channel._ +import fr.acinq.eclair.channel.fund.{InteractiveTxBuilder, InteractiveTxSigningSession} +import fr.acinq.eclair.crypto.ShaChain +import fr.acinq.eclair.transactions.Transactions.{ClosingTx, ClosingTxs, InputInfo} +import fr.acinq.eclair.transactions._ +import fr.acinq.eclair.wire.protocol.CommonCodecs._ +import fr.acinq.eclair.wire.protocol.LightningMessageCodecs._ +import fr.acinq.eclair.wire.protocol.{LiquidityAds, UpdateAddHtlc, UpdateMessage} +import fr.acinq.eclair.{FeatureSupport, Features, PermanentChannelFeature} +import scodec.bits.{BitVector, ByteVector} +import scodec.codecs._ +import scodec.{Attempt, Codec} + +/** + * Created by t-bast on 18/06/2025. + */ + +private[channel] object ChannelCodecs5 { + + private[version5] object Codecs { + private val keyPathCodec: Codec[KeyPath] = ("path" | listOfN(uint16, uint32)).xmap[KeyPath](l => KeyPath(l), keyPath => keyPath.path.toList).as[KeyPath] + private val outPointCodec: Codec[OutPoint] = lengthDelimited(bytes.xmap(d => OutPoint.read(d.toArray), d => OutPoint.write(d))) + private val txOutCodec: Codec[TxOut] = lengthDelimited(bytes.xmap(d => TxOut.read(d.toArray), d => TxOut.write(d))) + private val txCodec: Codec[Transaction] = lengthDelimited(bytes.xmap(d => Transaction.read(d.toArray), d => Transaction.write(d))) + private val scriptWitnessCodec: Codec[ScriptWitness] = listOfN(uint16, lengthDelimited(bytes)).xmap(s => ScriptWitness(s.toSeq), w => w.stack.toList) + + private val inputInfoCodec: Codec[InputInfo] = (("outPoint" | outPointCodec) :: ("txOut" | txOutCodec)).as[InputInfo] + + private val channelSpendSignatureCodec: Codec[ChannelSpendSignature] = discriminated[ChannelSpendSignature].by(uint8) + .typecase(0x01, bytes64.as[ChannelSpendSignature.IndividualSignature]) + .typecase(0x02, (("partialSig" | bytes32) :: ("nonce" | publicNonce)).as[ChannelSpendSignature.PartialSignatureWithNonce]) + + private def setCodec[T](codec: Codec[T]): Codec[Set[T]] = listOfN(uint16, codec).xmap(_.toSet, _.toList) + + private def mapCodec[K, V](keyCodec: Codec[K], valueCodec: Codec[V]): Codec[Map[K, V]] = listOfN(uint16, keyCodec ~ valueCodec).xmap(_.toMap, _.toList) + + private val channelConfigCodec: Codec[ChannelConfig] = lengthDelimited(bytes).xmap(b => { + val activated: Set[ChannelConfigOption] = b.bits.toIndexedSeq.reverse.zipWithIndex.collect { + case (true, 0) => ChannelConfig.FundingPubKeyBasedChannelKeyPath + }.toSet + ChannelConfig(activated) + }, cfg => { + val indices = cfg.options.map(_.supportBit) + if (indices.isEmpty) { + ByteVector.empty + } else { + // NB: when converting from BitVector to ByteVector, scodec pads right instead of left, so we make sure we pad to bytes *before* setting bits. + var buffer = BitVector.fill(indices.max + 1)(high = false).bytes.bits + indices.foreach(i => buffer = buffer.set(i)) + buffer.reverse.bytes + } + }) + + /** We use the same encoding as init features, even if we don't need the distinction between mandatory and optional */ + private val channelFeaturesCodec: Codec[ChannelFeatures] = lengthDelimited(bytes).xmap( + (b: ByteVector) => ChannelFeatures(Features(b).activated.keySet.collect { case f: PermanentChannelFeature => f }), // we make no difference between mandatory/optional, both are considered activated + (cf: ChannelFeatures) => Features(cf.features.map(f => f -> FeatureSupport.Mandatory).toMap).toByteVector // we encode features as mandatory, by convention + ) + + private val commitmentFormatCodec: Codec[Transactions.CommitmentFormat] = discriminated[Transactions.CommitmentFormat].by(uint8) + .typecase(0x00, provide(Transactions.DefaultCommitmentFormat)) + .typecase(0x01, provide(Transactions.UnsafeLegacyAnchorOutputsCommitmentFormat)) + .typecase(0x02, provide(Transactions.ZeroFeeHtlcTxAnchorOutputsCommitmentFormat)) + .typecase(0x03, provide(Transactions.LegacySimpleTaprootChannelCommitmentFormat)) + .typecase(0x04, provide(Transactions.ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat)) + + private val localChannelParamsCodec: Codec[LocalChannelParams] = ( + ("nodeId" | publicKey) :: + ("channelPath" | keyPathCodec) :: + ("channelReserve" | optional(bool8, satoshi)) :: + // We pad to keep codecs byte-aligned. + ("isChannelOpener" | bool) :: ("paysCommitTxFees" | bool) :: ignore(6) :: + ("upfrontShutdownScript_opt" | optional(bool8, lengthDelimited(bytes))) :: + ("walletStaticPaymentBasepoint" | optional(bool8, publicKey)) :: + ("features" | combinedFeaturesCodec)).as[LocalChannelParams] + + val remoteChannelParamsCodec: Codec[RemoteChannelParams] = ( + ("nodeId" | publicKey) :: + ("channelReserve" | optional(bool8, satoshi)) :: + ("revocationBasepoint" | publicKey) :: + ("paymentBasepoint" | publicKey) :: + ("delayedPaymentBasepoint" | publicKey) :: + ("htlcBasepoint" | publicKey) :: + ("features" | combinedFeaturesCodec) :: + ("shutdownScript" | optional(bool8, lengthDelimited(bytes)))).as[RemoteChannelParams] + + private val channelParamsCodec: Codec[ChannelParams] = ( + ("channelId" | bytes32) :: + ("channelConfig" | channelConfigCodec) :: + ("channelFeatures" | channelFeaturesCodec) :: + ("localParams" | localChannelParamsCodec) :: + ("remoteParams" | remoteChannelParamsCodec) :: + ("channelFlags" | channelflags)).as[ChannelParams] + + private val commitParamsCodec: Codec[CommitParams] = ( + ("dustLimit" | satoshi) :: + ("htlcMinimum" | millisatoshi) :: + ("maxHtlcValueInFlight" | uint64) :: + ("maxAcceptedHtlcs" | uint16) :: + ("toSelfDelay" | cltvExpiryDelta)).as[CommitParams] + + private val multisig2of2InputCodec: Codec[InteractiveTxBuilder.Multisig2of2Input] = ( + ("info" | inputInfoCodec) :: + ("fundingTxIndex" | uint32) :: + ("remoteFundingPubkey" | publicKey)).as[InteractiveTxBuilder.Multisig2of2Input] + + private val sharedFundingInputCodec: Codec[InteractiveTxBuilder.SharedFundingInput] = discriminated[InteractiveTxBuilder.SharedFundingInput].by(uint16) + .typecase(0x01, multisig2of2InputCodec) + + private val requireConfirmedInputsCodec: Codec[InteractiveTxBuilder.RequireConfirmedInputs] = (("forLocal" | bool8) :: ("forRemote" | bool8)).as[InteractiveTxBuilder.RequireConfirmedInputs] + + private val fundingParamsCodec: Codec[InteractiveTxBuilder.InteractiveTxParams] = ( + ("channelId" | bytes32) :: + ("isInitiator" | bool8) :: + ("localContribution" | satoshiSigned) :: + ("remoteContribution" | satoshiSigned) :: + ("sharedInput_opt" | optional(bool8, sharedFundingInputCodec)) :: + ("remoteFundingPubKey" | publicKey) :: + ("localOutputs" | listOfN(uint16, txOutCodec)) :: + ("commitmentFormat" | commitmentFormatCodec) :: + ("lockTime" | uint32) :: + ("dustLimit" | satoshi) :: + ("targetFeerate" | feeratePerKw) :: + ("requireConfirmedInputs" | requireConfirmedInputsCodec)).as[InteractiveTxBuilder.InteractiveTxParams] + + private val liquidityFeesCodec: Codec[LiquidityAds.Fees] = (("miningFees" | satoshi) :: ("serviceFees" | satoshi)).as[LiquidityAds.Fees] + private val liquidityPurchaseCodec: Codec[LiquidityAds.PurchaseBasicInfo] = (("isBuyer" | bool8) :: ("amount" | satoshi) :: ("fees" | liquidityFeesCodec)).as[LiquidityAds.PurchaseBasicInfo] + + private val sharedInteractiveTxInputCodec: Codec[InteractiveTxBuilder.Input.Shared] = ( + ("serialId" | uint64) :: + ("outPoint" | outPointCodec) :: + ("publicKeyScript" | lengthDelimited(bytes)) :: + ("sequence" | uint32) :: + ("localAmount" | millisatoshi) :: + ("remoteAmount" | millisatoshi) :: + ("htlcAmount" | millisatoshi)).as[InteractiveTxBuilder.Input.Shared] + + private val sharedInteractiveTxOutputCodec: Codec[InteractiveTxBuilder.Output.Shared] = ( + ("serialId" | uint64) :: + ("scriptPubKey" | lengthDelimited(bytes)) :: + ("localAmount" | millisatoshi) :: + ("remoteAmount" | millisatoshi) :: + ("htlcAmount" | millisatoshi)).as[InteractiveTxBuilder.Output.Shared] + + private val localOnlyInteractiveTxInputCodec: Codec[InteractiveTxBuilder.Input.Local] = ( + ("serialId" | uint64) :: + ("previousTx" | txCodec) :: + ("previousTxOutput" | uint32) :: + ("sequence" | uint32)).as[InteractiveTxBuilder.Input.Local] + + private val localInteractiveTxInputCodec: Codec[InteractiveTxBuilder.Input.Local] = discriminated[InteractiveTxBuilder.Input.Local].by(byte) + .typecase(0x01, localOnlyInteractiveTxInputCodec) + + private val remoteOnlyInteractiveTxInputCodec: Codec[InteractiveTxBuilder.Input.Remote] = ( + ("serialId" | uint64) :: + ("outPoint" | outPointCodec) :: + ("txOut" | txOutCodec) :: + ("sequence" | uint32)).as[InteractiveTxBuilder.Input.Remote] + + private val remoteInteractiveTxInputCodec: Codec[InteractiveTxBuilder.Input.Remote] = discriminated[InteractiveTxBuilder.Input.Remote].by(byte) + .typecase(0x01, remoteOnlyInteractiveTxInputCodec) + + private val localInteractiveTxChangeOutputCodec: Codec[InteractiveTxBuilder.Output.Local.Change] = ( + ("serialId" | uint64) :: + ("amount" | satoshi) :: + ("scriptPubKey" | lengthDelimited(bytes))).as[InteractiveTxBuilder.Output.Local.Change] + + private val localInteractiveTxNonChangeOutputCodec: Codec[InteractiveTxBuilder.Output.Local.NonChange] = ( + ("serialId" | uint64) :: + ("amount" | satoshi) :: + ("scriptPubKey" | lengthDelimited(bytes))).as[InteractiveTxBuilder.Output.Local.NonChange] + + private val localInteractiveTxOutputCodec: Codec[InteractiveTxBuilder.Output.Local] = discriminated[InteractiveTxBuilder.Output.Local].by(byte) + .typecase(0x01, localInteractiveTxChangeOutputCodec) + .typecase(0x02, localInteractiveTxNonChangeOutputCodec) + + private val remoteStandardInteractiveTxOutputCodec: Codec[InteractiveTxBuilder.Output.Remote] = ( + ("serialId" | uint64) :: + ("amount" | satoshi) :: + ("scriptPubKey" | lengthDelimited(bytes))).as[InteractiveTxBuilder.Output.Remote] + + private val remoteInteractiveTxOutputCodec: Codec[InteractiveTxBuilder.Output.Remote] = discriminated[InteractiveTxBuilder.Output.Remote].by(byte) + .typecase(0x01, remoteStandardInteractiveTxOutputCodec) + + private val sharedTransactionCodec: Codec[InteractiveTxBuilder.SharedTransaction] = ( + ("sharedInput" | optional(bool8, sharedInteractiveTxInputCodec)) :: + ("sharedOutput" | sharedInteractiveTxOutputCodec) :: + ("localInputs" | listOfN(uint16, localInteractiveTxInputCodec)) :: + ("remoteInputs" | listOfN(uint16, remoteInteractiveTxInputCodec)) :: + ("localOutputs" | listOfN(uint16, localInteractiveTxOutputCodec)) :: + ("remoteOutputs" | listOfN(uint16, remoteInteractiveTxOutputCodec)) :: + ("lockTime" | uint32)).as[InteractiveTxBuilder.SharedTransaction] + + private val partiallySignedSharedTransactionCodec: Codec[InteractiveTxBuilder.PartiallySignedSharedTransaction] = ( + ("sharedTx" | sharedTransactionCodec) :: + ("localSigs" | lengthDelimited(txSignaturesCodec))).as[InteractiveTxBuilder.PartiallySignedSharedTransaction] + + private val fullySignedSharedTransactionCodec: Codec[InteractiveTxBuilder.FullySignedSharedTransaction] = ( + ("sharedTx" | sharedTransactionCodec) :: + ("localSigs" | lengthDelimited(txSignaturesCodec)) :: + ("remoteSigs" | lengthDelimited(txSignaturesCodec)) :: + ("sharedSigs_opt" | optional(bool8, scriptWitnessCodec))).as[InteractiveTxBuilder.FullySignedSharedTransaction] + + private val signedSharedTransactionCodec: Codec[InteractiveTxBuilder.SignedSharedTransaction] = discriminated[InteractiveTxBuilder.SignedSharedTransaction].by(uint16) + .typecase(0x01, partiallySignedSharedTransactionCodec) + .typecase(0x02, fullySignedSharedTransactionCodec) + + private val localFundingStatusCodec: Codec[LocalFundingStatus] = discriminated[LocalFundingStatus].by(uint8) + .typecase(0x04, (txOutCodec :: realshortchannelid :: optional(bool8, lengthDelimited(txSignaturesCodec)) :: optional(bool8, liquidityPurchaseCodec)).as[LocalFundingStatus.ConfirmedFundingTx]) + .typecase(0x03, (txCodec :: optional(bool8, lengthDelimited(txSignaturesCodec)) :: optional(bool8, liquidityPurchaseCodec)).as[LocalFundingStatus.ZeroconfPublishedFundingTx]) + .typecase(0x02, (signedSharedTransactionCodec :: blockHeight :: fundingParamsCodec :: optional(bool8, liquidityPurchaseCodec)).as[LocalFundingStatus.DualFundedUnconfirmedFundingTx]) + .typecase(0x01, optional(bool8, txCodec).as[LocalFundingStatus.SingleFundedUnconfirmedFundingTx]) + + private val remoteFundingStatusCodec: Codec[RemoteFundingStatus] = discriminated[RemoteFundingStatus].by(uint8) + .typecase(0x01, provide(RemoteFundingStatus.NotLocked)) + .typecase(0x02, provide(RemoteFundingStatus.Locked)) + + private val htlcCodec: Codec[DirectedHtlc] = discriminated[DirectedHtlc].by(uint8) + .typecase(0x01, lengthDelimited(updateAddHtlcCodec).as[IncomingHtlc]) + .typecase(0x02, lengthDelimited(updateAddHtlcCodec).as[OutgoingHtlc]) + + private def minimalHtlcCodec(htlcs: Set[UpdateAddHtlc]): Codec[UpdateAddHtlc] = uint64overflow.xmap[UpdateAddHtlc](id => htlcs.find(_.id == id).get, _.id) + + private def minimalDirectedHtlcCodec(htlcs: Set[DirectedHtlc]): Codec[DirectedHtlc] = discriminated[DirectedHtlc].by(uint8) + .typecase(0x01, minimalHtlcCodec(htlcs.collect(DirectedHtlc.incoming)).as[IncomingHtlc]) + .typecase(0x02, minimalHtlcCodec(htlcs.collect(DirectedHtlc.outgoing)).as[OutgoingHtlc]) + + private def baseCommitmentSpecCodec(directedHtlcCodec: Codec[DirectedHtlc]): Codec[CommitmentSpec] = ( + ("htlcs" | setCodec(directedHtlcCodec)) :: + ("feeratePerKw" | feeratePerKw) :: + ("toLocal" | millisatoshi) :: + ("toRemote" | millisatoshi)).as[CommitmentSpec] + + /** HTLCs are stored separately to avoid duplicating data. */ + private def minimalCommitmentSpecCodec(htlcs: Set[DirectedHtlc]): Codec[CommitmentSpec] = baseCommitmentSpecCodec(minimalDirectedHtlcCodec(htlcs)) + + /** HTLCs are stored in full, the codec is stateless but creates duplication between local/remote commitment, and across commitments. */ + private val commitmentSpecCodec: Codec[CommitmentSpec] = baseCommitmentSpecCodec(htlcCodec) + + // Note that we use the default commitmentSpec codec that fully encodes HTLCs. This creates some duplication, but + // it's fine because this is a short-lived state during which the channel cannot be used for payments. + private val unsignedLocalCommitCodec: Codec[InteractiveTxSigningSession.UnsignedLocalCommit] = ( + ("index" | uint64overflow) :: + ("spec" | commitmentSpecCodec) :: + ("txId" | txId)).as[InteractiveTxSigningSession.UnsignedLocalCommit] + + private def localCommitCodec(commitmentSpecCodec: Codec[CommitmentSpec]): Codec[LocalCommit] = ( + ("index" | uint64overflow) :: + ("spec" | commitmentSpecCodec) :: + ("txId" | txId) :: + ("remoteSig" | channelSpendSignatureCodec) :: + ("htlcRemoteSigs" | listOfN(uint16, bytes64))).as[LocalCommit] + + private def remoteCommitCodec(commitmentSpecCodec: Codec[CommitmentSpec]): Codec[RemoteCommit] = ( + ("index" | uint64overflow) :: + ("spec" | commitmentSpecCodec) :: + ("txid" | txId) :: + ("remotePerCommitmentPoint" | publicKey)).as[RemoteCommit] + + private def nextRemoteCommitCodec(commitmentSpecCodec: Codec[CommitmentSpec]): Codec[NextRemoteCommit] = ( + ("sig" | lengthDelimited(commitSigCodec)) :: + ("commit" | remoteCommitCodec(commitmentSpecCodec))).as[NextRemoteCommit] + + private def commitmentCodec(htlcs: Set[DirectedHtlc]): Codec[Commitment] = ( + ("fundingTxIndex" | uint32) :: + ("firstRemoteCommitIndex" | uint64overflow) :: + ("fundingInput" | outPointCodec) :: + ("fundingAmount" | satoshi) :: + ("remoteFundingPubKey" | publicKey) :: + ("fundingTxStatus" | localFundingStatusCodec) :: + ("remoteFundingStatus" | remoteFundingStatusCodec) :: + ("commitmentFormat" | commitmentFormatCodec) :: + ("localCommitParams" | commitParamsCodec) :: + ("localCommit" | localCommitCodec(minimalCommitmentSpecCodec(htlcs))) :: + ("remoteCommitParams" | commitParamsCodec) :: + ("remoteCommit" | remoteCommitCodec(minimalCommitmentSpecCodec(htlcs.map(_.opposite)))) :: + ("nextRemoteCommit_opt" | optional(bool8, nextRemoteCommitCodec(minimalCommitmentSpecCodec(htlcs.map(_.opposite)))))).as[Commitment] + + private val waitForRevCodec: Codec[WaitForRev] = ("sentAfterLocalCommitIndex" | uint64overflow).as[WaitForRev] + + private val updateMessageCodec: Codec[UpdateMessage] = lengthDelimited(lightningMessageCodec.narrow[UpdateMessage](f => Attempt.successful(f.asInstanceOf[UpdateMessage]), g => g)) + + private val localChangesCodec: Codec[LocalChanges] = ( + ("proposed" | listOfN(uint16, updateMessageCodec)) :: + ("signed" | listOfN(uint16, updateMessageCodec)) :: + ("acked" | listOfN(uint16, updateMessageCodec))).as[LocalChanges] + + private val remoteChangesCodec: Codec[RemoteChanges] = ( + ("proposed" | listOfN(uint16, updateMessageCodec)) :: + ("acked" | listOfN(uint16, updateMessageCodec)) :: + ("signed" | listOfN(uint16, updateMessageCodec))).as[RemoteChanges] + + private val changesCodec: Codec[CommitmentChanges] = ( + ("localChanges" | localChangesCodec) :: + ("remoteChanges" | remoteChangesCodec) :: + ("localNextHtlcId" | uint64overflow) :: + ("remoteNextHtlcId" | uint64overflow)).as[CommitmentChanges] + + private val upstreamChannelCodec: Codec[Upstream.Cold.Channel] = ( + ("originChannelId" | bytes32) :: + ("originHtlcId" | int64) :: + ("amountIn" | millisatoshi)).as[Upstream.Cold.Channel] + + private val coldUpstreamCodec: Codec[Upstream.Cold] = discriminated[Upstream.Cold].by(uint16) + // NB: order matters! + .typecase(0x03, upstreamChannelCodec) + .typecase(0x02, listOfN(uint16, upstreamChannelCodec).as[Upstream.Cold.Trampoline]) + .typecase(0x01, ("id" | uuid).as[Upstream.Local]) + + private val originCodec: Codec[Origin] = coldUpstreamCodec.xmap[Origin]( + upstream => Origin.Cold(upstream), + { + case Origin.Hot(_, upstream) => Upstream.Cold(upstream) + case Origin.Cold(upstream) => upstream + } + ) + + private val originsMapCodec: Codec[Map[Long, Origin]] = mapCodec(int64, originCodec) + + private val commitmentsCodec: Codec[ChannelTypes5.EncodedCommitments] = ( + ("params" | channelParamsCodec) :: + ("changes" | changesCodec) :: + (("htlcs" | setCodec(htlcCodec)) >>:~ { htlcs => + ("active" | listOfN(uint16, commitmentCodec(htlcs))) :: + ("inactive" | listOfN(uint16, commitmentCodec(htlcs))) :: + ("remoteNextCommitInfo" | either(bool8, waitForRevCodec, publicKey)) :: + ("remotePerCommitmentSecrets" | byteAligned(ShaChain.shaChainCodec)) :: + ("originChannels" | originsMapCodec) :: + ("remoteChannelData_opt" | optional(bool8, varsizebinarydata)) + })).as[ChannelTypes5.EncodedCommitments] + + private val versionedCommitmentsCodec: Codec[Commitments] = discriminated[Commitments].by(uint8) + .typecase(0x01, commitmentsCodec.xmap(_.toCommitments, c => ChannelTypes5.EncodedCommitments.fromCommitments(c))) + + // Note that we use the default commitmentSpec codec that fully encodes HTLCs. This creates some duplication, but + // it's fine because this is a short-lived state during which the channel cannot be used for payments. + private val interactiveTxWaitingForSigsCodec: Codec[InteractiveTxSigningSession.WaitingForSigs] = ( + ("fundingParams" | fundingParamsCodec) :: + ("fundingTxIndex" | uint32) :: + ("fundingTx" | partiallySignedSharedTransactionCodec) :: + ("localCommitParams" | commitParamsCodec) :: + ("localCommit" | either(bool8, unsignedLocalCommitCodec, localCommitCodec(commitmentSpecCodec))) :: + ("remoteCommitParams" | commitParamsCodec) :: + ("remoteCommit" | remoteCommitCodec(commitmentSpecCodec)) :: + ("liquidityPurchase" | optional(bool8, liquidityPurchaseCodec))).as[InteractiveTxSigningSession.WaitingForSigs] + + val dualFundingStatusCodec: Codec[DualFundingStatus] = discriminated[DualFundingStatus].by(uint8) + .\(0x01) { case status: DualFundingStatus if !status.isInstanceOf[DualFundingStatus.RbfWaitingForSigs] => DualFundingStatus.WaitingForConfirmations }(provide(DualFundingStatus.WaitingForConfirmations)) + .\(0x02) { case status: DualFundingStatus.RbfWaitingForSigs => status }(interactiveTxWaitingForSigsCodec.as[DualFundingStatus.RbfWaitingForSigs]) + + private val spliceStatusCodec: Codec[SpliceStatus] = discriminated[SpliceStatus].by(uint8) + .\(0x01) { case status: SpliceStatus if !status.isInstanceOf[SpliceStatus.SpliceWaitingForSigs] => SpliceStatus.NoSplice }(provide(SpliceStatus.NoSplice)) + .\(0x02) { case status: SpliceStatus.SpliceWaitingForSigs => status }(interactiveTxWaitingForSigsCodec.as[SpliceStatus.SpliceWaitingForSigs]) + + private val closingFeeratesCodec: Codec[ClosingFeerates] = (("preferred" | feeratePerKw) :: ("min" | feeratePerKw) :: ("max" | feeratePerKw)).as[ClosingFeerates] + + private val closeStatusCodec: Codec[CloseStatus] = discriminated[CloseStatus].by(uint8) + .typecase(0x01, optional(bool8, closingFeeratesCodec).as[CloseStatus.Initiator]) + .typecase(0x02, optional(bool8, closingFeeratesCodec).as[CloseStatus.NonInitiator]) + + private val closingTxCodec: Codec[ClosingTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("outputIndex" | optional(bool8, uint32))).as[ClosingTx] + + private val closingTxsCodec: Codec[ClosingTxs] = ( + ("localAndRemote_opt" | optional(bool8, closingTxCodec)) :: + ("localOnly_opt" | optional(bool8, closingTxCodec)) :: + ("remoteOnly_opt" | optional(bool8, closingTxCodec))).as[ClosingTxs] + + private val closingTxProposedCodec: Codec[ClosingTxProposed] = (("unsignedTx" | closingTxCodec) :: ("localClosingSigned" | lengthDelimited(closingSignedCodec))).as[ClosingTxProposed] + + private val spentMapCodec: Codec[Map[OutPoint, Transaction]] = mapCodec(outPointCodec, txCodec) + + private val localCommitPublishedCodec: Codec[LocalCommitPublished] = ( + ("commitTx" | txCodec) :: + ("localOutput_opt" | optional(bool8, outPointCodec)) :: + ("anchorOutput_opt" | optional(bool8, outPointCodec)) :: + ("incomingHtlcs" | mapCodec(outPointCodec, uint64overflow)) :: + ("outgoingHtlcs" | mapCodec(outPointCodec, uint64overflow)) :: + ("htlcDelayedOutputs" | setCodec(outPointCodec)) :: + ("irrevocablySpent" | spentMapCodec)).as[LocalCommitPublished] + + private val remoteCommitPublishedCodec: Codec[RemoteCommitPublished] = ( + ("commitTx" | txCodec) :: + ("localOutput_opt" | optional(bool8, outPointCodec)) :: + ("anchorOutput_opt" | optional(bool8, outPointCodec)) :: + ("incomingHtlcs" | mapCodec(outPointCodec, uint64overflow)) :: + ("outgoingHtlcs" | mapCodec(outPointCodec, uint64overflow)) :: + ("irrevocablySpent" | spentMapCodec)).as[RemoteCommitPublished] + + private val revokedCommitPublishedCodec: Codec[RevokedCommitPublished] = ( + ("commitTx" | txCodec) :: + ("localOutput_opt" | optional(bool8, outPointCodec)) :: + ("remoteOutput_opt" | optional(bool8, outPointCodec)) :: + ("htlcOutputs" | setCodec(outPointCodec)) :: + ("htlcDelayedOutputs" | setCodec(outPointCodec)) :: + ("irrevocablySpent" | spentMapCodec)).as[RevokedCommitPublished] + + val DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec: Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = ( + ("commitments" | versionedCommitmentsCodec) :: + ("waitingSince" | blockHeight) :: + ("deferred" | optional(bool8, lengthDelimited(channelReadyCodec))) :: + ("lastSent" | either(bool8, lengthDelimited(fundingCreatedCodec), lengthDelimited(fundingSignedCodec)))).as[DATA_WAIT_FOR_FUNDING_CONFIRMED] + + val DATA_WAIT_FOR_CHANNEL_READY_Codec: Codec[DATA_WAIT_FOR_CHANNEL_READY] = ( + ("commitments" | versionedCommitmentsCodec) :: + ("aliases" | aliases)).as[DATA_WAIT_FOR_CHANNEL_READY] + + val DATA_WAIT_FOR_DUAL_FUNDING_SIGNED_Codec: Codec[DATA_WAIT_FOR_DUAL_FUNDING_SIGNED] = ( + ("channelParams" | channelParamsCodec) :: + ("secondRemotePerCommitmentPoint" | publicKey) :: + ("localPushAmount" | millisatoshi) :: + ("remotePushAmount" | millisatoshi) :: + ("status" | interactiveTxWaitingForSigsCodec)).as[DATA_WAIT_FOR_DUAL_FUNDING_SIGNED] + + val DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED_Codec: Codec[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED] = ( + ("commitments" | versionedCommitmentsCodec) :: + ("localPushAmount" | millisatoshi) :: + ("remotePushAmount" | millisatoshi) :: + ("waitingSince" | blockHeight) :: + ("lastChecked" | blockHeight) :: + ("status" | dualFundingStatusCodec) :: + ("deferred" | optional(bool8, lengthDelimited(channelReadyCodec)))).as[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED] + + val DATA_WAIT_FOR_DUAL_FUNDING_READY_Codec: Codec[DATA_WAIT_FOR_DUAL_FUNDING_READY] = ( + ("commitments" | versionedCommitmentsCodec) :: + ("aliases" | aliases)).as[DATA_WAIT_FOR_DUAL_FUNDING_READY] + + val DATA_NORMAL_Codec: Codec[DATA_NORMAL] = ( + ("commitments" | versionedCommitmentsCodec) :: + ("aliases" | aliases) :: + ("channelAnnouncement" | optional(bool8, lengthDelimited(channelAnnouncementCodec))) :: + ("channelUpdate" | lengthDelimited(channelUpdateCodec)) :: + ("spliceStatus" | spliceStatusCodec) :: + ("localShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: + ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: + ("closeStatus" | optional(bool8, closeStatusCodec))).as[DATA_NORMAL] + + val DATA_SHUTDOWN_Codec: Codec[DATA_SHUTDOWN] = ( + ("commitments" | versionedCommitmentsCodec) :: + ("localShutdown" | lengthDelimited(shutdownCodec)) :: + ("remoteShutdown" | lengthDelimited(shutdownCodec)) :: + ("closeStatus" | closeStatusCodec)).as[DATA_SHUTDOWN] + + val DATA_NEGOTIATING_Codec: Codec[DATA_NEGOTIATING] = ( + ("commitments" | versionedCommitmentsCodec) :: + ("localShutdown" | lengthDelimited(shutdownCodec)) :: + ("remoteShutdown" | lengthDelimited(shutdownCodec)) :: + ("closingTxProposed" | listOfN(uint16, listOfN(uint16, lengthDelimited(closingTxProposedCodec)))) :: + ("bestUnpublishedClosingTx_opt" | optional(bool8, closingTxCodec))).as[DATA_NEGOTIATING] + + val DATA_NEGOTIATING_SIMPLE_Codec: Codec[DATA_NEGOTIATING_SIMPLE] = ( + ("commitments" | versionedCommitmentsCodec) :: + ("lastClosingFeerate" | feeratePerKw) :: + ("localScriptPubKey" | varsizebinarydata) :: + ("remoteScriptPubKey" | varsizebinarydata) :: + ("proposedClosingTxs" | listOfN(uint16, closingTxsCodec)) :: + ("publishedClosingTxs" | listOfN(uint16, closingTxCodec))).as[DATA_NEGOTIATING_SIMPLE] + + val DATA_CLOSING_Codec: Codec[DATA_CLOSING] = ( + ("commitments" | versionedCommitmentsCodec) :: + ("waitingSince" | blockHeight) :: + ("finalScriptPubKey" | lengthDelimited(bytes)) :: + ("mutualCloseProposed" | listOfN(uint16, closingTxCodec)) :: + ("mutualClosePublished" | listOfN(uint16, closingTxCodec)) :: + ("localCommitPublished" | optional(bool8, localCommitPublishedCodec)) :: + ("remoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec)) :: + ("nextRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec)) :: + ("futureRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec)) :: + ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec)) :: + ("maxClosingFeerate" | optional(bool8, feeratePerKw))).as[DATA_CLOSING] + + val DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec: Codec[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT] = ( + ("commitments" | versionedCommitmentsCodec) :: + ("remoteChannelReestablish" | channelReestablishCodec)).as[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT] + } + + // Order matters! + val channelDataCodec: Codec[PersistentChannelData] = discriminated[PersistentChannelData].by(uint16) + .typecase(0x0b, Codecs.DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec) + .typecase(0x0a, Codecs.DATA_CLOSING_Codec) + .typecase(0x09, Codecs.DATA_NEGOTIATING_SIMPLE_Codec) + .typecase(0x08, Codecs.DATA_NEGOTIATING_Codec) + .typecase(0x07, Codecs.DATA_SHUTDOWN_Codec) + .typecase(0x06, Codecs.DATA_NORMAL_Codec) + .typecase(0x05, Codecs.DATA_WAIT_FOR_DUAL_FUNDING_READY_Codec) + .typecase(0x04, Codecs.DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED_Codec) + .typecase(0x03, Codecs.DATA_WAIT_FOR_DUAL_FUNDING_SIGNED_Codec) + .typecase(0x02, Codecs.DATA_WAIT_FOR_CHANNEL_READY_Codec) + .typecase(0x01, Codecs.DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec) + +} diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelTypes5.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelTypes5.scala new file mode 100644 index 0000000000..eb61d3bb95 --- /dev/null +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelTypes5.scala @@ -0,0 +1,84 @@ +/* + * Copyright 2025 ACINQ SAS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package fr.acinq.eclair.wire.internal.channel.version5 + +import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey +import fr.acinq.eclair.channel._ +import fr.acinq.eclair.crypto.ShaChain +import fr.acinq.eclair.transactions.DirectedHtlc +import scodec.bits.ByteVector + +/** + * Created by t-bast on 18/06/2025. + */ + +private[channel] object ChannelTypes5 { + + /** + * When multiple commitments are active, htlcs are shared between all of these commitments. + * There may be up to 2 * 483 = 966 htlcs, and every htlc uses at least 1452 bytes and at most 65536 bytes. + * The resulting htlc set size is thus between 1,4 MB and 64 MB, which can be pretty large. + * To avoid writing that htlc set multiple times to disk, we encode it separately. + */ + case class EncodedCommitments(channelParams: ChannelParams, + changes: CommitmentChanges, + // The direction we use is from our local point of view. + htlcs: Set[DirectedHtlc], + active: List[Commitment], + inactive: List[Commitment], + remoteNextCommitInfo: Either[WaitForRev, PublicKey], + remotePerCommitmentSecrets: ShaChain, + originChannels: Map[Long, Origin], + remoteChannelData_opt: Option[ByteVector]) { + def toCommitments: Commitments = { + Commitments( + channelParams = channelParams, + changes = changes, + active = active, + inactive = inactive, + remoteNextCommitInfo = remoteNextCommitInfo, + remotePerCommitmentSecrets = remotePerCommitmentSecrets, + originChannels = originChannels, + remoteChannelData_opt = remoteChannelData_opt + ) + } + } + + object EncodedCommitments { + def fromCommitments(commitments: Commitments): EncodedCommitments = { + // The direction we use is from our local point of view: we use sets, which deduplicates htlcs that are in both + // local and remote commitments. All active commitments have the same htlc set, but each inactive commitment may + // have a distinct htlc set. + val commitmentsSet = commitments.active.head +: commitments.inactive + val htlcs = commitmentsSet.flatMap(_.localCommit.spec.htlcs).toSet ++ + commitmentsSet.flatMap(_.remoteCommit.spec.htlcs.map(_.opposite)).toSet ++ + commitmentsSet.flatMap(_.nextRemoteCommit_opt.toList.flatMap(_.commit.spec.htlcs.map(_.opposite))).toSet + EncodedCommitments( + channelParams = commitments.channelParams, + changes = commitments.changes, + htlcs = htlcs, + active = commitments.active.toList, + inactive = commitments.inactive.toList, + remoteNextCommitInfo = commitments.remoteNextCommitInfo, + remotePerCommitmentSecrets = commitments.remotePerCommitmentSecrets, + originChannels = commitments.originChannels, + remoteChannelData_opt = commitments.remoteChannelData_opt + ) + } + } + +} diff --git a/eclair-core/src/test/resources/nonreg/codecs/000003-DATA_NORMAL/fundee/data.json b/eclair-core/src/test/resources/nonreg/codecs/000003-DATA_NORMAL/fundee/data.json index 1f34a0cdee..7d6a120a96 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/000003-DATA_NORMAL/fundee/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/000003-DATA_NORMAL/fundee/data.json @@ -8,12 +8,7 @@ "localParams" : { "nodeId" : "03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134", "fundingKeyPath" : [ 1457788542, 1007597768, 1455922339, 479707306 ], - "dustLimit" : 546, - "maxHtlcValueInFlightMsat" : 5000000000, "initialRequestedChannelReserve_opt" : 167772, - "htlcMinimum" : 1, - "toRemoteDelay" : 720, - "maxAcceptedHtlcs" : 30, "isChannelOpener" : false, "paysCommitTxFees" : false, "upfrontShutdownScript_opt" : "a9144805d016e47885dc7c852710cdd8cd0d576f57ec87", @@ -28,12 +23,7 @@ }, "remoteParams" : { "nodeId" : "034fe52e98a0e9d3c21b767e1b371881265d8c7578c21f5afd6d6438da10348b36", - "dustLimit" : 573, - "maxHtlcValueInFlightMsat" : 16609443000, "initialRequestedChannelReserve_opt" : 167772, - "htlcMinimum" : 1000, - "toRemoteDelay" : 2016, - "maxAcceptedHtlcs" : 483, "revocationBasepoint" : "02635ac9eedf5f219afbc4d125e37b5705f73c05deca71b05fe84096a691e055c1", "paymentBasepoint" : "034a711d28e8ed3ad389ec14ec75c199b6a45140c503bcc88110e3524e52ffbfb1", "delayedPaymentBasepoint" : "0316c70730b57a9e15845ce6f239e749ac78b25f44c90485a697066962a73d0467", @@ -67,16 +57,22 @@ }, "active" : [ { "fundingTxIndex" : 0, - "fundingTx" : { - "outPoint" : "3dd6450c0bb55d6e4ef6ba6bd62d9061af1690e0c6ebca5b79246ac1228f7307:1", - "amountSatoshis" : 16777215 - }, + "fundingInput" : "3dd6450c0bb55d6e4ef6ba6bd62d9061af1690e0c6ebca5b79246ac1228f7307:1", + "fundingAmount" : 16777215, "localFunding" : { "status" : "unconfirmed" }, "remoteFunding" : { "status" : "locked" }, + "commitmentFormat" : "legacy", + "localCommitParams" : { + "dustLimit" : 546, + "htlcMinimum" : 1, + "maxHtlcValueInFlight" : 5000000000, + "maxAcceptedHtlcs" : 30, + "toSelfDelay" : 2016 + }, "localCommit" : { "index" : 7675, "spec" : { @@ -86,15 +82,18 @@ "toRemote" : 16572475271 }, "txId" : "e25a866b79212015e01e155e530fb547abc8276869f8740a9948e52ca231f1e4", - "input" : { - "outPoint" : "3dd6450c0bb55d6e4ef6ba6bd62d9061af1690e0c6ebca5b79246ac1228f7307:1", - "amountSatoshis" : 16777215 - }, "remoteSig" : { "sig" : "4d4d24b8cb3a00dfd685ac73e3c85ba26449dc935469ce36c259f2db6cd519a865845eca78a998bc8213044e84eca0c884cdb01bda8b6e70f5c1ff821ca5388d" }, "htlcRemoteSigs" : [ ] }, + "remoteCommitParams" : { + "dustLimit" : 573, + "htlcMinimum" : 1000, + "maxHtlcValueInFlight" : 16609443000, + "maxAcceptedHtlcs" : 483, + "toSelfDelay" : 720 + }, "remoteCommit" : { "index" : 7779, "spec" : { diff --git a/eclair-core/src/test/resources/nonreg/codecs/000003-DATA_NORMAL/funder/data.json b/eclair-core/src/test/resources/nonreg/codecs/000003-DATA_NORMAL/funder/data.json index e699ba38e5..ae804b4955 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/000003-DATA_NORMAL/funder/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/000003-DATA_NORMAL/funder/data.json @@ -8,12 +8,7 @@ "localParams" : { "nodeId" : "03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134", "fundingKeyPath" : [ 3561221353, 3653515793, 2711311691, 2863050005 ], - "dustLimit" : 546, - "maxHtlcValueInFlightMsat" : 1000000000, "initialRequestedChannelReserve_opt" : 150000, - "htlcMinimum" : 1, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 30, "isChannelOpener" : true, "paysCommitTxFees" : true, "upfrontShutdownScript_opt" : "a91445e990148599176534ec9b75df92ace9263f7d3487", @@ -28,12 +23,7 @@ }, "remoteParams" : { "nodeId" : "0269a94e8b32c005e4336bfb743c08a6e9beb13d940d57c479d95c8e687ccbdb9f", - "dustLimit" : 573, - "maxHtlcValueInFlightMsat" : 14850000000, "initialRequestedChannelReserve_opt" : 150000, - "htlcMinimum" : 1000, - "toRemoteDelay" : 1802, - "maxAcceptedHtlcs" : 483, "revocationBasepoint" : "03d17fdddddae4aeeb7022dedf059f1d0f06b4b68b6309cade4e55ae1ac0f0230c", "paymentBasepoint" : "03c0c4257191e5c4b6e7dcf2e9fb9be00fc713686f77fc4719987e77ee2436d8bd", "delayedPaymentBasepoint" : "03550b13a43d2b09649423e75774bb5a91a243bac78af4d39aece23380bb42b397", @@ -67,16 +57,22 @@ }, "active" : [ { "fundingTxIndex" : 0, - "fundingTx" : { - "outPoint" : "115641011cceeb4a1709a6cbd8f5f1b387460ee5fd2e48be3fbd1ae0e9e1cf6e:0", - "amountSatoshis" : 15000000 - }, + "fundingInput" : "115641011cceeb4a1709a6cbd8f5f1b387460ee5fd2e48be3fbd1ae0e9e1cf6e:0", + "fundingAmount" : 15000000, "localFunding" : { "status" : "unconfirmed" }, "remoteFunding" : { "status" : "locked" }, + "commitmentFormat" : "legacy", + "localCommitParams" : { + "dustLimit" : 546, + "htlcMinimum" : 1, + "maxHtlcValueInFlight" : 1000000000, + "maxAcceptedHtlcs" : 30, + "toSelfDelay" : 1802 + }, "localCommit" : { "index" : 20024, "spec" : { @@ -86,15 +82,18 @@ "toRemote" : 13656683380 }, "txId" : "65fe0b1f079fa763448df3ab8d94b1ad7d377c061121376be90b9c0c1bb0cd43", - "input" : { - "outPoint" : "115641011cceeb4a1709a6cbd8f5f1b387460ee5fd2e48be3fbd1ae0e9e1cf6e:0", - "amountSatoshis" : 15000000 - }, "remoteSig" : { "sig" : "bd09313503ea357b3a231135c87cd1f5b26cb3bd8033e371815b7e2b4af623173b9824adf260c8735a72c58087f88f4a2f39554003996466857c1d1b25c8044f" }, "htlcRemoteSigs" : [ ] }, + "remoteCommitParams" : { + "dustLimit" : 573, + "htlcMinimum" : 1000, + "maxHtlcValueInFlight" : 14850000000, + "maxAcceptedHtlcs" : 483, + "toSelfDelay" : 144 + }, "remoteCommit" : { "index" : 20024, "spec" : { diff --git a/eclair-core/src/test/resources/nonreg/codecs/020002-DATA_NORMAL/funder/data.json b/eclair-core/src/test/resources/nonreg/codecs/020002-DATA_NORMAL/funder/data.json index a594bbd9c4..85501e3877 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/020002-DATA_NORMAL/funder/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/020002-DATA_NORMAL/funder/data.json @@ -8,12 +8,7 @@ "localParams" : { "nodeId" : "03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134", "fundingKeyPath" : [ 2353764507, 3184449568, 2809819526, 3258060413, 392846475, 1545000620, 720603293, 1808318336, 2147483649 ], - "dustLimit" : 546, - "maxHtlcValueInFlightMsat" : 20000000000, "initialRequestedChannelReserve_opt" : 150000, - "htlcMinimum" : 1, - "toRemoteDelay" : 720, - "maxAcceptedHtlcs" : 30, "isChannelOpener" : true, "paysCommitTxFees" : true, "upfrontShutdownScript_opt" : "00148061b7fbd2d84ed1884177ea785faecb2080b103", @@ -35,12 +30,7 @@ }, "remoteParams" : { "nodeId" : "027455aef8453d92f4706b560b61527cc217ddf14da41770e8ed6607190a1851b8", - "dustLimit" : 573, - "maxHtlcValueInFlightMsat" : 14850000000, "initialRequestedChannelReserve_opt" : 150000, - "htlcMinimum" : 1, - "toRemoteDelay" : 1802, - "maxAcceptedHtlcs" : 483, "revocationBasepoint" : "0343bf4bfbaea5c100f1f2bf1cdf82a0ef97c9a0069a2aec631e7c3084ba929b75", "paymentBasepoint" : "03c54e7d5ccfc13f1a6c7a441ffcfac86248574d1bc0fe9773836f4c724ea7b2bd", "delayedPaymentBasepoint" : "03765aaac2e8fa6dbce7de5143072e9d9d5e96a1fd451d02fe4ff803f413f303f8", @@ -81,16 +71,22 @@ }, "active" : [ { "fundingTxIndex" : 0, - "fundingTx" : { - "outPoint" : "1bade1718aaf98ab1f91a97ed5b34ab47bfb78085e384f67c156793544f68659:0", - "amountSatoshis" : 15000000 - }, + "fundingInput" : "1bade1718aaf98ab1f91a97ed5b34ab47bfb78085e384f67c156793544f68659:0", + "fundingAmount" : 15000000, "localFunding" : { "status" : "unconfirmed" }, "remoteFunding" : { "status" : "locked" }, + "commitmentFormat" : "legacy", + "localCommitParams" : { + "dustLimit" : 546, + "htlcMinimum" : 1, + "maxHtlcValueInFlight" : 20000000000, + "maxAcceptedHtlcs" : 30, + "toSelfDelay" : 1802 + }, "localCommit" : { "index" : 4, "spec" : { @@ -100,15 +96,18 @@ "toRemote" : 0 }, "txId" : "fa747ecb6f718c6831cc7148cf8d65c3468d2bb6c202605e2b82d2277491222f", - "input" : { - "outPoint" : "1bade1718aaf98ab1f91a97ed5b34ab47bfb78085e384f67c156793544f68659:0", - "amountSatoshis" : 15000000 - }, "remoteSig" : { "sig" : "871afd240e20a171b9cba46f20555f848c5850f94ec7da7b33b9eeaf6af6653c119cda8cbf5f80986d6a4f0db2590c734d1de399a7060a477b5d94df0183625b" }, "htlcRemoteSigs" : [ ] }, + "remoteCommitParams" : { + "dustLimit" : 573, + "htlcMinimum" : 1, + "maxHtlcValueInFlight" : 14850000000, + "maxAcceptedHtlcs" : 483, + "toSelfDelay" : 720 + }, "remoteCommit" : { "index" : 4, "spec" : { diff --git a/eclair-core/src/test/resources/nonreg/codecs/030000-DATA_WAIT_FOR_FUNDING_CONFIRMED/funder/data.json b/eclair-core/src/test/resources/nonreg/codecs/030000-DATA_WAIT_FOR_FUNDING_CONFIRMED/funder/data.json index 0cb947422d..4df96939c6 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/030000-DATA_WAIT_FOR_FUNDING_CONFIRMED/funder/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/030000-DATA_WAIT_FOR_FUNDING_CONFIRMED/funder/data.json @@ -8,12 +8,7 @@ "localParams" : { "nodeId" : "02aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa", "fundingKeyPath" : [ 4092535092, 4227137620, 3959690417, 2298849496, 2106263857, 1090614243, 1495530077, 1280982866, 2147483649 ], - "dustLimit" : 1100, - "maxHtlcValueInFlightMsat" : 500000000, "initialRequestedChannelReserve_opt" : 10000, - "htlcMinimum" : 0, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 100, "isChannelOpener" : true, "paysCommitTxFees" : true, "upfrontShutdownScript_opt" : "0014fec406ef7a0258cb503fe1f1803787d971eeb4d1", @@ -31,12 +26,7 @@ }, "remoteParams" : { "nodeId" : "02bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e63", - "dustLimit" : 1000, - "maxHtlcValueInFlightMsat" : 18446744073709551615, "initialRequestedChannelReserve_opt" : 20000, - "htlcMinimum" : 1000, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 30, "revocationBasepoint" : "02f7c3bf47cdc640304eda4c761a26dfebfee561de15ba106f3d9982d3ef3fbe10", "paymentBasepoint" : "02be361b7bf1bdfb283cff3f83bf16f6f8fb67d3f480b541e76518939f667ab834", "delayedPaymentBasepoint" : "03fcaec5443dd423f160c9b77a48b2585b186f2d850147f57210d3f8a8c8d754a7", @@ -74,10 +64,8 @@ }, "active" : [ { "fundingTxIndex" : 0, - "fundingTx" : { - "outPoint" : "f4e3ba374da1a85abcd12a86c9a25b1391bda144619c770fe03f3881c6ad17e9:0", - "amountSatoshis" : 1000000 - }, + "fundingInput" : "f4e3ba374da1a85abcd12a86c9a25b1391bda144619c770fe03f3881c6ad17e9:0", + "fundingAmount" : 1000000, "localFunding" : { "status" : "unconfirmed", "txid" : "f4e3ba374da1a85abcd12a86c9a25b1391bda144619c770fe03f3881c6ad17e9" @@ -85,6 +73,14 @@ "remoteFunding" : { "status" : "locked" }, + "commitmentFormat" : "legacy", + "localCommitParams" : { + "dustLimit" : 1100, + "htlcMinimum" : 0, + "maxHtlcValueInFlight" : 500000000, + "maxAcceptedHtlcs" : 100, + "toSelfDelay" : 144 + }, "localCommit" : { "index" : 0, "spec" : { @@ -94,15 +90,18 @@ "toRemote" : 200000000 }, "txId" : "c6fe6bc0a5a9c149a03a907d2351714aa27fc98a485e981343cea08a1904ee26", - "input" : { - "outPoint" : "f4e3ba374da1a85abcd12a86c9a25b1391bda144619c770fe03f3881c6ad17e9:0", - "amountSatoshis" : 1000000 - }, "remoteSig" : { "sig" : "1ea9cffd2af82f6c14251bd59ffa3e876178c7b263f16d12790c33fc093eaed053dd9e0d49f79558aeb3c2fe7fe84f95a91eecbea2679fb65916ad9632df07b4" }, "htlcRemoteSigs" : [ ] }, + "remoteCommitParams" : { + "dustLimit" : 1000, + "htlcMinimum" : 1000, + "maxHtlcValueInFlight" : 18446744073709551615, + "maxAcceptedHtlcs" : 30, + "toSelfDelay" : 144 + }, "remoteCommit" : { "index" : 0, "spec" : { diff --git a/eclair-core/src/test/resources/nonreg/codecs/03000a-DATA_WAIT_FOR_CHANNEL_READY/funder/data.json b/eclair-core/src/test/resources/nonreg/codecs/03000a-DATA_WAIT_FOR_CHANNEL_READY/funder/data.json index ad75510f4e..93350edf5f 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/03000a-DATA_WAIT_FOR_CHANNEL_READY/funder/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/03000a-DATA_WAIT_FOR_CHANNEL_READY/funder/data.json @@ -8,12 +8,7 @@ "localParams" : { "nodeId" : "02aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa", "fundingKeyPath" : [ 303987973, 3198768511, 3783619274, 2277156978, 1699864653, 63358126, 3265052696, 516813756, 2147483649 ], - "dustLimit" : 1100, - "maxHtlcValueInFlightMsat" : 500000000, "initialRequestedChannelReserve_opt" : 10000, - "htlcMinimum" : 0, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 100, "isChannelOpener" : true, "paysCommitTxFees" : true, "upfrontShutdownScript_opt" : "0014c59265957886e166f37c863dca15b49aa42d75b4", @@ -32,12 +27,7 @@ }, "remoteParams" : { "nodeId" : "02bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e63", - "dustLimit" : 1000, - "maxHtlcValueInFlightMsat" : 1000000000, "initialRequestedChannelReserve_opt" : 20000, - "htlcMinimum" : 1000, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 30, "revocationBasepoint" : "033b164d14b54f08a951f9e40fb84e637021f376c9ae2907975f9ff0795431205d", "paymentBasepoint" : "03759da3f6bdcc2f595e1a98eb4803729743ab8608e28788cfe96726b5328c214c", "delayedPaymentBasepoint" : "03abc3ae99fac104e94f79cafcd70247cc336cdc21a53d4c3a4c321b54e20902e7", @@ -76,16 +66,22 @@ }, "active" : [ { "fundingTxIndex" : 0, - "fundingTx" : { - "outPoint" : "7d314422179e4e93e201da84b7b86cf9a23470933877f10db675f9ada8dea683:0", - "amountSatoshis" : 1000000 - }, + "fundingInput" : "7d314422179e4e93e201da84b7b86cf9a23470933877f10db675f9ada8dea683:0", + "fundingAmount" : 1000000, "localFunding" : { "status" : "unconfirmed" }, "remoteFunding" : { "status" : "locked" }, + "commitmentFormat" : "legacy", + "localCommitParams" : { + "dustLimit" : 1100, + "htlcMinimum" : 0, + "maxHtlcValueInFlight" : 500000000, + "maxAcceptedHtlcs" : 100, + "toSelfDelay" : 144 + }, "localCommit" : { "index" : 0, "spec" : { @@ -95,15 +91,18 @@ "toRemote" : 200000000 }, "txId" : "a591186503570022767b96fc4e02448e2c2d95f9e83f06c526f810886b02dedd", - "input" : { - "outPoint" : "7d314422179e4e93e201da84b7b86cf9a23470933877f10db675f9ada8dea683:0", - "amountSatoshis" : 1000000 - }, "remoteSig" : { "sig" : "8ccca40bbf6c859ceb2d5288153801426e19f8b119c2d1d4ba6d82511c7816aa0eaa8e41eafb179818d1ba7b0cfdce2da5fa002921df00a1fb4e1458d0b9042c" }, "htlcRemoteSigs" : [ ] }, + "remoteCommitParams" : { + "dustLimit" : 1000, + "htlcMinimum" : 1000, + "maxHtlcValueInFlight" : 1000000000, + "maxAcceptedHtlcs" : 30, + "toSelfDelay" : 144 + }, "remoteCommit" : { "index" : 0, "spec" : { diff --git a/eclair-core/src/test/resources/nonreg/codecs/03000c-DATA_WAIT_FOR_DUAL_FUNDING_READY/funder/data.json b/eclair-core/src/test/resources/nonreg/codecs/03000c-DATA_WAIT_FOR_DUAL_FUNDING_READY/funder/data.json index 039730f794..23fff3527e 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/03000c-DATA_WAIT_FOR_DUAL_FUNDING_READY/funder/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/03000c-DATA_WAIT_FOR_DUAL_FUNDING_READY/funder/data.json @@ -8,11 +8,6 @@ "localParams" : { "nodeId" : "02aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa", "fundingKeyPath" : [ 3109590638, 2571039769, 2759029525, 192289746, 3879532998, 1343053922, 3645251601, 1767821717, 2147483649 ], - "dustLimit" : 1100, - "maxHtlcValueInFlightMsat" : 500000000, - "htlcMinimum" : 0, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 100, "isChannelOpener" : true, "paysCommitTxFees" : true, "upfrontShutdownScript_opt" : "0014fe2baa428e1f6b4b4134d444b3de42a86cffbabc", @@ -35,11 +30,6 @@ }, "remoteParams" : { "nodeId" : "02bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e63", - "dustLimit" : 1000, - "maxHtlcValueInFlightMsat" : 1000000000, - "htlcMinimum" : 1000, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 30, "revocationBasepoint" : "03626342f0af6e87ab41715bd6d1db7d6eee5e95a2a835680b42d9b357003c1c6a", "paymentBasepoint" : "02ed6013749b4e3a820e7c003d3d3c7c5f16dd0d25ce72d3de99e544d4011c0a67", "delayedPaymentBasepoint" : "038a15a3dcd087135509b8e91d95c4fb788be11735f5c1a9ac24283d3cecbc76d2", @@ -82,16 +72,22 @@ }, "active" : [ { "fundingTxIndex" : 0, - "fundingTx" : { - "outPoint" : "7443277377ab5ca44330a332d79e6ff33d21a3b8889559f54894982af47e1cdb:0", - "amountSatoshis" : 1500000 - }, + "fundingInput" : "7443277377ab5ca44330a332d79e6ff33d21a3b8889559f54894982af47e1cdb:0", + "fundingAmount" : 1500000, "localFunding" : { "status" : "unconfirmed" }, "remoteFunding" : { "status" : "locked" }, + "commitmentFormat" : "anchor_outputs", + "localCommitParams" : { + "dustLimit" : 1100, + "htlcMinimum" : 0, + "maxHtlcValueInFlight" : 500000000, + "maxAcceptedHtlcs" : 100, + "toSelfDelay" : 144 + }, "localCommit" : { "index" : 0, "spec" : { @@ -101,15 +97,18 @@ "toRemote" : 500000000 }, "txId" : "b543f86a2a9a06f80b7339f9aaa169a779a7754d3523c28845d10dad9b9d7bc9", - "input" : { - "outPoint" : "7443277377ab5ca44330a332d79e6ff33d21a3b8889559f54894982af47e1cdb:0", - "amountSatoshis" : 1500000 - }, "remoteSig" : { "sig" : "5d65b42b0a8f05ca2793360a6b34bd5c1b9d960cf62c300c0e493f9cd83d2ca41f46a663d4fc11891ef268c0200b8fea8fb454d9055d35c2d6f5ba0b14c78b2a" }, "htlcRemoteSigs" : [ ] }, + "remoteCommitParams" : { + "dustLimit" : 1000, + "htlcMinimum" : 1000, + "maxHtlcValueInFlight" : 1000000000, + "maxAcceptedHtlcs" : 30, + "toSelfDelay" : 144 + }, "remoteCommit" : { "index" : 0, "spec" : { diff --git a/eclair-core/src/test/resources/nonreg/codecs/04000b-DATA_WAIT_FOR_CHANNEL_READY/fundee/data.json b/eclair-core/src/test/resources/nonreg/codecs/04000b-DATA_WAIT_FOR_CHANNEL_READY/fundee/data.json index 4eed16c4f0..3ae13daa2a 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/04000b-DATA_WAIT_FOR_CHANNEL_READY/fundee/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/04000b-DATA_WAIT_FOR_CHANNEL_READY/fundee/data.json @@ -8,12 +8,7 @@ "localParams" : { "nodeId" : "02bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e63", "fundingKeyPath" : [ 3266606892, 3454308995, 379409286, 2058386039, 150235166, 1337553882, 292124276, 1286028724, 2147483648 ], - "dustLimit" : 1000, - "maxHtlcValueInFlightMsat" : 1000000000, "initialRequestedChannelReserve_opt" : 20000, - "htlcMinimum" : 1000, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 30, "isChannelOpener" : false, "paysCommitTxFees" : false, "walletStaticPaymentBasepoint" : "028feba10d0eafd0fad8fe20e6d9206e6bd30242826de05c63f459a00aced24b12", @@ -37,12 +32,7 @@ }, "remoteParams" : { "nodeId" : "02aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa", - "dustLimit" : 1100, - "maxHtlcValueInFlightMsat" : 500000000, "initialRequestedChannelReserve_opt" : 10000, - "htlcMinimum" : 0, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 100, "revocationBasepoint" : "02e1a7010650cd5cd6fbf7505f5f213b36e5cc0d127064c74619c83dfa7434ce25", "paymentBasepoint" : "028feba10d0eafd0fad8fe20e6d9206e6bd30242826de05c63f459a00aced24b12", "delayedPaymentBasepoint" : "031723f6ffc8e552694f09d067844851f14357fb2a294fd94fd0b596ee808d2a17", @@ -86,18 +76,23 @@ }, "active" : [ { "fundingTxIndex" : 0, - "fundingTx" : { - "outPoint" : "1d8d1bf9e04f9a0ac27261cbee89a195ceda835b740f15767049e175cb5e977d:0", - "amountSatoshis" : 1000000 - }, + "fundingInput" : "1d8d1bf9e04f9a0ac27261cbee89a195ceda835b740f15767049e175cb5e977d:0", + "fundingAmount" : 1000000, "localFunding" : { "status" : "confirmed", - "txid" : "1d8d1bf9e04f9a0ac27261cbee89a195ceda835b740f15767049e175cb5e977d", "shortChannelId" : "400000x42x0" }, "remoteFunding" : { "status" : "not-locked" }, + "commitmentFormat" : "legacy", + "localCommitParams" : { + "dustLimit" : 1000, + "htlcMinimum" : 1000, + "maxHtlcValueInFlight" : 1000000000, + "maxAcceptedHtlcs" : 30, + "toSelfDelay" : 144 + }, "localCommit" : { "index" : 0, "spec" : { @@ -107,15 +102,18 @@ "toRemote" : 800000000 }, "txId" : "d873d760728d9cf12d61c43360c383ab83799c9153f38c6012d9d6f6e6026df1", - "input" : { - "outPoint" : "1d8d1bf9e04f9a0ac27261cbee89a195ceda835b740f15767049e175cb5e977d:0", - "amountSatoshis" : 1000000 - }, "remoteSig" : { "sig" : "02b8ce0a760ae6052a0d6c5770ae626ae71ea1b64853f97bb2c784839264054c64dc8717789f804342cbd9c5a364b32f4cbd9e9a4f91f48127a58f95014f6663" }, "htlcRemoteSigs" : [ ] }, + "remoteCommitParams" : { + "dustLimit" : 1100, + "htlcMinimum" : 0, + "maxHtlcValueInFlight" : 500000000, + "maxAcceptedHtlcs" : 100, + "toSelfDelay" : 144 + }, "remoteCommit" : { "index" : 0, "spec" : { diff --git a/eclair-core/src/test/resources/nonreg/codecs/04000b-DATA_WAIT_FOR_CHANNEL_READY/funder/data.json b/eclair-core/src/test/resources/nonreg/codecs/04000b-DATA_WAIT_FOR_CHANNEL_READY/funder/data.json index fc48de0975..8cf7bd729f 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/04000b-DATA_WAIT_FOR_CHANNEL_READY/funder/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/04000b-DATA_WAIT_FOR_CHANNEL_READY/funder/data.json @@ -8,12 +8,7 @@ "localParams" : { "nodeId" : "02aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa", "fundingKeyPath" : [ 222163200, 3836794389, 3217943953, 1565449417, 2862146275, 3633046910, 2547274215, 4050695153, 2147483649 ], - "dustLimit" : 1100, - "maxHtlcValueInFlightMsat" : 500000000, "initialRequestedChannelReserve_opt" : 10000, - "htlcMinimum" : 0, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 100, "isChannelOpener" : true, "paysCommitTxFees" : true, "walletStaticPaymentBasepoint" : "028feba10d0eafd0fad8fe20e6d9206e6bd30242826de05c63f459a00aced24b12", @@ -37,12 +32,7 @@ }, "remoteParams" : { "nodeId" : "02bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e63", - "dustLimit" : 1000, - "maxHtlcValueInFlightMsat" : 1000000000, "initialRequestedChannelReserve_opt" : 20000, - "htlcMinimum" : 1000, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 30, "revocationBasepoint" : "0201dd773d2108f3ed8f59669240c7f098d3083963bef5b36ebf940f4bc40724c8", "paymentBasepoint" : "028feba10d0eafd0fad8fe20e6d9206e6bd30242826de05c63f459a00aced24b12", "delayedPaymentBasepoint" : "0243f77d02fbb07f25d6fe14f9c7ee904e1a19442a4aea0dc1ce959d5d2caea9b4", @@ -86,18 +76,23 @@ }, "active" : [ { "fundingTxIndex" : 0, - "fundingTx" : { - "outPoint" : "1d8d1bf9e04f9a0ac27261cbee89a195ceda835b740f15767049e175cb5e977d:0", - "amountSatoshis" : 1000000 - }, + "fundingInput" : "1d8d1bf9e04f9a0ac27261cbee89a195ceda835b740f15767049e175cb5e977d:0", + "fundingAmount" : 1000000, "localFunding" : { "status" : "confirmed", - "txid" : "1d8d1bf9e04f9a0ac27261cbee89a195ceda835b740f15767049e175cb5e977d", "shortChannelId" : "400000x42x0" }, "remoteFunding" : { "status" : "not-locked" }, + "commitmentFormat" : "legacy", + "localCommitParams" : { + "dustLimit" : 1100, + "htlcMinimum" : 0, + "maxHtlcValueInFlight" : 500000000, + "maxAcceptedHtlcs" : 100, + "toSelfDelay" : 144 + }, "localCommit" : { "index" : 0, "spec" : { @@ -107,15 +102,18 @@ "toRemote" : 200000000 }, "txId" : "ad28d946d13d331539d08da9d131311d1bec7308ddf072a59b07b2dc22779c3f", - "input" : { - "outPoint" : "1d8d1bf9e04f9a0ac27261cbee89a195ceda835b740f15767049e175cb5e977d:0", - "amountSatoshis" : 1000000 - }, "remoteSig" : { "sig" : "8d3c57514dd1c01073b3d3704eddcfd8ca6ac3bb58f145f2c2dd921b77f238e842dce706a711b69b250e9ec3b827a9d9e144720a41fadce820bb18280667bffd" }, "htlcRemoteSigs" : [ ] }, + "remoteCommitParams" : { + "dustLimit" : 1000, + "htlcMinimum" : 1000, + "maxHtlcValueInFlight" : 1000000000, + "maxAcceptedHtlcs" : 30, + "toSelfDelay" : 144 + }, "remoteCommit" : { "index" : 0, "spec" : { diff --git a/eclair-core/src/test/resources/nonreg/codecs/04000d-DATA_WAIT_FOR_DUAL_FUNDING_READY/fundee/data.json b/eclair-core/src/test/resources/nonreg/codecs/04000d-DATA_WAIT_FOR_DUAL_FUNDING_READY/fundee/data.json index e5426ace13..b9a4775d1a 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/04000d-DATA_WAIT_FOR_DUAL_FUNDING_READY/fundee/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/04000d-DATA_WAIT_FOR_DUAL_FUNDING_READY/fundee/data.json @@ -8,11 +8,6 @@ "localParams" : { "nodeId" : "02bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e63", "fundingKeyPath" : [ 2566431519, 2591595317, 1320214673, 2312083324, 3942626068, 2510065287, 1961707336, 236707474, 2147483648 ], - "dustLimit" : 1000, - "maxHtlcValueInFlightMsat" : 1000000000, - "htlcMinimum" : 1000, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 30, "isChannelOpener" : false, "paysCommitTxFees" : false, "initFeatures" : { @@ -37,11 +32,6 @@ }, "remoteParams" : { "nodeId" : "02aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa", - "dustLimit" : 1100, - "maxHtlcValueInFlightMsat" : 500000000, - "htlcMinimum" : 0, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 100, "revocationBasepoint" : "038039cb4b63c81052cf0893e27f7be58a5808453dffd44c662b1a75aebb1fc74e", "paymentBasepoint" : "0394e61db71e79c20f2ef131e99121996c7ae04330c4c50dae6bccc5afcf493661", "delayedPaymentBasepoint" : "029a15b644f2800a507565666956446c05ef8dd7b60023852607c97471d233d62e", @@ -88,18 +78,23 @@ }, "active" : [ { "fundingTxIndex" : 0, - "fundingTx" : { - "outPoint" : "e0052c9a6cebcf139248732fcc7c563b022c2ae2d665a670f09389e4ff514393:0", - "amountSatoshis" : 1500000 - }, + "fundingInput" : "e0052c9a6cebcf139248732fcc7c563b022c2ae2d665a670f09389e4ff514393:0", + "fundingAmount" : 1500000, "localFunding" : { "status" : "confirmed", - "txid" : "e0052c9a6cebcf139248732fcc7c563b022c2ae2d665a670f09389e4ff514393", "shortChannelId" : "400000x42x0" }, "remoteFunding" : { "status" : "not-locked" }, + "commitmentFormat" : "anchor_outputs", + "localCommitParams" : { + "dustLimit" : 1000, + "htlcMinimum" : 1000, + "maxHtlcValueInFlight" : 1000000000, + "maxAcceptedHtlcs" : 30, + "toSelfDelay" : 144 + }, "localCommit" : { "index" : 0, "spec" : { @@ -109,15 +104,18 @@ "toRemote" : 1000000000 }, "txId" : "800a5eab3f585d54f19d6e574b611d94fa7414af651e17104310bf004ccd5cd6", - "input" : { - "outPoint" : "e0052c9a6cebcf139248732fcc7c563b022c2ae2d665a670f09389e4ff514393:0", - "amountSatoshis" : 1500000 - }, "remoteSig" : { "sig" : "c70d3fc3e720cc4fd1a2e07b97d454392ecfbe27c0ef2eed401538466924f6c24accb834eed784536fe5d30656eb8308a8b0f48bdba2ccc9812ce49a12b9dd80" }, "htlcRemoteSigs" : [ ] }, + "remoteCommitParams" : { + "dustLimit" : 1100, + "htlcMinimum" : 0, + "maxHtlcValueInFlight" : 500000000, + "maxAcceptedHtlcs" : 100, + "toSelfDelay" : 144 + }, "remoteCommit" : { "index" : 0, "spec" : { diff --git a/eclair-core/src/test/resources/nonreg/codecs/04000d-DATA_WAIT_FOR_DUAL_FUNDING_READY/funder/data.json b/eclair-core/src/test/resources/nonreg/codecs/04000d-DATA_WAIT_FOR_DUAL_FUNDING_READY/funder/data.json index fd8d104943..cd187a325c 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/04000d-DATA_WAIT_FOR_DUAL_FUNDING_READY/funder/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/04000d-DATA_WAIT_FOR_DUAL_FUNDING_READY/funder/data.json @@ -8,11 +8,6 @@ "localParams" : { "nodeId" : "02aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa", "fundingKeyPath" : [ 461130578, 2441458723, 3315200959, 1624559903, 421875145, 1108846792, 2605590614, 1621284536, 2147483649 ], - "dustLimit" : 1100, - "maxHtlcValueInFlightMsat" : 500000000, - "htlcMinimum" : 0, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 100, "isChannelOpener" : true, "paysCommitTxFees" : true, "initFeatures" : { @@ -38,11 +33,6 @@ }, "remoteParams" : { "nodeId" : "02bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e63", - "dustLimit" : 1000, - "maxHtlcValueInFlightMsat" : 1000000000, - "htlcMinimum" : 1000, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 30, "revocationBasepoint" : "031a9b266c6a1f5d05f6f2b040b0eea59819c2fb5715945628541c58c2605e5c42", "paymentBasepoint" : "02cb3e21fe0825663cfa1d8a8accc93dedbdb906b0ec4b779bb41b0f030b38fd33", "delayedPaymentBasepoint" : "0370c28e0286bfcf6bfb18c2b98d5b265594ec363822cbb323e7f7451ca60b0330", @@ -88,18 +78,23 @@ }, "active" : [ { "fundingTxIndex" : 0, - "fundingTx" : { - "outPoint" : "e0052c9a6cebcf139248732fcc7c563b022c2ae2d665a670f09389e4ff514393:0", - "amountSatoshis" : 1500000 - }, + "fundingInput" : "e0052c9a6cebcf139248732fcc7c563b022c2ae2d665a670f09389e4ff514393:0", + "fundingAmount" : 1500000, "localFunding" : { "status" : "confirmed", - "txid" : "e0052c9a6cebcf139248732fcc7c563b022c2ae2d665a670f09389e4ff514393", "shortChannelId" : "400000x42x0" }, "remoteFunding" : { "status" : "not-locked" }, + "commitmentFormat" : "anchor_outputs", + "localCommitParams" : { + "dustLimit" : 1100, + "htlcMinimum" : 0, + "maxHtlcValueInFlight" : 500000000, + "maxAcceptedHtlcs" : 100, + "toSelfDelay" : 144 + }, "localCommit" : { "index" : 0, "spec" : { @@ -109,15 +104,18 @@ "toRemote" : 500000000 }, "txId" : "c98fac755c41c1688a55f820a3ac9c4c8b2adde1d6687b2a9d843b364211965f", - "input" : { - "outPoint" : "e0052c9a6cebcf139248732fcc7c563b022c2ae2d665a670f09389e4ff514393:0", - "amountSatoshis" : 1500000 - }, "remoteSig" : { "sig" : "353c51755a0c852a4c5dd9e2d33d4a9c0934e9a07a43da89f061a3b4499f374d1857dfa513ed714287baf4ac3f4375f06bf435577962f121182ad9ce4b19f549" }, "htlcRemoteSigs" : [ ] }, + "remoteCommitParams" : { + "dustLimit" : 1000, + "htlcMinimum" : 1000, + "maxHtlcValueInFlight" : 1000000000, + "maxAcceptedHtlcs" : 30, + "toSelfDelay" : 144 + }, "remoteCommit" : { "index" : 0, "spec" : { diff --git a/eclair-core/src/test/resources/nonreg/codecs/04000e-DATA_NORMAL/announced/data.json b/eclair-core/src/test/resources/nonreg/codecs/04000e-DATA_NORMAL/announced/data.json index 1ed6f0b14f..6bdb19e9ba 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/04000e-DATA_NORMAL/announced/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/04000e-DATA_NORMAL/announced/data.json @@ -8,12 +8,7 @@ "localParams" : { "nodeId" : "02aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa", "fundingKeyPath" : [ 1057736322, 487845715, 3068040012, 2828312867, 3790939166, 2388092911, 4276180524, 217832603, 2147483649 ], - "dustLimit" : 1100, - "maxHtlcValueInFlightMsat" : 500000000, "initialRequestedChannelReserve_opt" : 10000, - "htlcMinimum" : 0, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 100, "isChannelOpener" : true, "paysCommitTxFees" : true, "walletStaticPaymentBasepoint" : "028feba10d0eafd0fad8fe20e6d9206e6bd30242826de05c63f459a00aced24b12", @@ -37,12 +32,7 @@ }, "remoteParams" : { "nodeId" : "02bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e63", - "dustLimit" : 1000, - "maxHtlcValueInFlightMsat" : 1000000000, "initialRequestedChannelReserve_opt" : 20000, - "htlcMinimum" : 1000, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 30, "revocationBasepoint" : "02f943c4f199d1425fc6e52f160be536d526e9643af1430cfed4ce63f88beebd2f", "paymentBasepoint" : "028feba10d0eafd0fad8fe20e6d9206e6bd30242826de05c63f459a00aced24b12", "delayedPaymentBasepoint" : "03f3b6c8919d313d5bd14a2e567649ea80ed1dd299ebc72766775a1f4fdc3cc1b5", @@ -86,18 +76,23 @@ }, "active" : [ { "fundingTxIndex" : 0, - "fundingTx" : { - "outPoint" : "38acfe71db109d8cfaf075497e39d9dafaec8abed0df97d7a6b00d7011aa80c3:0", - "amountSatoshis" : 1000000 - }, + "fundingInput" : "38acfe71db109d8cfaf075497e39d9dafaec8abed0df97d7a6b00d7011aa80c3:0", + "fundingAmount" : 1000000, "localFunding" : { "status" : "confirmed", - "txid" : "38acfe71db109d8cfaf075497e39d9dafaec8abed0df97d7a6b00d7011aa80c3", "shortChannelId" : "400000x42x0" }, "remoteFunding" : { "status" : "not-locked" }, + "commitmentFormat" : "legacy", + "localCommitParams" : { + "dustLimit" : 1100, + "htlcMinimum" : 0, + "maxHtlcValueInFlight" : 500000000, + "maxAcceptedHtlcs" : 100, + "toSelfDelay" : 144 + }, "localCommit" : { "index" : 0, "spec" : { @@ -107,15 +102,18 @@ "toRemote" : 200000000 }, "txId" : "41418383164935b0753fa1f9b128dff2f1b3608e6b50e8beea4525edf7c3959d", - "input" : { - "outPoint" : "38acfe71db109d8cfaf075497e39d9dafaec8abed0df97d7a6b00d7011aa80c3:0", - "amountSatoshis" : 1000000 - }, "remoteSig" : { "sig" : "62de565629126afb02a76e67ddf18dfc883cf93bfeb7c807826dd6c8d1bd1f4d55528717f0d2303dc254b4512e32ce82dc7947d5d8e4d38647c2407fc53b874a" }, "htlcRemoteSigs" : [ ] }, + "remoteCommitParams" : { + "dustLimit" : 1000, + "htlcMinimum" : 1000, + "maxHtlcValueInFlight" : 1000000000, + "maxAcceptedHtlcs" : 30, + "toSelfDelay" : 144 + }, "remoteCommit" : { "index" : 0, "spec" : { diff --git a/eclair-core/src/test/resources/nonreg/codecs/04000e-DATA_NORMAL/splicing-private/data.json b/eclair-core/src/test/resources/nonreg/codecs/04000e-DATA_NORMAL/splicing-private/data.json index 1000fae8d6..2c0f3a88f7 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/04000e-DATA_NORMAL/splicing-private/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/04000e-DATA_NORMAL/splicing-private/data.json @@ -8,11 +8,6 @@ "localParams" : { "nodeId" : "02bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e63", "fundingKeyPath" : [ 2707154742, 1913609705, 398200054, 2009144985, 2117492679, 302300795, 2676284185, 1690699462, 2147483648 ], - "dustLimit" : 1000, - "maxHtlcValueInFlightMsat" : 9223372036854775807, - "htlcMinimum" : 1000, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 30, "isChannelOpener" : false, "paysCommitTxFees" : false, "walletStaticPaymentBasepoint" : "038bff1253b7b8e40532508a53d31b95b295df31edf0d067d734479a680bd395de", @@ -37,11 +32,6 @@ }, "remoteParams" : { "nodeId" : "02aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa", - "dustLimit" : 1100, - "maxHtlcValueInFlightMsat" : 9223372036854775807, - "htlcMinimum" : 0, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 100, "revocationBasepoint" : "02fab0332aecd8006f4937b220ea073b0e814a21e36f4d21de9bb3e5165c3ffa02", "paymentBasepoint" : "038bff1253b7b8e40532508a53d31b95b295df31edf0d067d734479a680bd395de", "delayedPaymentBasepoint" : "023a5359a4e12c66e5b6a1847161f837695ab6e7591e67cb5b1da82706b491ea12", @@ -86,10 +76,8 @@ }, "active" : [ { "fundingTxIndex" : 2, - "fundingTx" : { - "outPoint" : "8883382b78ace90653166f985e331d0e0a759a5215cd0598ab5d1f72330d3101:0", - "amountSatoshis" : 2500000 - }, + "fundingInput" : "8883382b78ace90653166f985e331d0e0a759a5215cd0598ab5d1f72330d3101:0", + "fundingAmount" : 2500000, "localFunding" : { "status" : "unconfirmed", "txid" : "8883382b78ace90653166f985e331d0e0a759a5215cd0598ab5d1f72330d3101" @@ -97,6 +85,14 @@ "remoteFunding" : { "status" : "not-locked" }, + "commitmentFormat" : "legacy", + "localCommitParams" : { + "dustLimit" : 1000, + "htlcMinimum" : 1000, + "maxHtlcValueInFlight" : 9223372036854775807, + "maxAcceptedHtlcs" : 30, + "toSelfDelay" : 144 + }, "localCommit" : { "index" : 0, "spec" : { @@ -106,15 +102,18 @@ "toRemote" : 1800000000 }, "txId" : "ec1fdbdffd12f20851f33319fb3ab6ecb8528a34a9a2c76e53aaf91cf4a18da4", - "input" : { - "outPoint" : "8883382b78ace90653166f985e331d0e0a759a5215cd0598ab5d1f72330d3101:0", - "amountSatoshis" : 2500000 - }, "remoteSig" : { "sig" : "52ea4ba9421f4b4ad8a9a1e3041a940b13751504faf7d79338d73bbc1187fb551d9340de809849d6d7dcd745b813e01c07809cb8eacc57a211990b0c021d4c45" }, "htlcRemoteSigs" : [ ] }, + "remoteCommitParams" : { + "dustLimit" : 1100, + "htlcMinimum" : 0, + "maxHtlcValueInFlight" : 9223372036854775807, + "maxAcceptedHtlcs" : 100, + "toSelfDelay" : 144 + }, "remoteCommit" : { "index" : 0, "spec" : { @@ -128,18 +127,23 @@ } }, { "fundingTxIndex" : 1, - "fundingTx" : { - "outPoint" : "91556cf5881038eb239cac05123f0be1daface928690e5f33ae1cc653f3d3005:0", - "amountSatoshis" : 2000000 - }, + "fundingInput" : "91556cf5881038eb239cac05123f0be1daface928690e5f33ae1cc653f3d3005:0", + "fundingAmount" : 2000000, "localFunding" : { "status" : "confirmed", - "txid" : "91556cf5881038eb239cac05123f0be1daface928690e5f33ae1cc653f3d3005", "shortChannelId" : "0x0x0" }, "remoteFunding" : { "status" : "not-locked" }, + "commitmentFormat" : "legacy", + "localCommitParams" : { + "dustLimit" : 1000, + "htlcMinimum" : 1000, + "maxHtlcValueInFlight" : 9223372036854775807, + "maxAcceptedHtlcs" : 30, + "toSelfDelay" : 144 + }, "localCommit" : { "index" : 0, "spec" : { @@ -149,15 +153,18 @@ "toRemote" : 1300000000 }, "txId" : "27f7218158a68f5bb23c9d8a280f85bfca32d7636529e1db2ba23172241161f4", - "input" : { - "outPoint" : "91556cf5881038eb239cac05123f0be1daface928690e5f33ae1cc653f3d3005:0", - "amountSatoshis" : 2000000 - }, "remoteSig" : { "sig" : "32a246840dd67eb72b59fc170303c508c77ee8666ac6be36abced02bf79dbfbe114cdd4e56600ae6e3573e6e10d3517ddc857fbd89c00e2cbcdf5186c6592f26" }, "htlcRemoteSigs" : [ ] }, + "remoteCommitParams" : { + "dustLimit" : 1100, + "htlcMinimum" : 0, + "maxHtlcValueInFlight" : 9223372036854775807, + "maxAcceptedHtlcs" : 100, + "toSelfDelay" : 144 + }, "remoteCommit" : { "index" : 0, "spec" : { @@ -171,18 +178,23 @@ } }, { "fundingTxIndex" : 0, - "fundingTx" : { - "outPoint" : "421298cf7c9a9d12d35fd68231986b43f6fe90e4b499fff4bc3e36d52d2d215f:2", - "amountSatoshis" : 1500000 - }, + "fundingInput" : "421298cf7c9a9d12d35fd68231986b43f6fe90e4b499fff4bc3e36d52d2d215f:2", + "fundingAmount" : 1500000, "localFunding" : { "status" : "confirmed", - "txid" : "421298cf7c9a9d12d35fd68231986b43f6fe90e4b499fff4bc3e36d52d2d215f", "shortChannelId" : "400000x42x2" }, "remoteFunding" : { "status" : "not-locked" }, + "commitmentFormat" : "legacy", + "localCommitParams" : { + "dustLimit" : 1000, + "htlcMinimum" : 1000, + "maxHtlcValueInFlight" : 9223372036854775807, + "maxAcceptedHtlcs" : 30, + "toSelfDelay" : 144 + }, "localCommit" : { "index" : 0, "spec" : { @@ -192,15 +204,18 @@ "toRemote" : 800000000 }, "txId" : "381ee5a54f127b776114b197b2695c39f2d9b81cb967628a32e12766cdf8f1b5", - "input" : { - "outPoint" : "421298cf7c9a9d12d35fd68231986b43f6fe90e4b499fff4bc3e36d52d2d215f:2", - "amountSatoshis" : 1500000 - }, "remoteSig" : { "sig" : "47f525be7304ef4fa4d868eccfa2389fe1d8f058d08305b0f1792cebbd14ec7b6b43f98d0f3aa22e60d7ef024785f3576d0c1d74b666c7c5d2da88c7e7f7bd18" }, "htlcRemoteSigs" : [ ] }, + "remoteCommitParams" : { + "dustLimit" : 1100, + "htlcMinimum" : 0, + "maxHtlcValueInFlight" : 9223372036854775807, + "maxAcceptedHtlcs" : 100, + "toSelfDelay" : 144 + }, "remoteCommit" : { "index" : 0, "spec" : { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala index f01d37998b..3863478e16 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala @@ -268,8 +268,7 @@ object TestConstants { isChannelOpener = true, paysCommitTxFees = true, dualFunded = false, - fundingSatoshis, - unlimitedMaxHtlcValueInFlight = false, + fundingSatoshis ).copy( fundingKeyPath = fundingKeyPath, initialRequestedChannelReserve_opt = Some(10_000 sat) // Bob will need to keep that much satoshis in his balance @@ -460,8 +459,7 @@ object TestConstants { isChannelOpener = false, paysCommitTxFees = false, dualFunded = false, - fundingSatoshis, - unlimitedMaxHtlcValueInFlight = false, + fundingSatoshis ).copy( fundingKeyPath = fundingKeyPath, initialRequestedChannelReserve_opt = Some(20_000 sat) // Alice will need to keep that much satoshis in her balance diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/balance/CheckBalanceSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/balance/CheckBalanceSpec.scala index c0c6fc5dcf..ce15232ac6 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/balance/CheckBalanceSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/balance/CheckBalanceSpec.scala @@ -92,7 +92,7 @@ class CheckBalanceSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with import f._ mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain) - val closingTxInput = alice.stateData.asInstanceOf[DATA_NEGOTIATING_SIMPLE].commitments.latest.commitInput.outPoint + val closingTxInput = alice.stateData.asInstanceOf[DATA_NEGOTIATING_SIMPLE].commitments.latest.fundingInput val expected = MainAndHtlcBalance(toLocal = 0 sat, htlcs = 0 sat) assert(CheckBalance.computeOffChainBalance(Seq(alice.stateData.asInstanceOf[DATA_NEGOTIATING_SIMPLE]), recentlySpentInputs = Set(closingTxInput)).negotiating == expected) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/CommitmentsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/CommitmentsSpec.scala index dcd287922d..0f56624625 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/CommitmentsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/CommitmentsSpec.scala @@ -17,12 +17,11 @@ package fr.acinq.eclair.channel import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey -import fr.acinq.bitcoin.scalacompat.{ByteVector64, DeterministicWallet, OutPoint, Satoshi, SatoshiLong, Transaction, TxOut} +import fr.acinq.bitcoin.scalacompat.{ByteVector64, DeterministicWallet, OutPoint, Satoshi, SatoshiLong, TxOut} import fr.acinq.eclair.TestUtils.randomTxId import fr.acinq.eclair._ import fr.acinq.eclair.blockchain.fee._ import fr.acinq.eclair.channel.ChannelSpendSignature.IndividualSignature -import fr.acinq.eclair.channel.Helpers.Funding import fr.acinq.eclair.channel.states.ChannelStateTestsBase import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.reputation.Reputation @@ -31,7 +30,6 @@ import fr.acinq.eclair.transactions.{CommitmentSpec, Transactions} import fr.acinq.eclair.wire.protocol._ import org.scalatest.funsuite.FixtureAnyFunSuiteLike import org.scalatest.{Outcome, Tag} -import scodec.bits.ByteVector import scala.concurrent.duration._ import scala.util.Random @@ -492,22 +490,22 @@ object CommitmentsSpec { def makeCommitments(toLocal: MilliSatoshi, toRemote: MilliSatoshi, feeRatePerKw: FeeratePerKw = FeeratePerKw(0 sat), dustLimit: Satoshi = 0 sat, isOpener: Boolean = true, announcement_opt: Option[ChannelAnnouncement] = None): Commitments = { val channelReserve = (toLocal + toRemote).truncateToSatoshi * 0.01 - val localChannelParams = LocalChannelParams(randomKey().publicKey, DeterministicWallet.KeyPath(Seq(42L)), dustLimit, UInt64.MaxValue, Some(channelReserve), 1 msat, CltvExpiryDelta(144), 50, isOpener, isOpener, None, None, Features.empty) - val remoteChannelParams = RemoteChannelParams(randomKey().publicKey, dustLimit, UInt64.MaxValue, Some(channelReserve), 1 msat, CltvExpiryDelta(144), 50, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, Features.empty, None) + val localChannelParams = LocalChannelParams(randomKey().publicKey, DeterministicWallet.KeyPath(Seq(42L)), Some(channelReserve), isOpener, isOpener, None, None, Features.empty) + val remoteChannelParams = RemoteChannelParams(randomKey().publicKey, Some(channelReserve), randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, Features.empty, None) + val commitParams = CommitParams(dustLimit, 1 msat, UInt64.MaxValue, 50, CltvExpiryDelta(144)) val localFundingPubKey = randomKey().publicKey val remoteFundingPubKey = randomKey().publicKey - val fundingTx = Transaction(2, Nil, Seq(TxOut((toLocal + toRemote).truncateToSatoshi, Funding.makeFundingScript(localFundingPubKey, remoteFundingPubKey, DefaultCommitmentFormat).pubkeyScript)), 0) - val commitmentInput = Transactions.InputInfo(OutPoint(fundingTx, 0), fundingTx.txOut.head, ByteVector.empty) - val localCommit = LocalCommit(0, CommitmentSpec(Set.empty, feeRatePerKw, toLocal, toRemote), randomTxId(), commitmentInput, IndividualSignature(ByteVector64.Zeroes), Nil) + val fundingTxOut = TxOut((toLocal + toRemote).truncateToSatoshi, Transactions.makeFundingScript(localFundingPubKey, remoteFundingPubKey, DefaultCommitmentFormat).pubkeyScript) + val localCommit = LocalCommit(0, CommitmentSpec(Set.empty, feeRatePerKw, toLocal, toRemote), randomTxId(), IndividualSignature(ByteVector64.Zeroes), Nil) val remoteCommit = RemoteCommit(0, CommitmentSpec(Set.empty, feeRatePerKw, toRemote, toLocal), randomTxId(), randomKey().publicKey) val localFundingStatus = announcement_opt match { - case Some(ann) => LocalFundingStatus.ConfirmedFundingTx(fundingTx, ann.shortChannelId, None, None) + case Some(ann) => LocalFundingStatus.ConfirmedFundingTx(fundingTxOut, ann.shortChannelId, None, None) case None => LocalFundingStatus.SingleFundedUnconfirmedFundingTx(None) } Commitments( ChannelParams(randomBytes32(), ChannelConfig.standard, ChannelFeatures(), localChannelParams, remoteChannelParams, ChannelFlags(announceChannel = announcement_opt.nonEmpty)), CommitmentChanges(LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), localNextHtlcId = 1, remoteNextHtlcId = 1), - List(Commitment(0, 0, remoteFundingPubKey, localFundingStatus, RemoteFundingStatus.Locked, localCommit, remoteCommit, None)), + List(Commitment(0, 0, OutPoint(randomTxId(), 0), fundingTxOut.amount, remoteFundingPubKey, localFundingStatus, RemoteFundingStatus.Locked, DefaultCommitmentFormat, commitParams, localCommit, commitParams, remoteCommit, None)), inactive = Nil, Right(randomKey().publicKey), ShaChain.init, @@ -517,22 +515,22 @@ object CommitmentsSpec { def makeCommitments(toLocal: MilliSatoshi, toRemote: MilliSatoshi, localNodeId: PublicKey, remoteNodeId: PublicKey, announcement_opt: Option[ChannelAnnouncement]): Commitments = { val channelReserve = (toLocal + toRemote).truncateToSatoshi * 0.01 - val localChannelParams = LocalChannelParams(localNodeId, DeterministicWallet.KeyPath(Seq(42L)), 0 sat, UInt64.MaxValue, Some(channelReserve), 1 msat, CltvExpiryDelta(144), 50, isChannelOpener = true, paysCommitTxFees = true, None, None, Features.empty) - val remoteChannelParams = RemoteChannelParams(remoteNodeId, 0 sat, UInt64.MaxValue, Some(channelReserve), 1 msat, CltvExpiryDelta(144), 50, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, Features.empty, None) + val localChannelParams = LocalChannelParams(localNodeId, DeterministicWallet.KeyPath(Seq(42L)), Some(channelReserve), isChannelOpener = true, paysCommitTxFees = true, None, None, Features.empty) + val remoteChannelParams = RemoteChannelParams(remoteNodeId, Some(channelReserve), randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, Features.empty, None) + val commitParams = CommitParams(0 sat, 1 msat, UInt64.MaxValue, 50, CltvExpiryDelta(144)) val localFundingPubKey = randomKey().publicKey val remoteFundingPubKey = randomKey().publicKey - val fundingTx = Transaction(2, Nil, Seq(TxOut((toLocal + toRemote).truncateToSatoshi, Funding.makeFundingScript(localFundingPubKey, remoteFundingPubKey, DefaultCommitmentFormat).pubkeyScript)), 0) - val commitmentInput = Transactions.InputInfo(OutPoint(fundingTx, 0), fundingTx.txOut.head, ByteVector.empty) - val localCommit = LocalCommit(0, CommitmentSpec(Set.empty, FeeratePerKw(0 sat), toLocal, toRemote), randomTxId(), commitmentInput, IndividualSignature(ByteVector64.Zeroes), Nil) + val fundingTxOut = TxOut((toLocal + toRemote).truncateToSatoshi, Transactions.makeFundingScript(localFundingPubKey, remoteFundingPubKey, DefaultCommitmentFormat).pubkeyScript) + val localCommit = LocalCommit(0, CommitmentSpec(Set.empty, FeeratePerKw(0 sat), toLocal, toRemote), randomTxId(), IndividualSignature(ByteVector64.Zeroes), Nil) val remoteCommit = RemoteCommit(0, CommitmentSpec(Set.empty, FeeratePerKw(0 sat), toRemote, toLocal), randomTxId(), randomKey().publicKey) val localFundingStatus = announcement_opt match { - case Some(ann) => LocalFundingStatus.ConfirmedFundingTx(fundingTx, ann.shortChannelId, None, None) + case Some(ann) => LocalFundingStatus.ConfirmedFundingTx(fundingTxOut, ann.shortChannelId, None, None) case None => LocalFundingStatus.SingleFundedUnconfirmedFundingTx(None) } Commitments( ChannelParams(randomBytes32(), ChannelConfig.standard, ChannelFeatures(), localChannelParams, remoteChannelParams, ChannelFlags(announceChannel = announcement_opt.nonEmpty)), CommitmentChanges(LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), localNextHtlcId = 1, remoteNextHtlcId = 1), - List(Commitment(0, 0, remoteFundingPubKey, localFundingStatus, RemoteFundingStatus.Locked, localCommit, remoteCommit, None)), + List(Commitment(0, 0, OutPoint(randomTxId(), 0), fundingTxOut.amount, remoteFundingPubKey, localFundingStatus, RemoteFundingStatus.Locked, DefaultCommitmentFormat, commitParams, localCommit, commitParams, remoteCommit, None)), inactive = Nil, Right(randomKey().publicKey), ShaChain.init, diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/FuzzySpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/FuzzySpec.scala index 8a1144abec..f9eeeb2e8f 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/FuzzySpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/FuzzySpec.scala @@ -19,7 +19,7 @@ package fr.acinq.eclair.channel import akka.actor.typed.scaladsl.adapter.actorRefAdapter import akka.actor.{Actor, ActorLogging, ActorRef, Props} import akka.testkit.{TestFSMRef, TestProbe} -import fr.acinq.bitcoin.scalacompat.ByteVector32 +import fr.acinq.bitcoin.scalacompat.{ByteVector32, SatoshiLong} import fr.acinq.eclair.TestConstants.{Alice, Bob} import fr.acinq.eclair._ import fr.acinq.eclair.blockchain.DummyOnChainWallet @@ -58,8 +58,7 @@ class FuzzySpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Channe val bobParams = Bob.nodeParams val aliceChannelParams = Alice.channelParams val bobChannelParams = Bob.channelParams - val aliceCommitParams = aliceChannelParams.proposedCommitParams - val bobCommitParams = bobChannelParams.proposedCommitParams + val commitParams = ProposedCommitParams(1000 sat, 1 msat, UInt64.MaxValue, 30, CltvExpiryDelta(720)) val channelFlags = ChannelFlags(announceChannel = false) val alicePeer = TestProbe() val bobPeer = TestProbe() @@ -82,9 +81,9 @@ class FuzzySpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Channe aliceRegister ! alice bobRegister ! bob // no announcements - alice ! INPUT_INIT_CHANNEL_INITIATOR(ByteVector32.Zeroes, TestConstants.fundingSatoshis, dualFunded = false, TestConstants.feeratePerKw, TestConstants.feeratePerKw, fundingTxFeeBudget_opt = None, Some(TestConstants.initiatorPushAmount), requireConfirmedInputs = false, requestFunding_opt = None, aliceChannelParams, aliceCommitParams, pipe, bobInit, channelFlags, ChannelConfig.standard, ChannelTypes.Standard(), replyTo = system.deadLetters) + alice ! INPUT_INIT_CHANNEL_INITIATOR(ByteVector32.Zeroes, TestConstants.fundingSatoshis, dualFunded = false, TestConstants.feeratePerKw, TestConstants.feeratePerKw, fundingTxFeeBudget_opt = None, Some(TestConstants.initiatorPushAmount), requireConfirmedInputs = false, requestFunding_opt = None, aliceChannelParams, commitParams, pipe, bobInit, channelFlags, ChannelConfig.standard, ChannelTypes.Standard(), replyTo = system.deadLetters) alice2blockchain.expectMsgType[TxPublisher.SetChannelId] - bob ! INPUT_INIT_CHANNEL_NON_INITIATOR(ByteVector32.Zeroes, None, dualFunded = false, None, requireConfirmedInputs = false, bobChannelParams, bobCommitParams, pipe, aliceInit, ChannelConfig.standard, ChannelTypes.Standard()) + bob ! INPUT_INIT_CHANNEL_NON_INITIATOR(ByteVector32.Zeroes, None, dualFunded = false, None, requireConfirmedInputs = false, bobChannelParams, commitParams, pipe, aliceInit, ChannelConfig.standard, ChannelTypes.Standard()) bob2blockchain.expectMsgType[TxPublisher.SetChannelId] pipe ! (alice, bob) alice2blockchain.expectMsgType[TxPublisher.SetChannelId] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala index 79201b3a58..94667ba947 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala @@ -198,7 +198,7 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat ) def toClosingTx(txOut: Seq[TxOut]): ClosingTx = { - ClosingTx(InputInfo(OutPoint(TxId(ByteVector32.Zeroes), 0), TxOut(1000 sat, Nil), ByteVector.empty), Transaction(2, Nil, txOut, 0), None) + ClosingTx(InputInfo(OutPoint(TxId(ByteVector32.Zeroes), 0), TxOut(1000 sat, Nil)), Transaction(2, Nil, txOut, 0), None) } assert(Closing.MutualClose.checkClosingDustAmounts(toClosingTx(allOutputsAboveDust))) @@ -218,7 +218,7 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat Transaction.read("0200000001c8a8934fb38a44b969528252bc37be66ee166c7897c57384d1e561449e110c93010000006b483045022100dc6c50f445ed53d2fb41067fdcb25686fe79492d90e6e5db43235726ace247210220773d35228af0800c257970bee9cf75175d75217de09a8ecd83521befd040c4ca012102082b751372fe7e3b012534afe0bb8d1f2f09c724b1a10a813ce704e5b9c217ccfdffffff0247ba2300000000001976a914f97a7641228e6b17d4b0b08252ae75bd62a95fe788ace3de24000000000017a914a9fefd4b9a9282a1d7a17d2f14ac7d1eb88141d287f7d50800"), Transaction.read("010000000235a2f5c4fd48672534cce1ac063047edc38683f43c5a883f815d6026cb5f8321020000006a47304402206be5fd61b1702599acf51941560f0a1e1965aa086634b004967747f79788bd6e022002f7f719a45b8b5e89129c40a9d15e4a8ee1e33be3a891cf32e859823ecb7a510121024756c5adfbc0827478b0db042ce09d9b98e21ad80d036e73bd8e7f0ecbc254a2ffffffffb2387d3125bb8c84a2da83f4192385ce329283661dfc70191f4112c67ce7b4d0000000006b483045022100a2c737eab1c039f79238767ccb9bb3e81160e965ef0fc2ea79e8360c61b7c9f702202348b0f2c0ea2a757e25d375d9be183200ce0a79ec81d6a4ebb2ae4dc31bc3c9012102db16a822e2ec3706c58fc880c08a3617c61d8ef706cc8830cfe4561d9a5d52f0ffffffff01808d5b00000000001976a9141210c32def6b64d0d77ba8d99adeb7e9f91158b988ac00000000"), Transaction.read("0100000001b14ba6952c83f6f8c382befbf4e44270f13e479d5a5ff3862ac3a112f103ff2a010000006b4830450221008b097fd69bfa3715fc5e119a891933c091c55eabd3d1ddae63a1c2cc36dc9a3e02205666d5299fa403a393bcbbf4b05f9c0984480384796cdebcf69171674d00809c01210335b592484a59a44f40998d65a94f9e2eecca47e8d1799342112a59fc96252830ffffffff024bf308000000000017a914440668d018e5e0ba550d6e042abcf726694f515c8798dd1801000000001976a91453a503fe151dd32e0503bd9a2fbdbf4f9a3af1da88ac00000000") - ).map(tx => ClosingTx(InputInfo(tx.txIn.head.outPoint, TxOut(10_000 sat, Nil), ByteVector.empty), tx, None)) + ).map(tx => ClosingTx(InputInfo(tx.txIn.head.outPoint, TxOut(10_000 sat, Nil)), tx, None)) // only mutual close assert(Closing.isClosingTypeAlreadyKnown( diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala index ca2cd2c6f9..0e44c1d780 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala @@ -86,12 +86,6 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit case None => input.sharedInput_opt.get } - private def sharedInputs(commitmentA: Commitment, commitmentB: Commitment): (SharedFundingInput, SharedFundingInput) = { - val sharedInputA = Multisig2of2Input(commitmentA) - val sharedInputB = Multisig2of2Input(commitmentB) - (sharedInputA, sharedInputB) - } - case class FixtureParams(fundingParamsA: InteractiveTxParams, nodeParamsA: NodeParams, channelParamsA: ChannelParams, @@ -110,8 +104,14 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit private val firstPerCommitmentPointB = channelKeysB.commitmentPoint(0) val fundingPubkeyScript: ByteVector = Script.write(Script.pay2wsh(Scripts.multiSig2of2(fundingParamsB.remoteFundingPubKey, fundingParamsA.remoteFundingPubKey))) + def sharedInputs(commitmentA: Commitment, commitmentB: Commitment): (SharedFundingInput, SharedFundingInput) = { + val sharedInputA = Multisig2of2Input(channelKeysA, commitmentA) + val sharedInputB = Multisig2of2Input(channelKeysB, commitmentB) + (sharedInputA, sharedInputB) + } + def dummySharedInputB(amount: Satoshi): SharedFundingInput = { - val inputInfo = InputInfo(OutPoint(randomTxId(), 3), TxOut(amount, fundingPubkeyScript), ByteVector.empty) + val inputInfo = InputInfo(OutPoint(randomTxId(), 3), TxOut(amount, fundingPubkeyScript)) val fundingTxIndex = fundingParamsA.sharedInput_opt match { case Some(input: Multisig2of2Input) => input.fundingTxIndex + 1 case _ => 0 @@ -122,63 +122,63 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit def createSpliceFixtureParams(fundingTxIndex: Long, fundingAmountA: Satoshi, fundingAmountB: Satoshi, targetFeerate: FeeratePerKw, dustLimit: Satoshi, lockTime: Long, sharedInputA: SharedFundingInput, sharedInputB: SharedFundingInput, spliceOutputsA: List[TxOut] = Nil, spliceOutputsB: List[TxOut] = Nil, requireConfirmedInputs: RequireConfirmedInputs = RequireConfirmedInputs(forLocal = false, forRemote = false)): FixtureParams = { val fundingPubKeyA = channelKeysA.fundingKey(fundingTxIndex).publicKey val fundingPubKeyB = channelKeysB.fundingKey(fundingTxIndex).publicKey - val fundingParamsA = InteractiveTxParams(channelId, isInitiator = true, fundingAmountA, fundingAmountB, Some(sharedInputA), fundingPubKeyB, spliceOutputsA, lockTime, dustLimit, targetFeerate, requireConfirmedInputs) - val fundingParamsB = InteractiveTxParams(channelId, isInitiator = false, fundingAmountB, fundingAmountA, Some(sharedInputB), fundingPubKeyA, spliceOutputsB, lockTime, dustLimit, targetFeerate, requireConfirmedInputs) - copy(fundingParamsA = fundingParamsA, fundingParamsB = fundingParamsB) + val fundingParamsA1 = InteractiveTxParams(channelId, isInitiator = true, fundingAmountA, fundingAmountB, Some(sharedInputA), fundingPubKeyB, spliceOutputsA, fundingParamsA.commitmentFormat, lockTime, dustLimit, targetFeerate, requireConfirmedInputs) + val fundingParamsB1 = InteractiveTxParams(channelId, isInitiator = false, fundingAmountB, fundingAmountA, Some(sharedInputB), fundingPubKeyA, spliceOutputsB, fundingParamsB.commitmentFormat, lockTime, dustLimit, targetFeerate, requireConfirmedInputs) + copy(fundingParamsA = fundingParamsA1, fundingParamsB = fundingParamsB1) } def spawnTxBuilderAlice(wallet: OnChainWallet, fundingParams: InteractiveTxParams = fundingParamsA, liquidityPurchase_opt: Option[LiquidityAds.Purchase] = None): ActorRef[InteractiveTxBuilder.Command] = system.spawnAnonymous(InteractiveTxBuilder( ByteVector32.Zeroes, - nodeParamsA, fundingParams, channelParamsA, channelKeysA, + nodeParamsA, fundingParams, channelParamsA, commitParamsA, commitParamsB, channelKeysA, FundingTx(commitFeerate, firstPerCommitmentPointB, feeBudget_opt = None), 0 msat, 0 msat, liquidityPurchase_opt, wallet)) def spawnTxBuilderRbfAlice(fundingParams: InteractiveTxParams, commitment: Commitment, previousTransactions: Seq[InteractiveTxBuilder.SignedSharedTransaction], wallet: OnChainWallet): ActorRef[InteractiveTxBuilder.Command] = system.spawnAnonymous(InteractiveTxBuilder( ByteVector32.Zeroes, - nodeParamsA, fundingParams, channelParamsA, channelKeysA, + nodeParamsA, fundingParams, channelParamsA, commitParamsA, commitParamsB, channelKeysA, FundingTxRbf(commitment, previousTransactions, feeBudget_opt = None), 0 msat, 0 msat, None, wallet)) def spawnTxBuilderSpliceAlice(fundingParams: InteractiveTxParams, commitment: Commitment, wallet: OnChainWallet, liquidityPurchase_opt: Option[LiquidityAds.Purchase] = None): ActorRef[InteractiveTxBuilder.Command] = system.spawnAnonymous(InteractiveTxBuilder( ByteVector32.Zeroes, - nodeParamsA, fundingParams, channelParamsA, channelKeysA, + nodeParamsA, fundingParams, channelParamsA, commitParamsA, commitParamsB, channelKeysA, SpliceTx(commitment, CommitmentChanges.init()), 0 msat, 0 msat, liquidityPurchase_opt, wallet)) def spawnTxBuilderSpliceRbfAlice(fundingParams: InteractiveTxParams, parentCommitment: Commitment, latestFundingTx: LocalFundingStatus.DualFundedUnconfirmedFundingTx, previousTransactions: Seq[InteractiveTxBuilder.SignedSharedTransaction], wallet: OnChainWallet): ActorRef[InteractiveTxBuilder.Command] = system.spawnAnonymous(InteractiveTxBuilder( ByteVector32.Zeroes, - nodeParamsA, fundingParams, channelParamsA, channelKeysA, + nodeParamsA, fundingParams, channelParamsA, commitParamsA, commitParamsB, channelKeysA, SpliceTxRbf(parentCommitment, CommitmentChanges.init(), latestFundingTx, previousTransactions, feeBudget_opt = None), 0 msat, 0 msat, None, wallet)) def spawnTxBuilderBob(wallet: OnChainWallet, fundingParams: InteractiveTxParams = fundingParamsB, liquidityPurchase_opt: Option[LiquidityAds.Purchase] = None): ActorRef[InteractiveTxBuilder.Command] = system.spawnAnonymous(InteractiveTxBuilder( ByteVector32.Zeroes, - nodeParamsB, fundingParams, channelParamsB, channelKeysB, + nodeParamsB, fundingParams, channelParamsB, commitParamsB, commitParamsA, channelKeysB, FundingTx(commitFeerate, firstPerCommitmentPointA, feeBudget_opt = None), 0 msat, 0 msat, liquidityPurchase_opt, wallet)) def spawnTxBuilderRbfBob(fundingParams: InteractiveTxParams, commitment: Commitment, previousTransactions: Seq[InteractiveTxBuilder.SignedSharedTransaction], wallet: OnChainWallet): ActorRef[InteractiveTxBuilder.Command] = system.spawnAnonymous(InteractiveTxBuilder( ByteVector32.Zeroes, - nodeParamsB, fundingParams, channelParamsB, channelKeysB, + nodeParamsB, fundingParams, channelParamsB, commitParamsB, commitParamsA, channelKeysB, FundingTxRbf(commitment, previousTransactions, feeBudget_opt = None), 0 msat, 0 msat, None, wallet)) def spawnTxBuilderSpliceBob(fundingParams: InteractiveTxParams, commitment: Commitment, wallet: OnChainWallet, liquidityPurchase_opt: Option[LiquidityAds.Purchase] = None): ActorRef[InteractiveTxBuilder.Command] = system.spawnAnonymous(InteractiveTxBuilder( ByteVector32.Zeroes, - nodeParamsB, fundingParams, channelParamsB, channelKeysB, + nodeParamsB, fundingParams, channelParamsB, commitParamsB, commitParamsA, channelKeysB, SpliceTx(commitment, CommitmentChanges.init()), 0 msat, 0 msat, liquidityPurchase_opt, wallet)) def spawnTxBuilderSpliceRbfBob(fundingParams: InteractiveTxParams, parentCommitment: Commitment, latestFundingTx: LocalFundingStatus.DualFundedUnconfirmedFundingTx, previousTransactions: Seq[InteractiveTxBuilder.SignedSharedTransaction], wallet: OnChainWallet): ActorRef[InteractiveTxBuilder.Command] = system.spawnAnonymous(InteractiveTxBuilder( ByteVector32.Zeroes, - nodeParamsB, fundingParams, channelParamsB, channelKeysB, + nodeParamsB, fundingParams, channelParamsB, commitParamsB, commitParamsA, channelKeysB, SpliceTxRbf(parentCommitment, CommitmentChanges.init(), latestFundingTx, previousTransactions, feeBudget_opt = None), 0 msat, 0 msat, None, wallet)) @@ -219,9 +219,9 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit private def createFixtureParams(fundingAmountA: Satoshi, fundingAmountB: Satoshi, targetFeerate: FeeratePerKw, dustLimit: Satoshi, lockTime: Long, requireConfirmedInputs: RequireConfirmedInputs = RequireConfirmedInputs(forLocal = false, forRemote = false), nonInitiatorPaysCommitTxFees: Boolean = false): FixtureParams = { val channelFeatures = ChannelFeatures(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), Features[InitFeature](Features.DualFunding -> FeatureSupport.Optional), Features[InitFeature](Features.DualFunding -> FeatureSupport.Optional), announceChannel = true) val Seq(nodeParamsA, nodeParamsB) = Seq(TestConstants.Alice.nodeParams, TestConstants.Bob.nodeParams).map(_.copy(features = Features(channelFeatures.features.map(f => f -> FeatureSupport.Optional).toMap[Feature, FeatureSupport]))) - val localChannelParamsA = makeChannelParams(nodeParamsA, nodeParamsA.features.initFeatures(), None, None, isChannelOpener = true, paysCommitTxFees = !nonInitiatorPaysCommitTxFees, dualFunded = true, fundingAmountA, unlimitedMaxHtlcValueInFlight = false) + val localChannelParamsA = makeChannelParams(nodeParamsA, nodeParamsA.features.initFeatures(), None, None, isChannelOpener = true, paysCommitTxFees = !nonInitiatorPaysCommitTxFees, dualFunded = true, fundingAmountA) val commitParamsA = CommitParams(nodeParamsA.channelConf.dustLimit, nodeParamsA.channelConf.htlcMinimum, nodeParamsA.channelConf.maxHtlcValueInFlight(fundingAmountA + fundingAmountB, unlimited = false), nodeParamsA.channelConf.maxAcceptedHtlcs, nodeParamsB.channelConf.toRemoteDelay) - val localChannelParamsB = makeChannelParams(nodeParamsB, nodeParamsB.features.initFeatures(), None, None, isChannelOpener = false, paysCommitTxFees = nonInitiatorPaysCommitTxFees, dualFunded = true, fundingAmountB, unlimitedMaxHtlcValueInFlight = false) + val localChannelParamsB = makeChannelParams(nodeParamsB, nodeParamsB.features.initFeatures(), None, None, isChannelOpener = false, paysCommitTxFees = nonInitiatorPaysCommitTxFees, dualFunded = true, fundingAmountB) val commitParamsB = CommitParams(nodeParamsB.channelConf.dustLimit, nodeParamsB.channelConf.htlcMinimum, nodeParamsB.channelConf.maxHtlcValueInFlight(fundingAmountA + fundingAmountB, unlimited = false), nodeParamsB.channelConf.maxAcceptedHtlcs, nodeParamsA.channelConf.toRemoteDelay) val channelKeysA = nodeParamsA.channelKeyManager.channelKeys(ChannelConfig.standard, localChannelParamsA.fundingKeyPath) val channelKeysB = nodeParamsB.channelKeyManager.channelKeys(ChannelConfig.standard, localChannelParamsB.fundingKeyPath) @@ -230,7 +230,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit case (nodeParams, localParams, channelKeys) => RemoteChannelParams( nodeParams.nodeId, - localParams.dustLimit, localParams.maxHtlcValueInFlightMsat, None, localParams.htlcMinimum, nodeParams.channelConf.toRemoteDelay, localParams.maxAcceptedHtlcs, + None, channelKeys.revocationBasePoint, localParams.walletStaticPaymentBasepoint.getOrElse(channelKeys.paymentBasePoint), channelKeys.delayedPaymentBasePoint, @@ -242,8 +242,8 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val channelId = randomBytes32() val fundingPubKeyA = channelKeysA.fundingKey(fundingTxIndex = 0).publicKey val fundingPubKeyB = channelKeysB.fundingKey(fundingTxIndex = 0).publicKey - val fundingParamsA = InteractiveTxParams(channelId, isInitiator = true, fundingAmountA, fundingAmountB, None, fundingPubKeyB, Nil, lockTime, dustLimit, targetFeerate, requireConfirmedInputs) - val fundingParamsB = InteractiveTxParams(channelId, isInitiator = false, fundingAmountB, fundingAmountA, None, fundingPubKeyA, Nil, lockTime, dustLimit, targetFeerate, requireConfirmedInputs) + val fundingParamsA = InteractiveTxParams(channelId, isInitiator = true, fundingAmountA, fundingAmountB, None, fundingPubKeyB, Nil, channelFeatures.commitmentFormat, lockTime, dustLimit, targetFeerate, requireConfirmedInputs) + val fundingParamsB = InteractiveTxParams(channelId, isInitiator = false, fundingAmountB, fundingAmountA, None, fundingPubKeyA, Nil, channelFeatures.commitmentFormat, lockTime, dustLimit, targetFeerate, requireConfirmedInputs) val channelParamsA = ChannelParams(channelId, ChannelConfig.standard, channelFeatures, localChannelParamsA, remoteChannelParamsB, ChannelFlags(announceChannel = true)) val channelParamsB = ChannelParams(channelId, ChannelConfig.standard, channelFeatures, localChannelParamsB, remoteChannelParamsA, ChannelFlags(announceChannel = true)) @@ -751,7 +751,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit // Alice and Bob decide to splice additional funds in the channel. val additionalFundingA2 = 30_000.sat val additionalFundingB2 = 25_000.sat - val (sharedInputA, sharedInputB) = sharedInputs(commitmentA1, commitmentB1) + val (sharedInputA, sharedInputB) = fixtureParams.sharedInputs(commitmentA1, commitmentB1) val spliceFixtureParams = fixtureParams.createSpliceFixtureParams(fundingTxIndex = 1, fundingAmountA = additionalFundingA2, fundingAmountB = additionalFundingB2, aliceParams.targetFeerate, aliceParams.dustLimit, aliceParams.lockTime, sharedInputA = sharedInputA, sharedInputB = sharedInputB, requireConfirmedInputs = aliceParams.requireConfirmedInputs) val aliceSplice = fixtureParams.spawnTxBuilderSpliceAlice(spliceFixtureParams.fundingParamsA, commitmentA1, walletA) val bobSplice = fixtureParams.spawnTxBuilderSpliceBob(spliceFixtureParams.fundingParamsB, commitmentB1, walletB) @@ -787,7 +787,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit // Bob has more balance than Alice in the shared input, so its total contribution is greater than Alice. // But Bob still signs first, because we don't split the shared input's balance when deciding who signs first. assert(spliceTxA.tx.localAmountIn < spliceTxA.tx.remoteAmountIn) - assert(spliceTxA.signedTx.txIn.exists(_.outPoint == commitmentA1.commitInput.outPoint)) + assert(spliceTxA.signedTx.txIn.exists(_.outPoint == commitmentA1.fundingInput)) assert(0.msat < spliceTxA.tx.localFees) assert(0.msat < spliceTxA.tx.remoteFees) assert(spliceTxB.tx.localFees == spliceTxA.tx.remoteFees) @@ -846,7 +846,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val spliceOutputsB = List(TxOut(30_000 sat, Script.pay2wpkh(randomKey().publicKey))) val subtractedFundingA = spliceOutputsA.map(_.amount).sum + 1_000.sat val subtractedFundingB = spliceOutputsB.map(_.amount).sum + 500.sat - val (sharedInputA, sharedInputB) = sharedInputs(commitmentA1, commitmentB1) + val (sharedInputA, sharedInputB) = fixtureParams.sharedInputs(commitmentA1, commitmentB1) val spliceFixtureParams = fixtureParams.createSpliceFixtureParams(fundingTxIndex = 1, fundingAmountA = -subtractedFundingA, fundingAmountB = -subtractedFundingB, aliceParams.targetFeerate, aliceParams.dustLimit, aliceParams.lockTime, sharedInputA = sharedInputA, sharedInputB = sharedInputB, spliceOutputsA = spliceOutputsA, spliceOutputsB = spliceOutputsB, requireConfirmedInputs = aliceParams.requireConfirmedInputs) val aliceSplice = fixtureParams.spawnTxBuilderSpliceAlice(spliceFixtureParams.fundingParamsA, commitmentA1, walletA) @@ -859,7 +859,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit // Alice --- tx_add_input --> Bob val sharedInput = fwdSplice.forwardAlice2Bob[TxAddInput] assert(sharedInput.previousTx_opt.isEmpty) - assert(sharedInput.sharedInput_opt.contains(commitmentA1.commitInput.outPoint)) + assert(sharedInput.sharedInput_opt.contains(commitmentA1.fundingInput)) // Alice <-- tx_add_output --- Bob val outputB = fwdSplice.forwardBob2Alice[TxAddOutput] // Alice --- tx_add_output --> Bob @@ -934,7 +934,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val spliceOutputsB = List(25_000 sat, 15_000 sat).map(amount => TxOut(amount, Script.pay2wpkh(randomKey().publicKey))) val subtractedFundingA = spliceOutputsA.map(_.amount).sum + 1_000.sat val subtractedFundingB = spliceOutputsB.map(_.amount).sum + 500.sat - val (sharedInputA, sharedInputB) = sharedInputs(commitmentA1, commitmentB1) + val (sharedInputA, sharedInputB) = fixtureParams.sharedInputs(commitmentA1, commitmentB1) val spliceFixtureParams = fixtureParams.createSpliceFixtureParams(fundingTxIndex = 1, fundingAmountA = -subtractedFundingA, fundingAmountB = -subtractedFundingB, aliceParams.targetFeerate, aliceParams.dustLimit, aliceParams.lockTime, sharedInputA = sharedInputA, sharedInputB = sharedInputB, spliceOutputsA = spliceOutputsA, spliceOutputsB = spliceOutputsB, requireConfirmedInputs = aliceParams.requireConfirmedInputs) val aliceSplice = fixtureParams.spawnTxBuilderSpliceAlice(spliceFixtureParams.fundingParamsA, commitmentA1, walletA) val bobSplice = fixtureParams.spawnTxBuilderSpliceBob(spliceFixtureParams.fundingParamsB, commitmentB1, walletB) @@ -946,7 +946,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit // Alice --- tx_add_input --> Bob val sharedInput = fwdSplice.forwardAlice2Bob[TxAddInput] assert(sharedInput.previousTx_opt.isEmpty) - assert(sharedInput.sharedInput_opt.contains(commitmentA1.commitInput.outPoint)) + assert(sharedInput.sharedInput_opt.contains(commitmentA1.fundingInput)) // Alice <-- tx_add_output --- Bob val outputB1 = fwdSplice.forwardBob2Alice[TxAddOutput] // Alice --- tx_add_output --> Bob @@ -1031,7 +1031,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val additionalFundingB = 15_000.sat val spliceOutputsA = List(TxOut(30_000 sat, Script.pay2wpkh(randomKey().publicKey))) val spliceOutputsB = List(TxOut(10_000 sat, Script.pay2wpkh(randomKey().publicKey))) - val (sharedInputA, sharedInputB) = sharedInputs(commitmentA1, commitmentB1) + val (sharedInputA, sharedInputB) = fixtureParams.sharedInputs(commitmentA1, commitmentB1) val spliceFixtureParams = fixtureParams.createSpliceFixtureParams(fundingTxIndex = 1, fundingAmountA = additionalFundingA, fundingAmountB = additionalFundingB, aliceParams.targetFeerate, aliceParams.dustLimit, aliceParams.lockTime, sharedInputA = sharedInputA, sharedInputB = sharedInputB, spliceOutputsA = spliceOutputsA, spliceOutputsB = spliceOutputsB, requireConfirmedInputs = aliceParams.requireConfirmedInputs) val aliceSplice = fixtureParams.spawnTxBuilderSpliceAlice(spliceFixtureParams.fundingParamsA, commitmentA1, walletA) val bobSplice = fixtureParams.spawnTxBuilderSpliceBob(spliceFixtureParams.fundingParamsB, commitmentB1, walletB) @@ -1540,7 +1540,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val additionalFundingB = 5_000.sat val spliceOutputsA = List(TxOut(20_000 sat, Script.pay2wpkh(randomKey().publicKey))) val spliceOutputsB = List(TxOut(10_000 sat, Script.pay2wpkh(randomKey().publicKey))) - val (sharedInputA, sharedInputB) = sharedInputs(commitmentA1, commitmentB1) + val (sharedInputA, sharedInputB) = fixtureParams.sharedInputs(commitmentA1, commitmentB1) val spliceFixtureParams = fixtureParams.createSpliceFixtureParams(fundingTxIndex = 1, fundingAmountA = additionalFundingA, fundingAmountB = additionalFundingB, aliceParams.targetFeerate, aliceParams.dustLimit, aliceParams.lockTime, sharedInputA = sharedInputA, sharedInputB = sharedInputB, spliceOutputsA = spliceOutputsA, spliceOutputsB = spliceOutputsB, requireConfirmedInputs = aliceParams.requireConfirmedInputs) val aliceSplice = fixtureParams.spawnTxBuilderSpliceAlice(spliceFixtureParams.fundingParamsA, commitmentA1, walletA) val bobSplice = fixtureParams.spawnTxBuilderSpliceBob(spliceFixtureParams.fundingParamsB, commitmentB1, walletB) @@ -1667,7 +1667,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val additionalFundingB = 5_000.sat val spliceOutputsA = List(TxOut(20_000 sat, Script.pay2wpkh(randomKey().publicKey))) val spliceOutputsB = List(TxOut(10_000 sat, Script.pay2wpkh(randomKey().publicKey))) - val (sharedInputA, sharedInputB) = sharedInputs(commitmentA1, commitmentB1) + val (sharedInputA, sharedInputB) = fixtureParams.sharedInputs(commitmentA1, commitmentB1) val spliceFixtureParams = fixtureParams.createSpliceFixtureParams(fundingTxIndex = 1, fundingAmountA = additionalFundingA, fundingAmountB = additionalFundingB, aliceParams.targetFeerate, aliceParams.dustLimit, aliceParams.lockTime, sharedInputA = sharedInputA, sharedInputB = sharedInputB, spliceOutputsA = spliceOutputsA, spliceOutputsB = spliceOutputsB, requireConfirmedInputs = aliceParams.requireConfirmedInputs) val fundingParamsA1 = spliceFixtureParams.fundingParamsA val fundingParamsB1 = spliceFixtureParams.fundingParamsB @@ -1806,13 +1806,13 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val spliceFeeA = { val dummySpliceTx = Transaction( version = 2, - txIn = Seq(TxIn(commitmentA1.commitInput.outPoint, ByteVector.empty, 0, Scripts.witness2of2(Transactions.PlaceHolderSig, Transactions.PlaceHolderSig, randomKey().publicKey, randomKey().publicKey))), - txOut = Seq(commitmentA1.commitInput.txOut), + txIn = Seq(TxIn(commitmentA1.fundingInput, ByteVector.empty, 0, Scripts.witness2of2(Transactions.PlaceHolderSig, Transactions.PlaceHolderSig, randomKey().publicKey, randomKey().publicKey))), + txOut = Seq(commitmentA1.commitInput(fixtureParams.channelKeysA).txOut), lockTime = 0 ) Transactions.weight2fee(targetFeerate, dummySpliceTx.weight()) } - val (sharedInputA, sharedInputB) = sharedInputs(commitmentA1, commitmentB1) + val (sharedInputA, sharedInputB) = fixtureParams.sharedInputs(commitmentA1, commitmentB1) val spliceFixtureParams = fixtureParams.createSpliceFixtureParams(fundingTxIndex = 1, fundingAmountA = -spliceFeeA, fundingAmountB = fundingB, targetFeerate, aliceParams.dustLimit, aliceParams.lockTime, sharedInputA = sharedInputA, sharedInputB = sharedInputB, spliceOutputsA = Nil, spliceOutputsB = Nil, requireConfirmedInputs = aliceParams.requireConfirmedInputs) val fundingParamsA1 = spliceFixtureParams.fundingParamsA val fundingParamsB1 = spliceFixtureParams.fundingParamsB @@ -1891,7 +1891,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit // Alice splices some funds in, which requires using an additional input. val additionalFundingA1 = 25_000.sat - val (sharedInputA, sharedInputB) = sharedInputs(commitmentA1, commitmentB1) + val (sharedInputA, sharedInputB) = fixtureParams.sharedInputs(commitmentA1, commitmentB1) val spliceFixtureParams = fixtureParams.createSpliceFixtureParams(fundingTxIndex = 1, fundingAmountA = additionalFundingA1, fundingAmountB = 0 sat, aliceParams.targetFeerate, aliceParams.dustLimit, aliceParams.lockTime, sharedInputA = sharedInputA, sharedInputB = sharedInputB, requireConfirmedInputs = aliceParams.requireConfirmedInputs) val fundingParamsA1 = spliceFixtureParams.fundingParamsA val fundingParamsB1 = spliceFixtureParams.fundingParamsB @@ -2144,7 +2144,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val subtractedFundingB = 398_000 sat val spliceOutputsA = List(TxOut(99_000 sat, Script.pay2wpkh(randomKey().publicKey))) val spliceOutputsB = List(TxOut(397_000 sat, Script.pay2wpkh(randomKey().publicKey))) - val (sharedInputA, sharedInputB) = sharedInputs(commitmentA, commitmentB) + val (sharedInputA, sharedInputB) = fixtureParams.sharedInputs(commitmentA, commitmentB) val fundingParamsA1 = aliceParams.copy(localContribution = -subtractedFundingA, remoteContribution = -subtractedFundingB, sharedInput_opt = Some(sharedInputA), localOutputs = spliceOutputsA) val fundingParamsB1 = bobParams.copy(localContribution = -subtractedFundingB, remoteContribution = -subtractedFundingA, sharedInput_opt = Some(sharedInputB), localOutputs = spliceOutputsB) val aliceSplice = fixtureParams.spawnTxBuilderSpliceAlice(fundingParamsA1, commitmentA, walletA) @@ -2205,7 +2205,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit // Alice splices some funds out, which creates two outputs (a shared output and a splice output). val subtractedFundingA = 30_000 sat val spliceOutputsA = List(TxOut(25_000 sat, Script.pay2wpkh(randomKey().publicKey))) - val (sharedInputA, sharedInputB) = sharedInputs(commitmentA, commitmentB) + val (sharedInputA, sharedInputB) = fixtureParams.sharedInputs(commitmentA, commitmentB) val spliceFixtureParams = fixtureParams.createSpliceFixtureParams(fundingTxIndex = 1, fundingAmountA = -subtractedFundingA, fundingAmountB = 0 sat, aliceParams.targetFeerate, aliceParams.dustLimit, aliceParams.lockTime, sharedInputA = sharedInputA, sharedInputB = sharedInputB, spliceOutputsA = spliceOutputsA, spliceOutputsB = Nil, requireConfirmedInputs = aliceParams.requireConfirmedInputs) val fundingParamsA1 = spliceFixtureParams.fundingParamsA val fundingParamsB1 = spliceFixtureParams.fundingParamsB @@ -2272,7 +2272,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit // Alice splices some funds out, but she doesn't have the same commitment index than Bob. val subtractedFundingA = 30_000 sat val spliceOutputsA = List(TxOut(25_000 sat, Script.pay2wpkh(randomKey().publicKey))) - val (sharedInputA, sharedInputB) = sharedInputs(commitmentA, commitmentB) + val (sharedInputA, sharedInputB) = fixtureParams.sharedInputs(commitmentA, commitmentB) val spliceFixtureParams = fixtureParams.createSpliceFixtureParams(fundingTxIndex = 1, fundingAmountA = -subtractedFundingA, fundingAmountB = 0 sat, aliceParams.targetFeerate, aliceParams.dustLimit, aliceParams.lockTime, sharedInputA = sharedInputA, sharedInputB = sharedInputB, spliceOutputsA = spliceOutputsA, spliceOutputsB = Nil, requireConfirmedInputs = aliceParams.requireConfirmedInputs) val fundingParamsA1 = spliceFixtureParams.fundingParamsA val fundingParamsB1 = spliceFixtureParams.fundingParamsB @@ -2606,7 +2606,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val wallet = new SingleKeyOnChainWallet() val params = createFixtureParams(100_000 sat, 0 sat, FeeratePerKw(5000 sat), 330 sat, 0) val previousCommitment = CommitmentsSpec.makeCommitments(25_000_000 msat, 50_000_000 msat).active.head - val fundingParams = params.fundingParamsB.copy(sharedInput_opt = Some(Multisig2of2Input(previousCommitment.commitInput, 0, randomKey().publicKey))) + val fundingParams = params.fundingParamsB.copy(sharedInput_opt = Some(Multisig2of2Input(previousCommitment.commitInput(params.channelKeysB), 0, randomKey().publicKey))) val bob = params.spawnTxBuilderSpliceBob(fundingParams, previousCommitment, wallet) bob ! Start(probe.ref) // Alice --- tx_add_input --> Bob @@ -2622,7 +2622,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val params = createFixtureParams(100_000 sat, 0 sat, FeeratePerKw(5000 sat), 330 sat, 0) val previousCommitment = CommitmentsSpec.makeCommitments(25_000_000 msat, 50_000_000 msat).active.head val fundingTx = Transaction(2, Nil, Seq(TxOut(50_000 sat, Script.pay2wpkh(randomKey().publicKey)), TxOut(20_000 sat, Script.pay2wpkh(randomKey().publicKey))), 0) - val sharedInput = Multisig2of2Input(InputInfo(OutPoint(fundingTx, 0), fundingTx.txOut.head, ByteVector.empty), 0, randomKey().publicKey) + val sharedInput = Multisig2of2Input(InputInfo(OutPoint(fundingTx, 0), fundingTx.txOut.head), 0, randomKey().publicKey) val bob = params.spawnTxBuilderSpliceBob(params.fundingParamsB.copy(sharedInput_opt = Some(sharedInput)), previousCommitment, wallet) bob ! Start(probe.ref) // Alice --- tx_add_input --> Bob diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala index 99be9c28b0..6c97e6c72f 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala @@ -190,12 +190,12 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w val commitment = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest val commitTx = commitment.fullySignedLocalCommitTx(alice.underlyingActor.channelKeys) - val commitFee = commitment.commitInput.txOut.amount - commitTx.txOut.map(_.amount).sum + val commitFee = commitment.capacity - commitTx.txOut.map(_.amount).sum probe.send(alice, CMD_FORCECLOSE(probe.ref)) probe.expectMsgType[CommandSuccess[CMD_FORCECLOSE]] // Forward the commit tx to the publisher. - val publishCommitTx = alice2blockchain.expectMsg(PublishFinalTx(commitTx, commitment.commitInput.outPoint, "commit-tx", commitFee, None)) + val publishCommitTx = alice2blockchain.expectMsg(PublishFinalTx(commitTx, commitment.fundingInput, "commit-tx", commitFee, None)) // Forward the anchor tx to the publisher. val publishAnchor = alice2blockchain.expectMsgType[PublishReplaceableTx].copy(confirmationTarget = ConfirmationTarget.Absolute(overrideCommitTarget)) assert(publishAnchor.commitTx == commitTx) @@ -1071,13 +1071,13 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w // Force-close channel and verify txs sent to watcher. val commitment = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest val commitTx = commitment.fullySignedLocalCommitTx(alice.underlyingActor.channelKeys) - val commitFee = commitment.commitInput.txOut.amount - commitTx.txOut.map(_.amount).sum + val commitFee = commitment.capacity - commitTx.txOut.map(_.amount).sum assert(commitTx.txOut.size == 6) probe.send(alice, CMD_FORCECLOSE(probe.ref)) probe.expectMsgType[CommandSuccess[CMD_FORCECLOSE]] // We make the commit tx confirm because htlc txs have a relative delay. - alice2blockchain.expectMsg(PublishFinalTx(commitTx, commitment.commitInput.outPoint, "commit-tx", commitFee, None)) + alice2blockchain.expectMsg(PublishFinalTx(commitTx, commitment.fundingInput, "commit-tx", commitFee, None)) wallet.publishTransaction(commitTx).pipeTo(probe.ref) probe.expectMsg(commitTx.txid) generateBlocks(1) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/TxPublisherSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/TxPublisherSpec.scala index f64f9c7802..d00b3f429e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/TxPublisherSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/TxPublisherSpec.scala @@ -112,7 +112,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { val confirmBefore = ConfirmationTarget.Absolute(nodeParams.currentBlockHeight + 12) val input = OutPoint(randomTxId(), 3) - val anchorTx = ClaimLocalAnchorTx(fundingKey, localCommitKeys, InputInfo(input, TxOut(25_000 sat, Nil), ByteVector.empty), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) + val anchorTx = ClaimLocalAnchorTx(fundingKey, localCommitKeys, InputInfo(input, TxOut(25_000 sat, Nil)), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) val cmd = PublishReplaceableTx(anchorTx, null, null, confirmBefore) txPublisher ! cmd val child = factory.expectMsgType[ReplaceableTxPublisherSpawned].actor @@ -125,7 +125,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { val confirmBefore = nodeParams.currentBlockHeight + 12 val input = OutPoint(randomTxId(), 3) - val anchorTx = ClaimLocalAnchorTx(fundingKey, localCommitKeys, InputInfo(input, TxOut(25_000 sat, Nil), ByteVector.empty), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) + val anchorTx = ClaimLocalAnchorTx(fundingKey, localCommitKeys, InputInfo(input, TxOut(25_000 sat, Nil)), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) val cmd = PublishReplaceableTx(anchorTx, null, null, ConfirmationTarget.Priority(ConfirmationPriority.Medium)) txPublisher ! cmd val child = factory.expectMsgType[ReplaceableTxPublisherSpawned].actor @@ -183,7 +183,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { val attempt2 = factory.expectMsgType[FinalTxPublisherSpawned].actor attempt2.expectMsgType[FinalTxPublisher.Publish] - val anchorTx = ClaimLocalAnchorTx(fundingKey, localCommitKeys, InputInfo(input, TxOut(25_000 sat, Nil), ByteVector.empty), Transaction(2, TxIn(input, Nil, 0) :: Nil, TxOut(20_000 sat, Nil) :: Nil, 0), ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) + val anchorTx = ClaimLocalAnchorTx(fundingKey, localCommitKeys, InputInfo(input, TxOut(25_000 sat, Nil)), Transaction(2, TxIn(input, Nil, 0) :: Nil, TxOut(20_000 sat, Nil) :: Nil, 0), ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) val cmd3 = PublishReplaceableTx(anchorTx, null, null, ConfirmationTarget.Absolute(nodeParams.currentBlockHeight)) txPublisher ! cmd3 val attempt3 = factory.expectMsgType[ReplaceableTxPublisherSpawned].actor @@ -206,7 +206,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { val attempt1 = factory.expectMsgType[FinalTxPublisherSpawned] attempt1.actor.expectMsgType[FinalTxPublisher.Publish] - val anchorTx = ClaimRemoteAnchorTx(fundingKey, remoteCommitKeys, InputInfo(input, TxOut(25_000 sat, Nil), ByteVector.empty), Transaction(2, TxIn(input, Nil, 0) :: Nil, TxOut(20_000 sat, Nil) :: Nil, 0), ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) + val anchorTx = ClaimRemoteAnchorTx(fundingKey, remoteCommitKeys, InputInfo(input, TxOut(25_000 sat, Nil)), Transaction(2, TxIn(input, Nil, 0) :: Nil, TxOut(20_000 sat, Nil) :: Nil, 0), ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) val cmd2 = PublishReplaceableTx(anchorTx, null, null, ConfirmationTarget.Absolute(nodeParams.currentBlockHeight)) txPublisher ! cmd2 val attempt2 = factory.expectMsgType[ReplaceableTxPublisherSpawned] @@ -248,7 +248,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { val input = OutPoint(randomTxId(), 7) val preimage = randomBytes32() val remoteSig = randomBytes64() - val htlcTx = HtlcSuccessTx(localCommitKeys, InputInfo(input, TxOut(25_000 sat, Nil), ByteVector.empty), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), 3, expiry, preimage, remoteSig, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) + val htlcTx = HtlcSuccessTx(localCommitKeys, InputInfo(input, TxOut(25_000 sat, Nil)), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), 3, expiry, preimage, remoteSig, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) val cmd = PublishReplaceableTx(htlcTx, null, null, ConfirmationTarget.Absolute(expiry.blockHeight)) txPublisher ! cmd val attempt1 = factory.expectMsgType[ReplaceableTxPublisherSpawned] @@ -314,7 +314,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { val input = OutPoint(randomTxId(), 7) val paymentHash = randomBytes32() val remoteSig = randomBytes64() - val htlcTx = HtlcTimeoutTx(localCommitKeys, InputInfo(input, TxOut(25_000 sat, Nil), ByteVector.empty), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), paymentHash, 3, CltvExpiry(nodeParams.currentBlockHeight), remoteSig, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) + val htlcTx = HtlcTimeoutTx(localCommitKeys, InputInfo(input, TxOut(25_000 sat, Nil)), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), paymentHash, 3, CltvExpiry(nodeParams.currentBlockHeight), remoteSig, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) val cmd = PublishReplaceableTx(htlcTx, null, null, ConfirmationTarget.Absolute(nodeParams.currentBlockHeight)) txPublisher ! cmd val attempt1 = factory.expectMsgType[ReplaceableTxPublisherSpawned] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala index c44d613474..eef593fd1c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala @@ -297,22 +297,22 @@ trait ChannelStateTestsBase extends Assertions with Eventually { val aliceChannelParams = Alice.channelParams .modify(_.initFeatures).setTo(aliceInitFeatures) .modify(_.walletStaticPaymentBasepoint).setToIf(channelType.paysDirectlyToWallet)(Some(Await.result(wallet.getP2wpkhPubkey(), 10 seconds))) - .modify(_.maxHtlcValueInFlightMsat).setToIf(tags.contains(ChannelStateTestsTags.NoMaxHtlcValueInFlight))(UInt64.MaxValue) - .modify(_.maxHtlcValueInFlightMsat).setToIf(tags.contains(ChannelStateTestsTags.AliceLowMaxHtlcValueInFlight))(UInt64(150_000_000)) - .modify(_.dustLimit).setToIf(tags.contains(ChannelStateTestsTags.HighDustLimitDifferenceAliceBob))(5000 sat) - .modify(_.dustLimit).setToIf(tags.contains(ChannelStateTestsTags.HighDustLimitDifferenceBobAlice))(1000 sat) .modify(_.initialRequestedChannelReserve_opt).setToIf(tags.contains(ChannelStateTestsTags.DualFunding))(None) .modify(_.upfrontShutdownScript_opt).setToIf(tags.contains(ChannelStateTestsTags.UpfrontShutdownScript))(Some(Script.write(Script.pay2wpkh(Await.result(wallet.getP2wpkhPubkey(), 10 seconds))))) + val aliceCommitParams = CommitParams(nodeParamsA.channelConf.dustLimit, nodeParamsA.channelConf.htlcMinimum, nodeParamsA.channelConf.maxHtlcValueInFlight(TestConstants.fundingSatoshis, unlimited = false), nodeParamsA.channelConf.maxAcceptedHtlcs, nodeParamsB.channelConf.toRemoteDelay) + .modify(_.maxHtlcValueInFlight).setToIf(tags.contains(ChannelStateTestsTags.NoMaxHtlcValueInFlight))(UInt64.MaxValue) + .modify(_.maxHtlcValueInFlight).setToIf(tags.contains(ChannelStateTestsTags.AliceLowMaxHtlcValueInFlight))(UInt64(150_000_000)) + .modify(_.dustLimit).setToIf(tags.contains(ChannelStateTestsTags.HighDustLimitDifferenceAliceBob))(5000 sat) + .modify(_.dustLimit).setToIf(tags.contains(ChannelStateTestsTags.HighDustLimitDifferenceBobAlice))(1000 sat) val bobChannelParams = Bob.channelParams .modify(_.initFeatures).setTo(bobInitFeatures) .modify(_.walletStaticPaymentBasepoint).setToIf(channelType.paysDirectlyToWallet)(Some(Await.result(wallet.getP2wpkhPubkey(), 10 seconds))) - .modify(_.maxHtlcValueInFlightMsat).setToIf(tags.contains(ChannelStateTestsTags.NoMaxHtlcValueInFlight))(UInt64.MaxValue) - .modify(_.dustLimit).setToIf(tags.contains(ChannelStateTestsTags.HighDustLimitDifferenceAliceBob))(1000 sat) - .modify(_.dustLimit).setToIf(tags.contains(ChannelStateTestsTags.HighDustLimitDifferenceBobAlice))(5000 sat) .modify(_.initialRequestedChannelReserve_opt).setToIf(tags.contains(ChannelStateTestsTags.DualFunding))(None) .modify(_.upfrontShutdownScript_opt).setToIf(tags.contains(ChannelStateTestsTags.UpfrontShutdownScript))(Some(Script.write(Script.pay2wpkh(Await.result(wallet.getP2wpkhPubkey(), 10 seconds))))) - val aliceCommitParams = CommitParams(aliceChannelParams.dustLimit, aliceChannelParams.htlcMinimum, aliceChannelParams.maxHtlcValueInFlightMsat, aliceChannelParams.maxAcceptedHtlcs, bobChannelParams.toRemoteDelay) - val bobCommitParams = CommitParams(bobChannelParams.dustLimit, bobChannelParams.htlcMinimum, bobChannelParams.maxHtlcValueInFlightMsat, bobChannelParams.maxAcceptedHtlcs, aliceChannelParams.toRemoteDelay) + val bobCommitParams = CommitParams(nodeParamsB.channelConf.dustLimit, nodeParamsB.channelConf.htlcMinimum, nodeParamsB.channelConf.maxHtlcValueInFlight(TestConstants.fundingSatoshis, unlimited = false), nodeParamsB.channelConf.maxAcceptedHtlcs, nodeParamsA.channelConf.toRemoteDelay) + .modify(_.maxHtlcValueInFlight).setToIf(tags.contains(ChannelStateTestsTags.NoMaxHtlcValueInFlight))(UInt64.MaxValue) + .modify(_.dustLimit).setToIf(tags.contains(ChannelStateTestsTags.HighDustLimitDifferenceAliceBob))(1000 sat) + .modify(_.dustLimit).setToIf(tags.contains(ChannelStateTestsTags.HighDustLimitDifferenceBobAlice))(5000 sat) ChannelParamsFixture(aliceChannelParams, aliceCommitParams, aliceInitFeatures, bobChannelParams, bobCommitParams, bobInitFeatures, channelType, alice2bob, bob2alice, aliceOpenReplyTo) } @@ -640,7 +640,7 @@ trait ChannelStateTestsBase extends Assertions with Eventually { val commitTx = s2blockchain.expectFinalTxPublished("commit-tx").tx assert(commitTx.txid == closingState.commitments.latest.localCommit.txId) - val commitInput = closingState.commitments.latest.commitInput + val commitInput = closingState.commitments.latest.commitInput(s.underlyingActor.channelKeys) Transaction.correctlySpends(commitTx, Map(commitInput.outPoint -> commitInput.txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) val publishedAnchorTx_opt = closingState.commitments.latest.commitmentFormat match { case DefaultCommitmentFormat => None diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala index 6987e09afa..155990c181 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala @@ -84,7 +84,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS assert(accept.channelType_opt.contains(ChannelTypes.AnchorOutputs())) bob2alice.forward(alice) awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL) - assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_INTERNAL].channelParams.commitmentFormat == UnsafeLegacyAnchorOutputsCommitmentFormat) + assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_INTERNAL].commitmentFormat == UnsafeLegacyAnchorOutputsCommitmentFormat) aliceOpenReplyTo.expectNoMessage() } @@ -94,7 +94,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS assert(accept.channelType_opt.contains(ChannelTypes.AnchorOutputsZeroFeeHtlcTx())) bob2alice.forward(alice) awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL) - assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_INTERNAL].channelParams.commitmentFormat == ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) + assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_INTERNAL].commitmentFormat == ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) aliceOpenReplyTo.expectNoMessage() } @@ -104,7 +104,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS assert(accept.channelType_opt.contains(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true))) bob2alice.forward(alice) awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL) - assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_INTERNAL].channelParams.commitmentFormat == ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) + assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_INTERNAL].commitmentFormat == ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) aliceOpenReplyTo.expectNoMessage() } @@ -126,7 +126,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS assert(accept.channelType_opt.contains(ChannelTypes.Standard())) bob2alice.forward(alice, accept) awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL) - assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_INTERNAL].channelParams.commitmentFormat == DefaultCommitmentFormat) + assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_INTERNAL].commitmentFormat == DefaultCommitmentFormat) aliceOpenReplyTo.expectNoMessage() } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala index 2bcd596e15..e73ed7dc53 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala @@ -68,7 +68,7 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui assert(open.channelType_opt.contains(ChannelTypes.StaticRemoteKey())) alice2bob.forward(bob) awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED) - assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].channelParams.commitmentFormat == DefaultCommitmentFormat) + assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].commitmentFormat == DefaultCommitmentFormat) } test("recv OpenChannel (anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => @@ -77,7 +77,7 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui assert(open.channelType_opt.contains(ChannelTypes.AnchorOutputs())) alice2bob.forward(bob) awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED) - assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].channelParams.commitmentFormat == UnsafeLegacyAnchorOutputsCommitmentFormat) + assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].commitmentFormat == UnsafeLegacyAnchorOutputsCommitmentFormat) } test("recv OpenChannel (anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => @@ -86,7 +86,7 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui assert(open.channelType_opt.contains(ChannelTypes.AnchorOutputsZeroFeeHtlcTx())) alice2bob.forward(bob) awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED) - assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].channelParams.commitmentFormat == ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) + assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].commitmentFormat == ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } test("recv OpenChannel (anchor outputs zero fee htlc txs and scid alias)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ScidAlias)) { f => @@ -95,7 +95,7 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui assert(open.channelType_opt.contains(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true))) alice2bob.forward(bob) awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED) - assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].channelParams.commitmentFormat == ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) + assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].commitmentFormat == ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } test("recv OpenChannel (non-default channel type)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(StandardChannelType)) { f => @@ -104,7 +104,7 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui assert(open.channelType_opt.contains(ChannelTypes.Standard())) alice2bob.forward(bob) awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED) - assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].channelParams.commitmentFormat == DefaultCommitmentFormat) + assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].commitmentFormat == DefaultCommitmentFormat) } test("recv OpenChannel (invalid chain)") { f => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForDualFundingReadyStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForDualFundingReadyStateSpec.scala index a921194a25..752b769125 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForDualFundingReadyStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForDualFundingReadyStateSpec.scala @@ -151,7 +151,7 @@ class WaitForDualFundingReadyStateSpec extends TestKitBaseClass with FixtureAnyF assert(aliceUpdate.shortChannelId == aliceChannelReady.alias_opt.value) assert(aliceUpdate.feeBaseMsat == 20.msat) assert(aliceUpdate.feeProportionalMillionths == 125) - assert(aliceCommitments.localChannelReserve == aliceCommitments.commitInput.txOut.amount / 100) + assert(aliceCommitments.localChannelReserve == aliceCommitments.capacity / 100) assert(aliceCommitments.localChannelReserve == aliceCommitments.remoteChannelReserve) val bobCommitments = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest assert(bobCommitments.commitment.shortChannelId_opt.nonEmpty) @@ -190,7 +190,7 @@ class WaitForDualFundingReadyStateSpec extends TestKitBaseClass with FixtureAnyF val aliceCommitments = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest assert(aliceCommitments.commitment.shortChannelId_opt.isEmpty) assert(alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate.shortChannelId == aliceChannelReady.alias_opt.value) - assert(aliceCommitments.localChannelReserve == aliceCommitments.commitInput.txOut.amount / 100) + assert(aliceCommitments.localChannelReserve == aliceCommitments.capacity / 100) assert(aliceCommitments.localChannelReserve == aliceCommitments.remoteChannelReserve) val bobCommitments = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest assert(bobCommitments.commitment.shortChannelId_opt.isEmpty) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalSplicesStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalSplicesStateSpec.scala index b8e5ce5f5a..57749678fe 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalSplicesStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalSplicesStateSpec.scala @@ -301,11 +301,11 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik TestHtlcs(Seq(adda1, adda2), Seq(addb1, addb2)) } - def spliceOutFee(f: FixtureParam, capacity: Satoshi): Satoshi = { + def spliceOutFee(f: FixtureParam, capacity: Satoshi, signedTx_opt: Option[Transaction] = None): Satoshi = { import f._ // When we only splice-out, the fees are paid by deducing them from the next funding amount. - val fundingTx = alice.stateData.asInstanceOf[ChannelDataWithCommitments].commitments.latest.localFundingStatus.signedTx_opt.get + val fundingTx = signedTx_opt.getOrElse(alice.stateData.asInstanceOf[ChannelDataWithCommitments].commitments.latest.localFundingStatus.signedTx_opt.get) val feerate = alice.nodeParams.onChainFeeConf.getFundingFeerate(alice.nodeParams.currentBitcoinCoreFeerates) val expectedMiningFee = Transactions.weight2fee(feerate, fundingTx.weight()) val actualMiningFee = capacity - alice.stateData.asInstanceOf[ChannelDataWithCommitments].commitments.latest.capacity @@ -3082,7 +3082,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik bob2blockchain.expectNoMessage(100 millis) awaitCond(bob.stateName == CLOSED) - checkPostSpliceState(f, spliceOutFee(f, capacity = 1_900_000.sat)) + checkPostSpliceState(f, spliceOutFee(f, capacity = 1_900_000.sat, signedTx_opt = Some(fundingTx2))) assert(Helpers.Closing.isClosed(alice.stateData.asInstanceOf[DATA_CLOSING], None).exists(_.isInstanceOf[LocalClose])) assert(Helpers.Closing.isClosed(bob.stateData.asInstanceOf[DATA_CLOSING], None).exists(_.isInstanceOf[RemoteClose])) } @@ -3447,7 +3447,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik test("put back watches after restart") { f => import f._ - val fundingTx0 = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localFundingStatus.signedTx_opt.get + val fundingTxId0 = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.fundingTxId val fundingTx1 = initiateSplice(f, spliceIn_opt = Some(SpliceIn(500_000 sat, pushAmount = 10_000_000 msat)), spliceOut_opt = Some(SpliceOut(100_000 sat, defaultSpliceOutScriptPubKey))) checkWatchConfirmed(f, fundingTx1) @@ -3480,7 +3480,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik alice2blockchain.expectMsgType[SetChannelId] alice2blockchain.expectWatchFundingConfirmed(fundingTx2.txid) alice2blockchain.expectWatchFundingConfirmed(fundingTx1.txid) - alice2blockchain.expectWatchFundingSpent(fundingTx0.txid) + alice2blockchain.expectWatchFundingSpent(fundingTxId0) alice2blockchain.expectNoMessage(100 millis) val bob2 = TestFSMRef(new Channel(bobNodeParams, bobKeys, wallet, aliceNodeParams.nodeId, bob2blockchain.ref, TestProbe().ref, FakeTxPublisherFactory(bob2blockchain)), bobPeer) @@ -3488,7 +3488,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik bob2blockchain.expectMsgType[SetChannelId] bob2blockchain.expectWatchFundingConfirmed(fundingTx2.txid) bob2blockchain.expectWatchFundingSpent(fundingTx1.txid) - bob2blockchain.expectWatchFundingSpent(fundingTx0.txid) + bob2blockchain.expectWatchFundingSpent(fundingTxId0) bob2blockchain.expectNoMessage(100 millis) } @@ -3565,12 +3565,12 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik val aliceCommitments1 = alice.stateData.asInstanceOf[DATA_NORMAL].commitments aliceCommitments1.active.foreach { c => val commitTx = c.fullySignedLocalCommitTx(aliceCommitments1.channelParams, alice.underlyingActor.channelKeys) - Transaction.correctlySpends(commitTx, Map(c.commitInput.outPoint -> c.commitInput.txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + Transaction.correctlySpends(commitTx, Map(c.fundingInput -> c.commitInput(alice.underlyingActor.channelKeys).txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) } val bobCommitments1 = bob.stateData.asInstanceOf[DATA_NORMAL].commitments bobCommitments1.active.foreach { c => val commitTx = c.fullySignedLocalCommitTx(bobCommitments1.channelParams, bob.underlyingActor.channelKeys) - Transaction.correctlySpends(commitTx, Map(c.commitInput.outPoint -> c.commitInput.txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + Transaction.correctlySpends(commitTx, Map(c.fundingInput -> c.commitInput(bob.underlyingActor.channelKeys).txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) } // alice fulfills that HTLC in both commitments @@ -3579,12 +3579,12 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik val aliceCommitments2 = alice.stateData.asInstanceOf[DATA_NORMAL].commitments aliceCommitments2.active.foreach { c => val commitTx = c.fullySignedLocalCommitTx(aliceCommitments2.channelParams, alice.underlyingActor.channelKeys) - Transaction.correctlySpends(commitTx, Map(c.commitInput.outPoint -> c.commitInput.txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + Transaction.correctlySpends(commitTx, Map(c.fundingInput -> c.commitInput(alice.underlyingActor.channelKeys).txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) } val bobCommitments2 = bob.stateData.asInstanceOf[DATA_NORMAL].commitments bobCommitments2.active.foreach { c => val commitTx = c.fullySignedLocalCommitTx(bobCommitments2.channelParams, bob.underlyingActor.channelKeys) - Transaction.correctlySpends(commitTx, Map(c.commitInput.outPoint -> c.commitInput.txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + Transaction.correctlySpends(commitTx, Map(c.fundingInput -> c.commitInput(bob.underlyingActor.channelKeys).txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) } resolveHtlcs(f, htlcs) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index 386071d0d8..6d89e3dd4e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -224,7 +224,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val sender = TestProbe() val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] // The anchor outputs commitment format costs more fees for the funder (bigger commit tx + cost of anchor outputs) - assert(initialState.commitments.availableBalanceForSend < initialState.commitments.modify(_.channelParams.channelFeatures).setTo(ChannelFeatures()).availableBalanceForSend) + assert(initialState.commitments.availableBalanceForSend < initialState.commitments.modify(_.active).apply(_.map(_.modify(_.commitmentFormat).setTo(DefaultCommitmentFormat))).availableBalanceForSend) val add = CMD_ADD_HTLC(sender.ref, initialState.commitments.availableBalanceForSend + 1.msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref)) alice ! add @@ -941,8 +941,8 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } assert(alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate.channelFlags.isEnabled) inside(bobListener.expectMsgType[LocalChannelUpdate]) { lcu => - assert(lcu.commitments.channelParams.localCommitParams.htlcMinimum == 1000.msat) - assert(lcu.commitments.channelParams.remoteCommitParams.htlcMinimum == 0.msat) + assert(lcu.commitments.latest.localCommitParams.htlcMinimum == 1000.msat) + assert(lcu.commitments.latest.remoteCommitParams.htlcMinimum == 0.msat) assert(lcu.channelUpdate.htlcMaximumMsat == 1000.msat) assert(lcu.channelUpdate.shortChannelId.isInstanceOf[RealShortChannelId]) assert(lcu.channelUpdate.channelFlags.isEnabled) @@ -3584,7 +3584,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val fundingTx = aliceState.commitments.latest.localFundingStatus.signedTx_opt.get val (blockHeight, txIndex) = (BlockHeight(400_000), 42) alice ! WatchFundingConfirmedTriggered(blockHeight, txIndex, fundingTx) - val realShortChannelId = RealShortChannelId(blockHeight, txIndex, alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.commitInput.outPoint.index.toInt) + val realShortChannelId = RealShortChannelId(blockHeight, txIndex, alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.fundingInput.index.toInt) val annSigsA = alice2bob.expectMsgType[AnnouncementSignatures] assert(annSigsA.shortChannelId == realShortChannelId) // Alice updates her internal state wih the real scid. diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/OpenChannelInterceptorSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/OpenChannelInterceptorSpec.scala index c1b644f42a..9313a2669a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/OpenChannelInterceptorSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/OpenChannelInterceptorSpec.scala @@ -134,14 +134,13 @@ class OpenChannelInterceptorSpec extends ScalaTestWithActorTestKit(ConfigFactory val currentChannels = Seq( Peer.ChannelInfo(TestProbe().ref, SHUTDOWN, DATA_SHUTDOWN(commitments(isOpener = true), Shutdown(randomBytes32(), ByteVector.empty), Shutdown(randomBytes32(), ByteVector.empty), CloseStatus.Initiator(None))), Peer.ChannelInfo(TestProbe().ref, NEGOTIATING, DATA_NEGOTIATING(commitments(), Shutdown(randomBytes32(), ByteVector.empty), Shutdown(randomBytes32(), ByteVector.empty), List(Nil), None)), - Peer.ChannelInfo(TestProbe().ref, CLOSING, DATA_CLOSING(commitments(), BlockHeight(0), ByteVector.empty, Nil, ClosingTx(InputInfo(OutPoint(TxId(randomBytes32()), 5), TxOut(100_000 sat, Nil), ByteVector.empty), Transaction(2, Nil, Nil, 0), None) :: Nil)), + Peer.ChannelInfo(TestProbe().ref, CLOSING, DATA_CLOSING(commitments(), BlockHeight(0), ByteVector.empty, Nil, ClosingTx(InputInfo(OutPoint(TxId(randomBytes32()), 5), TxOut(100_000 sat, Nil)), Transaction(2, Nil, Nil, 0), None) :: Nil)), Peer.ChannelInfo(TestProbe().ref, WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT, DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT(commitments(), ChannelReestablish(randomBytes32(), 0, 0, randomKey(), randomKey().publicKey))), ) peer.expectMessageType[Peer.GetPeerChannels].replyTo ! Peer.PeerChannels(remoteNodeId, currentChannels) val result = peer.expectMessageType[SpawnChannelNonInitiator] assert(!result.localParams.isChannelOpener) assert(result.localParams.paysCommitTxFees) - assert(result.localParams.maxHtlcValueInFlightMsat == UInt64(500_000_000)) assert(result.addFunding_opt.map(_.fundingAmount).contains(250_000 sat)) assert(result.addFunding_opt.flatMap(_.rates_opt).contains(TestConstants.defaultLiquidityRates)) } @@ -155,7 +154,6 @@ class OpenChannelInterceptorSpec extends ScalaTestWithActorTestKit(ConfigFactory openChannelInterceptor ! openChannelInitiator val result = peer.expectMessageType[SpawnChannelInitiator] assert(result.cmd == openChannelInitiator.open) - assert(result.localParams.maxHtlcValueInFlightMsat == UInt64(450_000_000)) } test("continue channel open if no interceptor plugin registered and pending channels rate limiter accepts it") { f => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/PendingChannelsRateLimiterSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/PendingChannelsRateLimiterSpec.scala index e4ae7b3b8e..713b399db9 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/PendingChannelsRateLimiterSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/PendingChannelsRateLimiterSpec.scala @@ -73,7 +73,7 @@ class PendingChannelsRateLimiterSpec extends ScalaTestWithActorTestKit(ConfigFac val probe = TestProbe[PendingChannelsRateLimiter.Response]() val nodeParams = TestConstants.Alice.nodeParams.copy(channelConf = TestConstants.Alice.nodeParams.channelConf.copy(maxPendingChannelsPerPeer = maxPendingChannelsPerPeer, maxTotalPendingChannelsPrivateNodes = maxTotalPendingChannelsPrivateNodes, channelOpenerWhitelist = Set(peerOnWhitelistAtLimit))) val tx = Transaction.read("010000000110f01d4a4228ef959681feb1465c2010d0135be88fd598135b2e09d5413bf6f1000000006a473044022074658623424cebdac8290488b76f893cfb17765b7a3805e773e6770b7b17200102202892cfa9dda662d5eac394ba36fcfd1ea6c0b8bb3230ab96220731967bbdb90101210372d437866d9e4ead3d362b01b615d24cc0d5152c740d51e3c55fb53f6d335d82ffffffff01408b0700000000001976a914678db9a7caa2aca887af1177eda6f3d0f702df0d88ac00000000") - val closingTx = ClosingTx(InputInfo(tx.txIn.head.outPoint, TxOut(10_000 sat, Nil), ByteVector.empty), tx, None) + val closingTx = ClosingTx(InputInfo(tx.txIn.head.outPoint, TxOut(10_000 sat, Nil)), tx, None) val channelsOnWhitelistAtLimit: Seq[PersistentChannelData] = Seq( DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments(peerOnWhitelistAtLimit, randomBytes32()), BlockHeight(0), None, Left(FundingCreated(randomBytes32(), TxId(ByteVector32.Zeroes), 3, randomBytes64()))), DATA_WAIT_FOR_CHANNEL_READY(commitments(peerOnWhitelistAtLimit, randomBytes32()), ShortIdAliases(ShortChannelId.generateLocalAlias(), None)), @@ -91,13 +91,13 @@ class PendingChannelsRateLimiterSpec extends ScalaTestWithActorTestKit(ConfigFac ) val channelsBelowLimit2 = Seq( DATA_WAIT_FOR_DUAL_FUNDING_READY(commitments(peerBelowLimit2, channelIdBelowLimit2), ShortIdAliases(ShortChannelId.generateLocalAlias(), None)), - DATA_NORMAL(commitments(peerBelowLimit2, randomBytes32()), ShortIdAliases(ShortChannelId.generateLocalAlias(), None), None, null, None, None, None, SpliceStatus.NoSplice), + DATA_NORMAL(commitments(peerBelowLimit2, randomBytes32()), ShortIdAliases(ShortChannelId.generateLocalAlias(), None), None, null, SpliceStatus.NoSplice, None, None, None), DATA_SHUTDOWN(commitments(peerBelowLimit2, randomBytes32()), Shutdown(randomBytes32(), ByteVector.empty), Shutdown(randomBytes32(), ByteVector.empty), CloseStatus.Initiator(None)), DATA_CLOSING(commitments(peerBelowLimit2, randomBytes32()), BlockHeight(0), ByteVector.empty, List(), List(closingTx)) ) val privateChannels = Seq( DATA_WAIT_FOR_DUAL_FUNDING_READY(commitments(privatePeer1, channelIdPrivate1), ShortIdAliases(ShortChannelId.generateLocalAlias(), None)), - DATA_NORMAL(commitments(privatePeer2, randomBytes32()), ShortIdAliases(ShortChannelId.generateLocalAlias(), None), None, null, None, None, None, SpliceStatus.NoSplice), + DATA_NORMAL(commitments(privatePeer2, randomBytes32()), ShortIdAliases(ShortChannelId.generateLocalAlias(), None), None, null, SpliceStatus.NoSplice, None, None, None), ) val initiatorChannels = Seq( DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments(peerBelowLimit1, randomBytes32(), isOpener = true), BlockHeight(0), None, Left(FundingCreated(channelIdAtLimit1, TxId(ByteVector32.Zeroes), 3, randomBytes64()))), @@ -320,7 +320,7 @@ class PendingChannelsRateLimiterSpec extends ScalaTestWithActorTestKit(ConfigFac val channels = Seq( DATA_WAIT_FOR_CHANNEL_READY(commitments(randomKey().publicKey, randomBytes32()), ShortIdAliases(ShortChannelId.generateLocalAlias(), None)), DATA_WAIT_FOR_DUAL_FUNDING_READY(commitments(randomKey().publicKey, randomBytes32()), ShortIdAliases(ShortChannelId.generateLocalAlias(), None)), - DATA_NORMAL(commitments(randomKey().publicKey, randomBytes32()), ShortIdAliases(ShortChannelId.generateLocalAlias(), None), None, null, None, None, None, SpliceStatus.NoSplice), + DATA_NORMAL(commitments(randomKey().publicKey, randomBytes32()), ShortIdAliases(ShortChannelId.generateLocalAlias(), None), None, null, SpliceStatus.NoSplice, None, None, None), DATA_SHUTDOWN(commitments(randomKey().publicKey, randomBytes32()), Shutdown(randomBytes32(), ByteVector.empty), Shutdown(randomBytes32(), ByteVector.empty), CloseStatus.Initiator(None)), DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments(randomKey().publicKey, randomBytes32()), BlockHeight(0), None, Left(FundingCreated(randomBytes32(), TxId(ByteVector32.Zeroes), 3, randomBytes64()))), ) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/SwitchboardSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/SwitchboardSpec.scala index 8d704c66c4..ab4bdca2ab 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/SwitchboardSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/SwitchboardSpec.scala @@ -166,7 +166,7 @@ class SwitchboardSpec extends TestKitBaseClass with AnyFunSuiteLike { def dummyDataNormal(remoteNodeId: PublicKey, capacity: Satoshi): DATA_NORMAL = { val data = ChannelCodecsSpec.normal .modify(_.commitments.channelParams.remoteParams.nodeId).setTo(remoteNodeId) - .modify(_.commitments.active).apply(_.map(_.modify(_.localCommit.input.txOut.amount).setTo(capacity))) + .modify(_.commitments.active).apply(_.map(_.modify(_.fundingAmount).setTo(capacity))) assert(data.remoteNodeId == remoteNodeId) assert(data.commitments.capacity == capacity) data diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala index b74a56dc07..8f96c7918a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala @@ -25,7 +25,6 @@ import fr.acinq.eclair.balance.CheckBalance import fr.acinq.eclair.balance.CheckBalance.{GlobalBalance, MainAndHtlcBalance} import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.channel.ChannelSpendSignature.IndividualSignature -import fr.acinq.eclair.channel.Helpers.Funding import fr.acinq.eclair.channel._ import fr.acinq.eclair.crypto.{ShaChain, Sphinx} import fr.acinq.eclair.db.OfferData @@ -33,7 +32,7 @@ import fr.acinq.eclair.io.Peer import fr.acinq.eclair.io.Peer.PeerInfo import fr.acinq.eclair.payment.{Invoice, PaymentSettlingOnChain} import fr.acinq.eclair.transactions.Transactions._ -import fr.acinq.eclair.transactions.{CommitmentSpec, IncomingHtlc, OutgoingHtlc} +import fr.acinq.eclair.transactions.{CommitmentSpec, IncomingHtlc, OutgoingHtlc, Transactions} import fr.acinq.eclair.wire.internal.channel.ChannelCodecs import fr.acinq.eclair.wire.protocol.OfferTypes.{Offer, OfferTlv} import fr.acinq.eclair.wire.protocol._ @@ -122,10 +121,12 @@ class JsonSerializersSpec extends TestKitBaseClass with AnyFunSuiteLike with Mat val probe = TestProbe()(system) val dummyPublicKey = PrivateKey(hex"0101010101010101010101010101010101010101010101010101010101010101").publicKey val dummyBytes32 = ByteVector32(hex"0202020202020202020202020202020202020202020202020202020202020202") - val localChannelParams = LocalChannelParams(dummyPublicKey, DeterministicWallet.KeyPath(Seq(42L)), 546 sat, UInt64(Long.MaxValue), Some(1000 sat), 1 msat, CltvExpiryDelta(144), 50, isChannelOpener = true, paysCommitTxFees = true, None, None, Features.empty) - val remoteChannelParams = RemoteChannelParams(dummyPublicKey, 546 sat, UInt64.MaxValue, Some(1000 sat), 1 msat, CltvExpiryDelta(144), 50, dummyPublicKey, dummyPublicKey, dummyPublicKey, dummyPublicKey, Features.empty, None) - val commitmentInput = Funding.makeFundingInputInfo(TxId(dummyBytes32), 0, 150_000 sat, dummyPublicKey, dummyPublicKey, DefaultCommitmentFormat) - val localCommit = LocalCommit(0, CommitmentSpec(Set.empty, FeeratePerKw(2500 sat), 100_000_000 msat, 50_000_000 msat), TxId(dummyBytes32), commitmentInput, IndividualSignature(ByteVector64.Zeroes), Nil) + val localChannelParams = LocalChannelParams(dummyPublicKey, DeterministicWallet.KeyPath(Seq(42L)), Some(1000 sat), isChannelOpener = true, paysCommitTxFees = true, None, None, Features.empty) + val localCommitParams = CommitParams(546 sat, 1 msat, UInt64(Long.MaxValue), 50, CltvExpiryDelta(144)) + val remoteChannelParams = RemoteChannelParams(dummyPublicKey, Some(1000 sat), dummyPublicKey, dummyPublicKey, dummyPublicKey, dummyPublicKey, Features.empty, None) + val remoteCommitParams = CommitParams(546 sat, 1 msat, UInt64.MaxValue, 50, CltvExpiryDelta(144)) + val commitmentInput = Transactions.makeFundingInputInfo(TxId(dummyBytes32), 0, 150_000 sat, dummyPublicKey, dummyPublicKey, DefaultCommitmentFormat) + val localCommit = LocalCommit(0, CommitmentSpec(Set.empty, FeeratePerKw(2500 sat), 100_000_000 msat, 50_000_000 msat), TxId(dummyBytes32), IndividualSignature(ByteVector64.Zeroes), Nil) val remoteCommit = RemoteCommit(0, CommitmentSpec(Set.empty, FeeratePerKw(2500 sat), 50_000_000 msat, 100_000_000 msat), TxId(dummyBytes32), dummyPublicKey) val channelInfo = RES_GET_CHANNEL_INFO( PublicKey(hex"0270685ca81a8e4d4d01beec5781f4cc924684072ae52c507f8ebe9daf0caaab7b"), @@ -136,7 +137,7 @@ class JsonSerializersSpec extends TestKitBaseClass with AnyFunSuiteLike with Mat Commitments( ChannelParams(dummyBytes32, ChannelConfig.standard, ChannelFeatures(), localChannelParams, remoteChannelParams, ChannelFlags(announceChannel = true)), CommitmentChanges(LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), localNextHtlcId = 1, remoteNextHtlcId = 1), - List(Commitment(0, 0, dummyPublicKey, LocalFundingStatus.SingleFundedUnconfirmedFundingTx(None), RemoteFundingStatus.Locked, localCommit, remoteCommit, None)), + List(Commitment(0, 0, commitmentInput.outPoint, 150_000 sat, dummyPublicKey, LocalFundingStatus.SingleFundedUnconfirmedFundingTx(None), RemoteFundingStatus.Locked, DefaultCommitmentFormat, localCommitParams, localCommit, remoteCommitParams, remoteCommit, None)), inactive = Nil, Right(dummyPublicKey), ShaChain.init, @@ -145,7 +146,7 @@ class JsonSerializersSpec extends TestKitBaseClass with AnyFunSuiteLike with Mat ShortIdAliases(Alias(42), None), None, ChannelUpdate(ByteVector64(hex"345b2b05ec046ffe0c14d3b61838c79980713ad1cf8ae7a45c172ce90c9c0b9f345b2b05ec046ffe0c14d3b61838c79980713ad1cf8ae7a45c172ce90c9c0b9f"), Block.RegtestGenesisBlock.hash, ShortChannelId(0), 0 unixsec, ChannelUpdate.MessageFlags(dontForward = false), ChannelUpdate.ChannelFlags.DUMMY, CltvExpiryDelta(12), 1 msat, 100 msat, 0, 2_000_000 msat), - None, None, None, SpliceStatus.NoSplice + SpliceStatus.NoSplice, None, None, None ) ) val expected = @@ -163,24 +164,14 @@ class JsonSerializersSpec extends TestKitBaseClass with AnyFunSuiteLike with Mat | "localParams": { | "nodeId": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", | "fundingKeyPath": [42], - | "dustLimit": 546, - | "maxHtlcValueInFlightMsat": 9223372036854775807, | "initialRequestedChannelReserve_opt": 1000, - | "htlcMinimum": 1, - | "toRemoteDelay": 144, - | "maxAcceptedHtlcs": 50, | "isChannelOpener": true, | "paysCommitTxFees" : true, | "initFeatures": { "activated": {}, "unknown": [] } | }, | "remoteParams": { | "nodeId": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", - | "dustLimit": 546, - | "maxHtlcValueInFlightMsat": 18446744073709551615, | "initialRequestedChannelReserve_opt": 1000, - | "htlcMinimum": 1, - | "toRemoteDelay": 144, - | "maxAcceptedHtlcs": 50, | "revocationBasepoint": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", | "paymentBasepoint": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", | "delayedPaymentBasepoint": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", @@ -201,22 +192,34 @@ class JsonSerializersSpec extends TestKitBaseClass with AnyFunSuiteLike with Mat | "active": [ | { | "fundingTxIndex": 0, - | "fundingTx": { "outPoint": "0202020202020202020202020202020202020202020202020202020202020202:0", "amountSatoshis": 150000 }, + | "fundingInput": "0202020202020202020202020202020202020202020202020202020202020202:0", + | "fundingAmount": 150000, | "localFunding": { "status":"unconfirmed" }, | "remoteFunding": { "status":"locked" }, + | "commitmentFormat": "legacy", + | "localCommitParams": { + | "dustLimit": 546, + | "htlcMinimum": 1, + | "maxHtlcValueInFlight": 9223372036854775807, + | "maxAcceptedHtlcs": 50, + | "toSelfDelay": 144 + | }, | "localCommit": { | "index": 0, | "spec": { "htlcs": [], "commitTxFeerate": 2500, "toLocal": 100000000, "toRemote": 50000000 }, | "txId": "0202020202020202020202020202020202020202020202020202020202020202", - | "input": { - | "outPoint":"0202020202020202020202020202020202020202020202020202020202020202:0", - | "amountSatoshis": 150000 - | }, | "remoteSig": { | "sig": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" | }, | "htlcRemoteSigs": [] | }, + | "remoteCommitParams": { + | "dustLimit": 546, + | "htlcMinimum": 1, + | "maxHtlcValueInFlight": 18446744073709551615, + | "maxAcceptedHtlcs": 50, + | "toSelfDelay": 144 + | }, | "remoteCommit": { | "index": 0, | "spec": { "htlcs": [], "commitTxFeerate": 2500, "toLocal": 50000000, "toRemote": 100000000 }, @@ -296,7 +299,6 @@ class JsonSerializersSpec extends TestKitBaseClass with AnyFunSuiteLike with Mat val inputInfo = InputInfo( outPoint = OutPoint(TxHash.fromValidHex("345b2b05ec046ffe0c14d3b61838c79980713ad1cf8ae7a45c172ce90c9c0b9f"), 42), txOut = TxOut(456651 sat, hex"3c7a66997c681a3de1bae56438abeee4fc50a16554725a430ade1dc8db6bdd76704d45c6151c4051d710cf487e63"), - unusedRedeemScript = ByteVector.empty, ) JsonSerializers.serialization.write(inputInfo)(JsonSerializers.formats) shouldBe """{"outPoint":"9f0b9c0ce92c175ca4e78acfd13a718099c73818b6d3140cfe6f04ec052b5b34:42","amountSatoshis":456651}""" } @@ -413,7 +415,7 @@ class JsonSerializersSpec extends TestKitBaseClass with AnyFunSuiteLike with Mat test("TransactionWithInputInfo serializer") { // the input info is ignored when serializing to JSON - val dummyInputInfo = InputInfo(OutPoint(TxId(ByteVector32.Zeroes), 0), TxOut(Satoshi(0), Nil), ByteVector.empty) + val dummyInputInfo = InputInfo(OutPoint(TxId(ByteVector32.Zeroes), 0), TxOut(Satoshi(0), Nil)) val htlcSuccessTx = Transaction.read("0200000001c8a8934fb38a44b969528252bc37be66ee166c7897c57384d1e561449e110c93010000006b483045022100dc6c50f445ed53d2fb41067fdcb25686fe79492d90e6e5db43235726ace247210220773d35228af0800c257970bee9cf75175d75217de09a8ecd83521befd040c4ca012102082b751372fe7e3b012534afe0bb8d1f2f09c724b1a10a813ce704e5b9c217ccfdffffff0247ba2300000000001976a914f97a7641228e6b17d4b0b08252ae75bd62a95fe788ace3de24000000000017a914a9fefd4b9a9282a1d7a17d2f14ac7d1eb88141d287f7d50800") val htlcSuccessTxInfo = UnsignedHtlcSuccessTx(dummyInputInfo, htlcSuccessTx, ByteVector32.One, 3, CltvExpiry(1105), ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentPacketSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentPacketSpec.scala index f9c9c77bbd..03269bf14e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentPacketSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentPacketSpec.scala @@ -35,7 +35,6 @@ import fr.acinq.eclair.reputation.Reputation import fr.acinq.eclair.router.BaseRouterSpec.{blindedRouteFromHops, channelHopFromUpdate} import fr.acinq.eclair.router.BlindedRouteCreation import fr.acinq.eclair.router.Router.{NodeHop, Route} -import fr.acinq.eclair.transactions.Transactions.InputInfo import fr.acinq.eclair.wire.protocol.OfferTypes.{InvoiceRequest, Offer, PaymentInfo} import fr.acinq.eclair.wire.protocol.PaymentOnion.{FinalPayload, IntermediatePayload, OutgoingBlindedPerHopPayload} import fr.acinq.eclair.wire.protocol._ @@ -745,23 +744,23 @@ object PaymentPacketSpec { def makeCommitments(channelId: ByteVector32, testAvailableBalanceForSend: MilliSatoshi = 50000000 msat, testAvailableBalanceForReceive: MilliSatoshi = 50000000 msat, testCapacity: Satoshi = 100000 sat, channelFeatures: ChannelFeatures = ChannelFeatures(), announcement_opt: Option[ChannelAnnouncement] = None): Commitments = { val channelReserve = testCapacity * 0.01 - val localChannelParams = LocalChannelParams(null, null, null, UInt64.MaxValue, Some(channelReserve), null, null, 0, isChannelOpener = true, paysCommitTxFees = true, None, None, Features.empty) - val remoteChannelParams = RemoteChannelParams(randomKey().publicKey, null, UInt64.MaxValue, Some(channelReserve), null, null, maxAcceptedHtlcs = 0, null, null, null, null, null, None) + val localChannelParams = LocalChannelParams(null, null, Some(channelReserve), isChannelOpener = true, paysCommitTxFees = true, None, None, Features.empty) + val remoteChannelParams = RemoteChannelParams(randomKey().publicKey, Some(channelReserve), null, null, null, null, null, None) + val commitParams = CommitParams(546 sat, 1 msat, UInt64.MaxValue, 30, CltvExpiryDelta(720)) val fundingTx = Transaction(2, Nil, Seq(TxOut(testCapacity, Nil)), 0) - val commitInput = InputInfo(OutPoint(fundingTx, 0), fundingTx.txOut.head, ByteVector.empty) - val localCommit = LocalCommit(0, null, randomTxId(), commitInput, IndividualSignature(ByteVector64.Zeroes), Nil) + val localCommit = LocalCommit(0, null, randomTxId(), IndividualSignature(ByteVector64.Zeroes), Nil) val remoteCommit = RemoteCommit(0, null, randomTxId(), randomKey().publicKey) val localChanges = LocalChanges(Nil, Nil, Nil) val remoteChanges = RemoteChanges(Nil, Nil, Nil) val localFundingStatus = announcement_opt match { - case Some(ann) => LocalFundingStatus.ConfirmedFundingTx(fundingTx, ann.shortChannelId, None, None) + case Some(ann) => LocalFundingStatus.ConfirmedFundingTx(fundingTx.txOut.head, ann.shortChannelId, None, None) case None => LocalFundingStatus.SingleFundedUnconfirmedFundingTx(None) } val channelFlags = ChannelFlags(announceChannel = announcement_opt.nonEmpty) new Commitments( ChannelParams(channelId, ChannelConfig.standard, channelFeatures, localChannelParams, remoteChannelParams, channelFlags), CommitmentChanges(localChanges, remoteChanges, 0, 0), - List(Commitment(0, 0, null, localFundingStatus, RemoteFundingStatus.Locked, localCommit, remoteCommit, None)), + List(Commitment(0, 0, OutPoint(fundingTx, 0), testCapacity, randomKey().publicKey, localFundingStatus, RemoteFundingStatus.Locked, channelFeatures.commitmentFormat, commitParams, localCommit, commitParams, remoteCommit, None)), inactive = Nil, Right(randomKey().publicKey), ShaChain.init, diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PostRestartHtlcCleanerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PostRestartHtlcCleanerSpec.scala index d209eb70ab..fe15c42619 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PostRestartHtlcCleanerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PostRestartHtlcCleanerSpec.scala @@ -511,7 +511,7 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit val normal = ChannelCodecsSpec.makeChannelDataNormal(htlc_bc, origins) // NB: this isn't actually a revoked commit tx, but we don't check that here, if the channel says it's a revoked // commit we accept it as such, so it simplifies the test. - val revokedCommitTx = Transaction(2, Seq(TxIn(normal.commitments.latest.localCommit.input.outPoint, Nil, 0)), Seq(TxOut(4500 sat, Script.pay2wpkh(randomKey().publicKey))), 0) + val revokedCommitTx = Transaction(2, Seq(TxIn(normal.commitments.latest.fundingInput, Nil, 0)), Seq(TxOut(4500 sat, Script.pay2wpkh(randomKey().publicKey))), 0) val rcp = RevokedCommitPublished(revokedCommitTx, Some(OutPoint(revokedCommitTx, 0)), None, Set.empty, Set.empty, Map(revokedCommitTx.txIn.head.outPoint -> revokedCommitTx)) DATA_CLOSING(normal.commitments, BlockHeight(0), Script.write(Script.pay2wpkh(randomKey().publicKey)), mutualCloseProposed = Nil, revokedCommitPublished = List(rcp)) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/OnTheFlyFundingSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/OnTheFlyFundingSpec.scala index f384a82ce8..00569b880e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/OnTheFlyFundingSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/OnTheFlyFundingSpec.scala @@ -166,9 +166,9 @@ class OnTheFlyFundingSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { def makeChannelData(htlcMinimum: MilliSatoshi = 1 msat, localChanges: LocalChanges = LocalChanges(Nil, Nil, Nil)): DATA_NORMAL = { val commitments = CommitmentsSpec.makeCommitments(500_000_000 msat, 500_000_000 msat, nodeParams.nodeId, remoteNodeId, announcement_opt = None) - .modify(_.channelParams.remoteParams.htlcMinimum).setTo(htlcMinimum) + .modify(_.active).apply(_.map(_.modify(_.remoteCommitParams.htlcMinimum).setTo(htlcMinimum))) .modify(_.changes.localChanges).setTo(localChanges) - DATA_NORMAL(commitments, ShortIdAliases(Alias(42), None), None, null, None, None, None, SpliceStatus.NoSplice) + DATA_NORMAL(commitments, ShortIdAliases(Alias(42), None), None, null, SpliceStatus.NoSplice, None, None, None) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala index 2856f0f48c..db201cebb8 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala @@ -1102,7 +1102,7 @@ class RouterSpec extends BaseRouterSpec { } // The second channel is announced and moves from the private channels to the public channels. - val fundingConfirmed = LocalFundingStatus.ConfirmedFundingTx(Transaction(2, Nil, TxOut(100_000 sat, Nil) :: Nil, 0), scid2, None, None) + val fundingConfirmed = LocalFundingStatus.ConfirmedFundingTx(TxOut(100_000 sat, Nil), scid2, None, None) val commitments3 = commitments2.updateLocalFundingStatus(commitments2.latest.fundingTxId, fundingConfirmed, None)(akka.event.NoLogging).toOption.get._1 assert(commitments3.channelId == commitments2.channelId) sender.send(router, LocalChannelUpdate(sender.ref, commitments3.channelId, aliases2, x.publicKey, Some(AnnouncedCommitment(commitments3.latest.commitment, announcement2)), update2, commitments3)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala index 1d2ad03c59..38297eb98f 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala @@ -21,7 +21,6 @@ import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.scalacompat.{ByteVector32, Crypto, Satoshi, SatoshiLong, Script, Transaction} import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.channel.ChannelFeatures -import fr.acinq.eclair.channel.Helpers.Funding import fr.acinq.eclair.crypto.keymanager.{ChannelKeys, LocalCommitmentKeys, RemoteCommitmentKeys} import fr.acinq.eclair.reputation.Reputation import fr.acinq.eclair.transactions.Transactions._ @@ -144,8 +143,8 @@ trait TestVectorsSpec extends AnyFunSuite with Logging { val fundingAmount = fundingTx.txOut(0).amount logger.info(s"# funding-tx: $fundingTx}") - val fundingScript = Funding.makeFundingScript(Local.funding_pubkey, Remote.funding_pubkey, commitmentFormat).asInstanceOf[RedeemInfo.SegwitV0] - val commitmentInput = Funding.makeFundingInputInfo(fundingTx.txid, 0, fundingAmount, Local.funding_pubkey, Remote.funding_pubkey, commitmentFormat) + val fundingScript = makeFundingScript(Local.funding_pubkey, Remote.funding_pubkey, commitmentFormat).asInstanceOf[RedeemInfo.SegwitV0] + val commitmentInput = makeFundingInputInfo(fundingTx.txid, 0, fundingAmount, Local.funding_pubkey, Remote.funding_pubkey, commitmentFormat) val obscured_tx_number = Transactions.obscuredCommitTxNumber(42, localIsChannelOpener = true, Local.payment_basepoint, Remote.payment_basepoint) assert(obscured_tx_number == (0x2bb038521914L ^ 42L)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala index d8ee07c2cc..b56570e47c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala @@ -24,7 +24,6 @@ import fr.acinq.eclair.TestUtils.randomTxId import fr.acinq.eclair._ import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.channel.ChannelSpendSignature -import fr.acinq.eclair.channel.Helpers.Funding import fr.acinq.eclair.crypto.keymanager.{LocalCommitmentKeys, RemoteCommitmentKeys} import fr.acinq.eclair.reputation.Reputation import fr.acinq.eclair.transactions.CommitmentOutput.OutHtlc @@ -251,10 +250,10 @@ class TransactionsSpec extends AnyFunSuite with Logging { val walletPriv = randomKey() val walletPub = walletPriv.publicKey val finalPubKeyScript = Script.write(Script.pay2wpkh(walletPub)) - val fundingInfo = Funding.makeFundingScript(localFundingPriv.publicKey, remoteFundingPriv.publicKey, commitmentFormat) + val fundingInfo = makeFundingScript(localFundingPriv.publicKey, remoteFundingPriv.publicKey, commitmentFormat) val fundingTx = Transaction(version = 2, txIn = Nil, txOut = TxOut(Btc(1), fundingInfo.pubkeyScript) :: Nil, lockTime = 0) val fundingTxOutpoint = OutPoint(fundingTx.txid, 0) - val commitInput = Funding.makeFundingInputInfo(fundingTxOutpoint.txid, fundingTxOutpoint.index.toInt, Btc(1), localFundingPriv.publicKey, remoteFundingPriv.publicKey, commitmentFormat) + val commitInput = makeFundingInputInfo(fundingTxOutpoint.txid, fundingTxOutpoint.index.toInt, Btc(1), localFundingPriv.publicKey, remoteFundingPriv.publicKey, commitmentFormat) val paymentPreimages = Seq(randomBytes32(), randomBytes32(), randomBytes32(), randomBytes32(), randomBytes32(), randomBytes32(), randomBytes32(), randomBytes32()) val paymentPreimageMap = paymentPreimages.map(p => sha256(p) -> p).toMap @@ -587,7 +586,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { theirHtlcPublicKey = remoteHtlcPriv.publicKey, revocationPublicKey = localRevocationPriv.publicKey, ) - val commitInput = Funding.makeFundingInputInfo(TxId.fromValidHex("a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0"), 0, Btc(1), localFundingPriv.publicKey, remoteFundingPriv.publicKey, DefaultCommitmentFormat) + val commitInput = makeFundingInputInfo(TxId.fromValidHex("a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0"), 0, Btc(1), localFundingPriv.publicKey, remoteFundingPriv.publicKey, DefaultCommitmentFormat) // htlc1 and htlc2 are two regular incoming HTLCs with different amounts. // htlc2 and htlc3 have the same amounts and should be sorted according to their scriptPubKey @@ -648,7 +647,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { } test("find our output in closing tx") { - val commitInput = Funding.makeFundingInputInfo(randomTxId(), 0, Btc(1), localFundingPriv.publicKey, remoteFundingPriv.publicKey, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) + val commitInput = makeFundingInputInfo(randomTxId(), 0, Btc(1), localFundingPriv.publicKey, remoteFundingPriv.publicKey, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) val localPubKeyScript = Script.write(Script.pay2wpkh(PrivateKey(randomBytes32()).publicKey)) val remotePubKeyScript = Script.write(Script.pay2wpkh(PrivateKey(randomBytes32()).publicKey)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala index ba6d64e1a4..b34f084a56 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala @@ -17,11 +17,10 @@ package fr.acinq.eclair.wire.internal.channel import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} -import fr.acinq.bitcoin.scalacompat.{Block, BlockHash, ByteVector32, ByteVector64, Crypto, DeterministicWallet, Satoshi, SatoshiLong, Transaction, TxId} +import fr.acinq.bitcoin.scalacompat.{Block, BlockHash, ByteVector32, ByteVector64, Crypto, DeterministicWallet, OutPoint, SatoshiLong, Transaction, TxId} import fr.acinq.eclair._ import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.channel.ChannelSpendSignature.IndividualSignature -import fr.acinq.eclair.channel.Helpers.Funding import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.fsm.Channel import fr.acinq.eclair.crypto.ShaChain @@ -91,7 +90,7 @@ class ChannelCodecsSpec extends AnyFunSuite { // and re-encode it with the new codec val bin_new = ByteVector(channelDataCodec.encode(data_new).require.toByteVector.toArray) // data should now be encoded under the new format - assert(bin_new.startsWith(hex"04000a")) + assert(bin_new.startsWith(hex"05000101")) // now let's decode it again val data_new2 = channelDataCodec.decode(bin_new.toBitVector).require.value // data should match perfectly @@ -175,7 +174,7 @@ class ChannelCodecsSpec extends AnyFunSuite { // and we encode with the new codec val newBin = channelDataCodec.encode(decoded1).require.bytes // make sure that encoding used the new codec - assert(newBin.startsWith(hex"0400")) + assert(newBin.startsWith(hex"0500")) // make sure that round-trip yields the same data val decoded2 = channelDataCodec.decode(newBin.bits).require.value assert(decoded1 == decoded2) @@ -241,7 +240,8 @@ class ChannelCodecsSpec extends AnyFunSuite { val remoteSig = newnormal.commitments.latest.localCommit.remoteSig.asInstanceOf[ChannelSpendSignature.IndividualSignature] val commitTxId = newnormal.commitments.latest.localCommit.txId assert(testCase.commitTx.txid == commitTxId) - val commitTx = CommitTx(newnormal.commitments.latest.localCommit.input, testCase.commitTx) + val commitInput = Transactions.makeFundingInputInfo(newnormal.commitments.latest.fundingTxId, newnormal.commitments.latest.fundingInput.index.toInt, newnormal.commitments.latest.capacity, testCase.localFundingPublicKey, testCase.remoteFundingPublicKey, newnormal.commitments.latest.commitmentFormat) + val commitTx = CommitTx(commitInput, testCase.commitTx) assert(commitTx.checkRemoteSig(testCase.localFundingPublicKey, testCase.remoteFundingPublicKey, remoteSig)) } } @@ -255,12 +255,7 @@ object ChannelCodecsSpec { val localChannelParams: LocalChannelParams = LocalChannelParams( nodeKeyManager.nodeId, fundingKeyPath = DeterministicWallet.KeyPath(Seq(42L)), - dustLimit = Satoshi(546), - maxHtlcValueInFlightMsat = UInt64(50_000_000), initialRequestedChannelReserve_opt = Some(10000 sat), - htlcMinimum = 10000 msat, - toRemoteDelay = CltvExpiryDelta(144), - maxAcceptedHtlcs = 50, upfrontShutdownScript_opt = None, walletStaticPaymentBasepoint = None, isChannelOpener = true, @@ -268,12 +263,7 @@ object ChannelCodecsSpec { initFeatures = Features.empty) val remoteChannelParams: RemoteChannelParams = RemoteChannelParams( nodeId = randomKey().publicKey, - dustLimit = 546 sat, - maxHtlcValueInFlightMsat = UInt64(5000000), initialRequestedChannelReserve_opt = Some(10000 sat), - htlcMinimum = 5000 msat, - toRemoteDelay = CltvExpiryDelta(144), - maxAcceptedHtlcs = 50, revocationBasepoint = PrivateKey(ByteVector.fill(32)(2)).publicKey, paymentBasepoint = PrivateKey(ByteVector.fill(32)(3)).publicKey, delayedPaymentBasepoint = PrivateKey(ByteVector.fill(32)(4)).publicKey, @@ -310,22 +300,22 @@ object ChannelCodecsSpec { val fundingTx = Transaction.read("0200000001adbb20ea41a8423ea937e76e8151636bf6093b70eaff942930d20576600521fd000000006b48304502210090587b6201e166ad6af0227d3036a9454223d49a1f11839c1a362184340ef0240220577f7cd5cca78719405cbf1de7414ac027f0239ef6e214c90fcaab0454d84b3b012103535b32d5eb0a6ed0982a0479bbadc9868d9836f6ba94dd5a63be16d875069184ffffffff028096980000000000220020c015c4a6be010e21657068fc2e6a9d02b27ebe4d490a25846f7237f104d1a3cd20256d29010000001600143ca33c2e4446f4a305f23c80df8ad1afdcf652f900000000") val fundingAmount = fundingTx.txOut.head.amount val fundingTxIndex = 0 - val localFundingPubKey = channelKeyManager.channelKeys(ChannelConfig.standard, localChannelParams.fundingKeyPath).fundingKey(fundingTxIndex = 0).publicKey val remoteFundingPubKey = PrivateKey(ByteVector32(ByteVector.fill(32)(1)) :+ 1.toByte).publicKey - val commitmentInput = Funding.makeFundingInputInfo(fundingTx.txid, 0, fundingAmount, localFundingPubKey, remoteFundingPubKey, DefaultCommitmentFormat) val remoteSig = ByteVector64(hex"2148d2d4aac8c793eb82d31bcf22d4db707b9fd7eee1b89b4b1444c9e19ab7172bab8c3d997d29163fa0cb255c75afb8ade13617ad1350c1515e9be4a222a04d") - val localCommit = LocalCommit(0, CommitmentSpec(htlcs.toSet, FeeratePerKw(1500 sat), 50000000 msat, 70000000 msat), TxId.fromValidHex("2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a"), commitmentInput, IndividualSignature(remoteSig), Nil) + val localCommitParams = CommitParams(546 sat, 10_000 msat, UInt64(50_000_000), 50, CltvExpiryDelta(144)) + val localCommit = LocalCommit(0, CommitmentSpec(htlcs.toSet, FeeratePerKw(1500 sat), 50000000 msat, 70000000 msat), TxId.fromValidHex("2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a"), IndividualSignature(remoteSig), Nil) + val remoteCommitParams = CommitParams(546 sat, 5_000 msat, UInt64(5_000_000), 50, CltvExpiryDelta(144)) val remoteCommit = RemoteCommit(0, CommitmentSpec(htlcs.map(_.opposite).toSet, FeeratePerKw(1500 sat), 50000 msat, 700000 msat), TxId.fromValidHex("0303030303030303030303030303030303030303030303030303030303030303"), PrivateKey(ByteVector.fill(32)(4)).publicKey) val channelId = htlcs.headOption.map(_.add.channelId).getOrElse(ByteVector32.Zeroes) val channelFlags = ChannelFlags(announceChannel = true) val commitments = Commitments( ChannelParams(channelId, ChannelConfig.standard, ChannelFeatures(), localChannelParams, remoteChannelParams, channelFlags), CommitmentChanges(LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), localNextHtlcId = 32, remoteNextHtlcId = 4), - Seq(Commitment(fundingTxIndex, 0, remoteFundingPubKey, LocalFundingStatus.SingleFundedUnconfirmedFundingTx(None), RemoteFundingStatus.NotLocked, localCommit, remoteCommit, None)), + Seq(Commitment(fundingTxIndex, 0, OutPoint(fundingTx.txid, 0), fundingAmount, remoteFundingPubKey, LocalFundingStatus.SingleFundedUnconfirmedFundingTx(None), RemoteFundingStatus.NotLocked, DefaultCommitmentFormat, localCommitParams, localCommit, remoteCommitParams, remoteCommit, None)), remoteNextCommitInfo = Right(randomKey().publicKey), remotePerCommitmentSecrets = ShaChain.init, originChannels = origins) - DATA_NORMAL(commitments, ShortIdAliases(ShortChannelId.generateLocalAlias(), None), None, channelUpdate, None, None, None, SpliceStatus.NoSplice) + DATA_NORMAL(commitments, ShortIdAliases(ShortChannelId.generateLocalAlias(), None), None, channelUpdate, SpliceStatus.NoSplice, None, None, None) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4Spec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4Spec.scala index 3c7851e030..58b7829f7e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4Spec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4Spec.scala @@ -1,22 +1,17 @@ package fr.acinq.eclair.wire.internal.channel.version4 -import com.softwaremill.quicklens.ModifyPimp import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, DeterministicWallet, OutPoint, Satoshi, SatoshiLong, Script, Transaction, TxId, TxOut} -import fr.acinq.eclair.FeatureSupport.{Mandatory, Optional} -import fr.acinq.eclair.Features.{ChannelRangeQueries, PaymentSecret, VariableLengthOnion} -import fr.acinq.eclair.TestUtils.randomTxId import fr.acinq.eclair.blockchain.fee.{FeeratePerByte, FeeratePerKw} import fr.acinq.eclair.channel._ +import fr.acinq.eclair.channel.fund.InteractiveTxBuilder import fr.acinq.eclair.channel.fund.InteractiveTxBuilder.{InteractiveTxParams, PartiallySignedSharedTransaction, RequireConfirmedInputs, SharedTransaction} -import fr.acinq.eclair.channel.fund.InteractiveTxSigningSession.UnsignedLocalCommit -import fr.acinq.eclair.channel.fund.{InteractiveTxBuilder, InteractiveTxSigningSession} +import fr.acinq.eclair.transactions.Scripts import fr.acinq.eclair.transactions.Transactions._ -import fr.acinq.eclair.transactions.{CommitmentSpec, Scripts} -import fr.acinq.eclair.wire.internal.channel.ChannelCodecsSpec.normal +import fr.acinq.eclair.wire.internal.channel.version0.ChannelTypes0 import fr.acinq.eclair.wire.internal.channel.version4.ChannelCodecs4.Codecs._ import fr.acinq.eclair.wire.internal.channel.version4.ChannelCodecs4.channelDataCodec -import fr.acinq.eclair.wire.protocol.{LiquidityAds, TxSignatures} +import fr.acinq.eclair.wire.protocol.TxSignatures import fr.acinq.eclair.{BlockHeight, CltvExpiryDelta, Features, MilliSatoshi, MilliSatoshiLong, UInt64, randomBytes32, randomKey} import org.scalatest.funsuite.AnyFunSuite import scodec.bits._ @@ -25,14 +20,6 @@ import scala.util.Random class ChannelCodecs4Spec extends AnyFunSuite { - test("basic serialization test (NORMAL)") { - val data = normal - val bin = channelDataCodec.encode(data).require - val check = channelDataCodec.decodeValue(bin).require.asInstanceOf[ChannelDataWithCommitments] - assert(data.commitments.latest.localCommit.spec == check.commitments.latest.localCommit.spec) - assert(data == check) - } - test("encode/decode channel configuration options") { assert(channelConfigCodec.encode(ChannelConfig(Set.empty[ChannelConfigOption])).require.bytes == hex"00") assert(channelConfigCodec.decode(hex"00".bits).require.value == ChannelConfig(Set.empty[ChannelConfigOption])) @@ -47,7 +34,7 @@ class ChannelCodecs4Spec extends AnyFunSuite { } test("encode/decode optional channel reserve") { - val localParams = LocalChannelParams( + val localParams = ChannelTypes0.LocalParams( randomKey().publicKey, DeterministicWallet.KeyPath(Seq(42L)), Satoshi(660), @@ -61,7 +48,7 @@ class ChannelCodecs4Spec extends AnyFunSuite { Some(hex"deadbeef"), None, Features().initFeatures()) - val remoteParams = RemoteChannelParams( + val remoteParams = ChannelTypes4.RemoteParams( randomKey().publicKey, Satoshi(500), UInt64(100000), @@ -94,70 +81,10 @@ class ChannelCodecs4Spec extends AnyFunSuite { } } - test("encode/decode optional shutdown script") { - val codec = remoteParamsCodec(ChannelFeatures()) - val remoteParams = RemoteChannelParams( - randomKey().publicKey, - Satoshi(600), - UInt64(123456L), - Some(Satoshi(300)), - MilliSatoshi(1000), - CltvExpiryDelta(42), - 42, - randomKey().publicKey, - randomKey().publicKey, - randomKey().publicKey, - randomKey().publicKey, - Features(ChannelRangeQueries -> Optional, VariableLengthOnion -> Mandatory, PaymentSecret -> Mandatory), - None) - assert(codec.decodeValue(codec.encode(remoteParams).require).require == remoteParams) - val remoteParams1 = remoteParams.copy(upfrontShutdownScript_opt = Some(ByteVector.fromValidHex("deadbeef"))) - assert(codec.decodeValue(codec.encode(remoteParams1).require).require == remoteParams1) - - val dataWithoutRemoteShutdownScript = normal.modify(_.commitments.channelParams.remoteParams).setTo(remoteParams) - assert(channelDataCodec.decode(channelDataCodec.encode(dataWithoutRemoteShutdownScript).require).require.value == dataWithoutRemoteShutdownScript) - - val dataWithRemoteShutdownScript = normal.modify(_.commitments.channelParams.remoteParams).setTo(remoteParams1) - assert(channelDataCodec.decode(channelDataCodec.encode(dataWithRemoteShutdownScript).require).require.value == dataWithRemoteShutdownScript) - } - - test("encode/decode rbf status") { - val channelId = randomBytes32() - val fundingInput = InputInfo(OutPoint(randomTxId(), 3), TxOut(175_000 sat, Script.pay2wpkh(randomKey().publicKey)), ByteVector.empty) - val fundingTx = SharedTransaction( - sharedInput_opt = None, - sharedOutput = InteractiveTxBuilder.Output.Shared(UInt64(8), ByteVector.empty, 100_000_600 msat, 74_000_400 msat, 0 msat), - localInputs = Nil, remoteInputs = Nil, - localOutputs = Nil, remoteOutputs = Nil, - lockTime = 0 - ) - val waitingForSigs = InteractiveTxSigningSession.WaitingForSigs( - InteractiveTxParams(channelId, isInitiator = true, 100_000 sat, 75_000 sat, None, randomKey().publicKey, Nil, 0, 330 sat, FeeratePerKw(500 sat), RequireConfirmedInputs(forLocal = false, forRemote = false)), - fundingTxIndex = 0, - PartiallySignedSharedTransaction(fundingTx, TxSignatures(channelId, randomTxId(), Nil)), - Left(UnsignedLocalCommit(0, CommitmentSpec(Set.empty, FeeratePerKw(1000 sat), 100_000_000 msat, 75_000_000 msat), randomTxId(), fundingInput)), - RemoteCommit(0, CommitmentSpec(Set.empty, FeeratePerKw(1000 sat), 75_000_000 msat, 100_000_000 msat), randomTxId(), randomKey().publicKey), - Some(LiquidityAds.PurchaseBasicInfo(isBuyer = true, 100_000 sat, LiquidityAds.Fees(1000 sat, 500 sat))), - ) - val testCases = Map( - DualFundingStatus.WaitingForConfirmations -> DualFundingStatus.WaitingForConfirmations, - DualFundingStatus.RbfRequested(CMD_BUMP_FUNDING_FEE(null, FeeratePerKw(750 sat), fundingFeeBudget = 100_000.sat, 0, None)) -> DualFundingStatus.WaitingForConfirmations, - DualFundingStatus.RbfInProgress(None, null, None) -> DualFundingStatus.WaitingForConfirmations, - DualFundingStatus.RbfWaitingForSigs(waitingForSigs) -> DualFundingStatus.RbfWaitingForSigs(waitingForSigs), - DualFundingStatus.RbfWaitingForSigs(waitingForSigs.copy(liquidityPurchase_opt = None)) -> DualFundingStatus.RbfWaitingForSigs(waitingForSigs.copy(liquidityPurchase_opt = None)), - DualFundingStatus.RbfAborted -> DualFundingStatus.WaitingForConfirmations, - ) - testCases.foreach { case (status, expected) => - val encoded = dualFundingStatusCodec.encode(status).require - val decoded = dualFundingStatusCodec.decode(encoded).require.value - assert(decoded == expected) - } - } - test("decode unconfirmed dual funded") { // data encoded with the previous version of eclair, when Shared.Input did not include a pubkey script val raw = ByteVector.fromValidHex("0x020001ff02000000000000002a2400000000000000000000000000000000000000000000000000000000000000000000000000003039000000000000006400000000000000c8000000000000012c02000000000000002b04deadbeef000000000000006400000000000000c8000000000000012c00000000000000000000000042000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e80000000000000000000000000000000000000000000000000000000000000000ff000000000000006400000000000000c8ff0001240000000000000000000000000000000000000000000000000000000000000000000000002be803000000000000220020eb72e573a9513d982a01f0e6a6b53e92764db81a0c26d2be94c5fc5b69a0db7d475221024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d076621031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f52ae00000000024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f000000000000000000000000014a000002ee0000") - val decoded = fundingTxStatusCodec.decode(raw.bits).require.value.asInstanceOf[LocalFundingStatus.DualFundedUnconfirmedFundingTx] + val decoded = fundingTxStatusCodec.decode(raw.bits).require.value.migrate(ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) // check that our codec will set the pubkeyscript using the one from the funding params val channelId = ByteVector32.Zeroes @@ -175,90 +102,91 @@ class ChannelCodecs4Spec extends AnyFunSuite { createdAt = BlockHeight(1000), fundingParams = InteractiveTxParams(channelId = channelId, isInitiator = true, localContribution = 100.sat, remoteContribution = 200.sat, sharedInput_opt = Some(InteractiveTxBuilder.Multisig2of2Input( - InputInfo(OutPoint(TxId(ByteVector32.Zeroes), 0), TxOut(1000.sat, Script.pay2wsh(script)), Script.write(script)), + InputInfo(OutPoint(TxId(ByteVector32.Zeroes), 0), TxOut(1000.sat, Script.pay2wsh(script))), 0, PrivateKey(ByteVector.fromValidHex("02" * 32)).publicKey )), remoteFundingPubKey = PrivateKey(ByteVector.fromValidHex("01" * 32)).publicKey, - localOutputs = Nil, lockTime = 0, dustLimit = 330.sat, targetFeerate = FeeratePerKw(FeeratePerByte(3.sat)), requireConfirmedInputs = RequireConfirmedInputs(forLocal = false, forRemote = false)), + localOutputs = Nil, + commitmentFormat = ZeroFeeHtlcTxAnchorOutputsCommitmentFormat, + lockTime = 0, dustLimit = 330.sat, targetFeerate = FeeratePerKw(FeeratePerByte(3.sat)), requireConfirmedInputs = RequireConfirmedInputs(forLocal = false, forRemote = false)), liquidityPurchase_opt = None ) assert(decoded == dualFundedUnconfirmedFundingTx) - - val dualFundedUnconfirmedFundingTx1 = dualFundedUnconfirmedFundingTx.copy( - liquidityPurchase_opt = Some(LiquidityAds.PurchaseBasicInfo(isBuyer = true, 250_000 sat, LiquidityAds.Fees(1500 sat, 700 sat))) - ) - assert(fundingTxStatusCodec.decode(fundingTxStatusCodec.encode(dualFundedUnconfirmedFundingTx1).require).require.value == dualFundedUnconfirmedFundingTx1) } test("decode dual-funded unsigned local commit") { val bin = ByteVector.fromValidHex("00130d411da7b37693960ef0a495ffa8c0fcbf82c1fd8034ab3bf37f8a184242b7230101041000100002aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa0009f22f74ef88d63eb6ece1076957feb94a7cf7f20b07f3ee710a9f2ac024d3872180000001000000000000044c000000001dcd6500000000000000000000900064c0000392a15254566846b06ea693dbeedb1a6ded24b02d53ee40f091f628dc117a1a5d0000186b02000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000001808220a598202bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e6300000000000003e8000000003b9aca0000000000000003e80090001e021ef6e2ee1a5d15aba3c663f1b608ed66315fa8a855b91f53eefa8debec663b5e0392a15254566846b06ea693dbeedb1a6ded24b02d53ee40f091f628dc117a1a5d0289365d47702d068083d9444c9167be1addfe353d9466a9805215c89361aaf85903e60c20e9744d7a3ff07a72c0170569a9841ea2fb6e0dac7d90ee550a491e931b0000001408000000000000000000000000001008228a598200000262866d2d58787368382a8fc145e34c08f5fbfd54d582b582e088de5e32fd8736000000000000000000000000000000000d411da7b37693960ef0a495ffa8c0fcbf82c1fd8034ab3bf37f8a184242b723ff00000000000f4240000000000007a120000230d9b88fad260907c43f97b88bb9992bc334ef29015cae8d7a8aa5063539931d000000061a80000000000000044c000027100000000000000002000000000000000422002059106401877b19606b711f9980431fd80e830d2a30c59103d7f12e16a7e141d3000000003b9aca00000000001dcd6500000000000000000000010100000000000000005202000000014a12a0174ed233d1e65150f14ccb948a0508dbeaaa49236dce4c4fde7b69d31201000000000000000001e0c81000000000001600149e0bb5d89fbd10f3054bdde0fa3e22d42d1e7abc000000000000000000000000000101000000000000000124e3b1062400aaf012a8e25bb0932a3de9ab16a1accf7302b333f6020dfea5a22b000000002bc0270900000000002251202ee2991cf9a98cb8be01ed4ebc8279bcd84536bed6f73f4dd0012d1927d5d8250000000000010100000000000000020000000000016ec21600149e0bb5d89fbd10f3054bdde0fa3e22d42d1e7abc000101000000000000000300000000000176d82251202ee2991cf9a98cb8be01ed4ebc8279bcd84536bed6f73f4dd0012d1927d5d82500061a80af0d411da7b37693960ef0a495ffa8c0fcbf82c1fd8034ab3bf37f8a184242b723b0e1b56831c5dd8ef32c85df9896c057c36a7e8d3c95aeba8c9a4da79b0aa1430001006b0247304402203f4fdd467e2d27c89946d2851004c01988294b0071748c4acd315c90b9ab0b1902202c86d68228cf322d4b676ece89a5106d2518565cb859c436b9284d78f81b4d1201210392a15254566846b06ea693dbeedb1a6ded24b02d53ee40f091f628dc117a1a5d000000000000000000000000002710000000003b9aca00000000001dcd650024b0e1b56831c5dd8ef32c85df9896c057c36a7e8d3c95aeba8c9a4da79b0aa143020000002b60e316000000000022002059106401877b19606b711f9980431fd80e830d2a30c59103d7f12e16a7e141d3007d0200000001b0e1b56831c5dd8ef32c85df9896c057c36a7e8d3c95aeba8c9a4da79b0aa1430200000000a9d907800220a10700000000001600149e0bb5d89fbd10f3054bdde0fa3e22d42d1e7abcf8250f00000000002200203737db5c8aa01d33fd415cabd8b8680a16527c8d9c6fe32ab5cedbe610484aa187c9772000000000000000000000000000002710000000001dcd6500000000003b9aca007c6fd871c702806ae018d8609df56a1705d275b99994bb3563878d554fed62f4039fa3697fcaf99e44e3a5b601600667e00d22e940cba46708e3bf0beae9fda8e20000") val decoded = channelDataCodec.decode(bin.bits).require.value.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_SIGNED] - assert(decoded.channelParams.commitmentFormat == DefaultCommitmentFormat) + assert(decoded.channelParams.channelFeatures.commitmentFormat == DefaultCommitmentFormat) assert(decoded.channelParams.channelFeatures.features == Set(Features.DualFunding, Features.StaticRemoteKey)) // Local params. assert(decoded.channelParams.localParams.isChannelOpener) - assert(decoded.channelParams.localParams.toSelfDelay == CltvExpiryDelta(144)) - assert(decoded.channelParams.localParams.dustLimit == 1100.sat) - assert(decoded.channelParams.localParams.htlcMinimum == 0.msat) - assert(decoded.channelParams.localParams.maxAcceptedHtlcs == 100) - assert(decoded.channelParams.localParams.maxHtlcValueInFlightMsat == 500_000_000.msat) + assert(decoded.signingSession.localCommitParams.toSelfDelay == CltvExpiryDelta(144)) + assert(decoded.signingSession.localCommitParams.dustLimit == 1100.sat) + assert(decoded.signingSession.localCommitParams.htlcMinimum == 0.msat) + assert(decoded.signingSession.localCommitParams.maxAcceptedHtlcs == 100) + assert(decoded.signingSession.localCommitParams.maxHtlcValueInFlight == UInt64(500_000_000)) // Remote params. - assert(decoded.channelParams.remoteParams.toSelfDelay == CltvExpiryDelta(144)) - assert(decoded.channelParams.remoteParams.dustLimit == 1000.sat) - assert(decoded.channelParams.remoteParams.htlcMinimum == 1000.msat) - assert(decoded.channelParams.remoteParams.maxAcceptedHtlcs == 30) - assert(decoded.channelParams.remoteParams.maxHtlcValueInFlightMsat == UInt64(1_000_000_000)) + assert(decoded.signingSession.remoteCommitParams.toSelfDelay == CltvExpiryDelta(144)) + assert(decoded.signingSession.remoteCommitParams.dustLimit == 1000.sat) + assert(decoded.signingSession.remoteCommitParams.htlcMinimum == 1000.msat) + assert(decoded.signingSession.remoteCommitParams.maxAcceptedHtlcs == 30) + assert(decoded.signingSession.remoteCommitParams.maxHtlcValueInFlight == UInt64(1_000_000_000)) // Signing session. assert(decoded.signingSession.fundingTxIndex == 0) - assert(decoded.signingSession.commitInput.outPoint == OutPoint(TxId.fromValidHex("43a10a9ba74d9a8cbaae953c8d7e6ac357c09698df852cf38eddc53168b5e1b0"), 2)) - assert(decoded.signingSession.commitInput.txOut == TxOut(1_500_000 sat, hex"002059106401877b19606b711f9980431fd80e830d2a30c59103d7f12e16a7e141d3")) + assert(decoded.signingSession.fundingTx.txId == TxId.fromValidHex("43a10a9ba74d9a8cbaae953c8d7e6ac357c09698df852cf38eddc53168b5e1b0")) + assert(decoded.signingSession.fundingTx.tx.sharedOutput.amount == 1_500_000.sat) + assert(decoded.signingSession.fundingTx.tx.sharedOutput.pubkeyScript == hex"002059106401877b19606b711f9980431fd80e830d2a30c59103d7f12e16a7e141d3") + assert(decoded.signingSession.fundingTx.tx.buildUnsignedTx().txOut.indexWhere(_.publicKeyScript == hex"002059106401877b19606b711f9980431fd80e830d2a30c59103d7f12e16a7e141d3") == 2) assert(decoded.signingSession.localCommit.isLeft) val unsignedLocalCommit = decoded.signingSession.localCommit.left.toOption.get assert(unsignedLocalCommit.index == 0) assert(unsignedLocalCommit.txId == TxId.fromValidHex("ab3c0f7a2242e5656cc59db4852347b9ad4c6b54186639bf3b5698df2b129f88")) - assert(unsignedLocalCommit.input == InputInfo(OutPoint(TxId.fromValidHex("43a10a9ba74d9a8cbaae953c8d7e6ac357c09698df852cf38eddc53168b5e1b0"), 2), TxOut(1_500_000 sat, hex"002059106401877b19606b711f9980431fd80e830d2a30c59103d7f12e16a7e141d3"), ByteVector.empty)) } test("decode splice unsigned local commit") { val bin = ByteVector.fromValidHex("001801d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d230101041000100002bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e630009439468f0eab4a6375ce444fc12eaf57df47aba7e2644be598e5249349f8172218000000000000000000003e8000000003b9aca0000000000000003e80090001e000003fcfbfa87a789958625832770826a2e391b87880a5698e601033942c75173460a0000001408000000000000000000000000001008228a598202aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa000000000000044c000000001dcd6500000000000000000000900064027800eedc36e641dff2b5cd5041895a8dbff6b52bf1d8fe58511fe0b2ae60c19503fcfbfa87a789958625832770826a2e391b87880a5698e601033942c75173460a03c6804903002dbb70e488fb53009ea0a024f87acafda1d96f8a0f1fd2f98b3e1302a26a217b4263be92199009db2b503e48272c0600d753a97ae34bd9827251c6700000186b02000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000001808220a59820000000000000000000000000000000000000000000200000000000000020004fffd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000000000000000e4e1c0342bb8ca8412a52cbaa04072e0e285ff414a4bf36be1828482705a6bd13501a200061b100003e368b6d07bc35ff7e3475feddeb313912771046d96fa86ec9a5ba2e9024f5df44c2609468b1f86640de3863c1e3ff60042667392d43809462bc3d18d454ad70de3d85e738909aaa1930559a093e363449e7a00c85cb62562298a108131f6f88635d990d4a6de980d076bfee5281d62828c19da4db7843c304c6d2fe742a8421ffb46c26b5e06249fb8fb4ff0b4d1f08d44d8d7d87953db3da6154f8d31d5af9780f6e45f4867fc7d77a1e438dd87172758779414784275dd05b88eafca27ed4424f1249c14d0894202377b2a394e3cc063badd9bd57f66122bb391fff3718520fc9dffa0b225b1eba27be4f0a386aeda91f85b126771f356afa9d1c9aeed249ac100ece76fc8b32903eaa9caeb2c0e3e6d05f69ad025d7ba625d536e6ad06d8b77aceb6d7a01dbb17adee7e17eca234b251e353e26de28849876b8cf88bd57f4a04745d6a88d32f00b35afdf416efd8f84826840e9e974cbd8aa4a102507f0c7e441ad9e1c1275ccc89266bde55f4a3582b78fa8aafa1994ef36b35d3943d3cd326bc70a3ddd377e895b498d79cbc857b5bb2e61f30ac8ca48fe186e40326fd9fef0215e2ef31844bcaec6334490a89dcd42219d64bfeacec7978294ac964b66b49958130a77f7569b083b1c280d4e451e872403454a3947d2cb10ad2f96f92b070f886a5ed771332f51889d18cc2ad941c6638eebefe5fe6cc4b79468b1e469adb08c34982783d49be95f32a774f51db6788d34f26a8af1b31fc709d78338c6c4d9834fa25a910b4b63e05aa6d383d282bba31c9378e3f27f985088e682e11a46a9a1c8e18be9b7c7ef02d9faa8bb08a53d5ffe059d48d440c6364292fb42df68a04aacdfaaba029f1460912b11a6f8eb57578832fb3c10675c7989114177bdb3b587a80856f351177d01eefbb1de210839c9b8b9ba621942f250dc6b4bbf014899b77f52e8e40cb06f7063371251c419bb8aa9d3c0bd83ce651520b6e2911dd38db6c0fff7cc64550320c5cac621765175460cc8f54ee4a0026ec8e57fd789e0fef66c4940cabc3944ff4e3fe10229294e6fcb3059adb4f9868e53b52a7da60f5ae75d4e42e6a94c44e375960f9ed44a56c0b6c49e7c5775dcc1a819547bf3f146e8bdb13f305ea0d15b5616623db4c0657059d07df7acf1062069705ae5699ce86d9d0de16f3afae97f5327fd340693f756aba0bf338af880927cd7f976705c2b511077bbd19b9a842cb7a9b2f713f183c1e4eac23bee3ac081be66de275451773444e81536e183c8e2f033766b854cc92773205abb0f39d90e762c1f0673af8e4051205f752757d2deb7dce6adf8a55297a3a56fe1265368970e8d480b8ee61fa38313e5a7069579f10ac2db37c05c58b38544a8750e186c43a153c7780cb8ddfa309fac85b487b43b1452624afbc4005bbc0777eda63c96edc94016d1fe880dcb2faab472802921adc29dd3410628b8431186a7ef3a6839eb16123ffccf40c4c0907764e40d1dabbaa12a3a9ed3f341c070fb622600b6f4595f4601f67f2d03e7221d57a20bad2e0fcde738107a57fea1e1257a89970bc94517fecfe4990c67f7bfde2ad523f0f4dc760e0e8958eb32c6a7b0e7d952e75fdf08550ab72b6824122697846e599c8a2cd7f3066c8c444e2bb41aa38cb063493af2106eeae5228247706a53169f306d6b0d74867db69d29e9a6eca2b77aeb3353fa5a593b9f4fa379ffd01464a6929841f0fe934d68c8ae9e523a87d02ce5df5636cc5dcd72536d044cedd2a1d1e9047712a399627a44c8776cc5d0b2fa7e3dbef52102602f97090afab7a6070af4a1e50d19fb646f25212a7e750d6d0f765c0d5324721afe49d1070a686b05dafd39114bc65cc11cb30cda09b872c79521273ff2f075ebc4a2e5444c6880b3854d623bf1c42be1026d8b0409fe0001a1470107fffd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000010000000000e4e1c0175abe3cc14d94456c949372ed6ace0b5b92e58c595a6297e6c1cdfbe5bd750a00061b1000025b15002f7b27451dba144086e3ccebd9726c2e1af20883b676631e13908f2a8a0dcabd2fe4d39cd5b0c9d007a4dc3f1a5624208693e461d934fcbb48428eb1cfba41682d19ab1221e11648459eee58b855ea196e854047d82869b62b2efc978f520eb13e35e3752e53026a0f7b22ddf65de7a4e3fea56fc86b9c85cb8df4cfd0262cf3bbe1e16eb25574db8fd56cee33cbfdb73073f700b9992c19ac808354db41e488aa31d6b8a3be8d151732aaf6353f30a18cd773285cddbd3b279e6455f9b90aa37d7055ccd9fdafa66ab0ad216fd123e4a4aadb25f9e004c5a8e794abced52cd6e208d8d2657d03f5179b6353133a9eaf151cf5d47298058ad85ada4e42d29b9c61524ac20264d3b6551efef60789c6a91cf1ce4e069e3ad123fba8d515d705c0e2f3aec108ab317116d593100a4db4affd32b4091a1368cfe3c91b422ff53c75ade0ed61dd562e55cd674e5a0c5ca5952c2204c401d9c61c25b565c83c38cc09eaa6e25c42d234875f2a353813b8db46f031f0392f73a9cc8a88925347e04cfb477f3a7c5578edaa447927c048aa631b4f3dafcb608fafef2e125a549e029f2598cae566f93febee55ab1175359913732673a44fd2a8e28f71e281917c7d0fc04816a5774a3411d1db8f290fe442e3d3141ab366633f07e46528d44528a422c9225129b0ae4e7f3b76c9c8fb25b9c7722be9b5862ce866980003752c178e7a94224a2b0b84fa1e2635b5e2207ade589baa96372d4453868b3e01e9ac2d1d80bc0c7e06a22c205f85e578d55e3283e9b2defb70f46a6084ea40e92b25f35550e7f3fc3c9e764dd4eea7b85df10175a0799e6a46cae937ddb1fbc43795a88850870cdd0f5bfb4506f227b1420f6e8d39b7ccbdc888a7df7066cfcd8da6ba6bceb2aa80bf14fd5d096f604af41ae92b9f4e3c6a7f29208e8b3fb70cf81fd56ff17ee56adf35cb18b39a067f67f9fc3f94e8c30d44760da2322a77679bd6ca0da0e47f206ef7b644448e7330bf19455ca777418896f0e07e551ae4995c7ac5026a7b84c0cd07baf9569135ae4cc3893339ac886ab704b37d46aa3edcfeaa6c4c8aec3ec8bf8ffb51a591ff02f04367314c267a3152070069950d384b99235e5398547d5084c4afbe86ce6a6a60964b8cd89b20c690e8de12606f64ea559a36431dc98d6189a3f15d439dcafd831a9dab64bf8dd5722c91d59fab00fad5b27a89d5f4237cb20ca55648a8256e8a0ba18394608840336d15a84451821619a2363eb51c5ee83e1722476106248962ccb5f7bd843e7dd14620a2b40107d972fa6b1e4ed7bd6b13b659b012870b64906229c93c2625b02da587d2239e09d64bec81f40c50398eb666ff8912711c2377165cbb0ad16999c0b09bc36f354cd495d63fc6cd7b89e4063ff1b63b319afa4a7a64da69aa41ba9af3131df5cb384ce68ab74898d699ea09730b53d3d862521927b0cde151844a9eb11aef8e0e25a3fb607d57c2a9f83eaf01f1c387e09be5fc5ac0eb091359ca26c8fb36cea3ccae8a63e57dcbc60ee66975bfee8964cfdfc840b114dc6ed5eac0babd26f99ec251c45ec602a44fcc6965c0d7c065c186569be6426658b8a5b32fc2dee315a0457e963635795a0d699db9587eeacf8f893699410a87b6390d8c8736552e325f5ccec1b6d4b45002b678649b234301610ecd605f9e56c5dc45d81cccf73e442b872369cbbd3a03eaba79c0af36e23ed97cf73808dc5ed0f5fcd751772b57e2666c5a70b1a5af61a0dd461e8a383e8426369ee6a4e03cf256a6bc3570997c3c06879946ffdd2924aa329e006759c0d6b564c89198c0c09f88c2c9d0c2cd4e0163e8f98145456cc859f55aa8d11a9969ae253d93d56d9fc6a386ae7c6ee884f483913bd739124f69b4bd5238fdc33d860a9cd4670de5dafe4a24fe0001a147010700fd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000000000000001312d00af8cea7a0a1be6687852d7f2249be3b445ae0cddc097a05ea468ef797567856d00061b100003e5507553c137054ae33b5d7cdaf142fbdbfae51246ea3db4441251aa49dac2e8cac063650741353682da85f0df1b1db72997177b74940bcabcb961f364c1861a493066a9cf2533aca5f866b280419fe04f9e9807d3049b759f78d7ce322554ed7891184e73d934c62f88ca33f7cd75d56617664cc674d74f553b8674093e60f014a89e1ab4ea7adde5d54f28a9976d2a3ddb20854b3a1f3ecac8490a9243234c7ce9c5df1b0e45d862c847fd93b99ed1f4d5fd5b06ca416ed048433782d4bd867d36196a7ebe1106f687d0868cd2f2cc1fd94f999de8e59e9a9b333aac6304ac7c01cb91d20c09d4e4f38397ccc1a0cbb46865b7292b84d4f11a783abb0d5da2e31fdc7c2885c6c90053f716264d0b8ba1ce53dc72e8f04e702e319029ca234b8bf72f5321382723010153eaf113e0c823914bf6ea319a1083efe2f0736e1f9b36125c4b77b9e4a581e6fecec959be70f2e21b8c9bd568641d45feb3ffdd39491abc973f310cb1f9c46a874471e5edc3ea7d4063274a9e10e2d21a9e836e99d386d062ac31bf19a14ad6d39c19752eb749c4dacc64aa4591228c039a1f3c0dfd919a2576edf70cbe018075d90c94bebbb91290487cb9cc613eef312a87025891ea6d8b02435b26abf72fea504aad333100e4f14ee916c678c6c9a42b3421bb8f7389b642f89b5abaf99f0df25e5859bee216c3845bbc9fdb032acb88cc144306cc17fe754280bccfecd86670d6994833c4847c71913caed6d904525d22d28aadf3d4e867a617f20a9cf61e95152eb1cab90bae7885da01effee11b7780bd2398d37f04096e7d14252e2026f329422c077762996f45a7c4d546d7ad0422996e659c98f1feebc5c73eeecab837c025a44d94cb5ee39d5a1bbd95973e8e5cdd97cabb7b8f4f86a5bb228c0d12be6b9b2032c54795f4f7180828e94377bf6e0f56545dfcd5252da0936d79aef70caa8715e8416579ca1a928c2aecfa1e03b6cd795528a967fb3dbe524996be92b6168222112063077fb39811ebf9dbb97abafe1b315488c8812224faef56c1eb86837ffcf61bc22a60de7e4f6e0df67c332389556df7da838200dcceeafcb6e8a94aaad013c30934ec0ada06fa17afb5414de4812deb6fa96517df57d503e1e521562ed62a65073252dd08aafba343c029e04faf58b4c451061f156669be5ef68a2adff506cba6351e407d3c6077b71807f21e93d0ca69843f2d5fd6b7c7f415d740fac1f1f8c8bd8f95297ee1ffafbe1b41c548b69dd6d211352c556f14bb4e5af76244e7656f882452201e2bfd90a87413904081a78c2d6849a6a07ad77920465141da19034b70c3edac53a560e7b5c07923c9bc6e17b8091257fe7431e7cb18d770ba1457e62714a47beb8e503874f24405c49121d2cb024edee3458b31ebe1bf40a13f564b8ff95dc537ec926311f59150544fcc57efcef53d9f43c41b0dc32f93b91d2663e9f6b09f9483b4a788b0642d667c722d10ddf09a70886153fc8c943a9d6f3eecec3ab7144669c10b3c5864a174c7343a74e4b58da29f0ae480d5be8adb5d5b55e9cc3c11da1a935c2b1cb966d85612b6c4bbd42aaaeeebada9f062175f502c2cde7daa345239f99201be98ae989cd8a3cd0610779fd413ef1c6c48cd6e8bcb454b847815016e214f1e6162df1b388503b06bad1e5e35ca424fa1ac4407757504e311f4ee0f4d8e5dce45759a96031460842697d577205255b9ba2bbbe4eb08528a9224087a40129906655170405ad4491052ba71d6dde4000b7d7154634db60db540a16cc430b5998dc1465bc57b5a4125c967e4fc451d66026b650b9a70c26998f559793809df549ca1d5e730db0759ed40d3a030c8a1c6bccb28b5fd9230c55ef83d23b369938200099274978bcc017a96bd1b506c6d28b46788f9a483f3e0b234fe0001a147010700fd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000010000000000e4e1c0415149b9751963f87c1583f34e4fb1bc5599cb48dc36e0ab744c37fb0630f93e00061b1000033fabe908e0bb47028814d9361e2518822ec95a72c8e611b1562fb595ad452db3272b17a6f9280115fa6e17434cfc1639b3b1795ff6cfe4aaa80b3393100ba4a2308b9e42dbbb3cba55bec53bbf574b9307e2023f3e13e6d0424ce7b4376280ea0d3a187c8585212fe9effee9aa102873a48f1b71a2ae81cc97fdf6075b4ee17716bbcbcd930a23d483f04907dcf89d9d55d62ac6935ce45afe8549de48a363c595876e32e8ba33806671cb54a92a81409f9bc8964983552f709335efb962d17c2bcc348fcf9af666ae7cff45833b4b0449481a6cc869a6cc2d928ccba9499e6415a52c455ebce14cd94a36fa0bf127682e890be81a1dcc77dd2b94f886103d793ffce97430edf04f4b8a0aaf9622b34bfceaef96bd6848717430d2212f2df30105354306fe8d5d452c32e1b0f1872cb7c953295d2794805fcad0c50cebd26b14cc903fd995d4eb397d85d3bbcc92a4b640ec4448a16eaae0941f2f074ea2f292b55c8cd8051f5b43c4f0d14ae04038d06216cf01b344fb3b8b42ae2680d7998c5f24c16813dcfc521525b5bda78bf604edab7edcac3934cbb9e5e1baae044ea136613ac003e86e3caa9f0e93c38c5997a17ab472004726730f79248be824fbe894422e2b07f951d31ccc32c7f513a61c7e0a4f3790faf2dca5fc4afca1a0f14b0a7a07178402f1875430c905250a5d4d8d70521418cca1c82a2b3721831087e342d74bab5395c5099fe8710c667fb463e50f59da0b67d7f8b2a4b47896c84954a82d604a570f41fb1a5a9906592ee050b0cad8fdc652a411f8dd0d4aa5c46540479b202f7752985311671be2fca007d87b2f27f16be6a590357aeb87f9f75d9f2c798599f3e8bc2fe5b1e7b5d4e3a779761d983f52e5d4844c7eabbd4e543436954a9e361f2f099bcb603a94f2cfc1f1409aa50aee8644c5a1222e2df52fd8a1f884c0a1a800c92be47b7bed41ceff4ee44700c88e527b55c0abdd04f86ce1dae36f0c2802bc4cc74e0698092fed0e8ab7a95e1bfdf0b6fd160a7c80b0f1bba8dde89740e4caa6681f1d4c926a49ee2088282d18e3646e4ade293e1b2cb5a0155d3ee662d5c468cb5d2ac7557e7666396336e3608d84c97fb1f48dd8ca7f8528b218eeac9a825b161147849d1c2a1ee461150cf8984f492d02221ea43d994821790f5e7357e615ce721817e89495bc3422b98a7e6d6288d75c3b727d1669742b682560660079d8860ed65022d279ca414cd56e169e1147d27ad808290f7daeb665a949b793ae503c1dc9d3f1ae0e57b02d373357408d881e2354337143f7d0b99dd2adf48e6c4f4983bc1de85345892e246c6edbd9aee3747fcc2efa956e3e89e9f5a9be0401933b51d625e25c8296be8ef13720d15411d029a471387fa2c28a168f72e0802d84b3085937f9a83353e002b39596e1567d712e729aa55e648f87043be90b359d3e1fd439091d3ded3b72c0c0bdac8e3033a2b2d6e4e074094b7ff6eb631e2b62ec36e35ea434154e0189b1b1e8b9b60da771cf0077ff267e7e861045d24d0c3fa57b9408d3e2116bf75ba02b4d8cd78701ca9836074440c6c0c0ccdb2cd490ee616bc6d3f8d10ddaf60dfe77003ea326673193d21032997f8fb77c2393ce7af5f1d3d3abef5550e80c5a4e3b7ae75ae3f79950a2f8e5065e8d8fecd2f195aac557d7a4b4a5d1e0c5f1e60c0005d73df261401196c93384fcc271b0ebf0a9da2adee213319ed1fb5bec02202b4a0778bdd48420e701105a135b8bf2d4f50b6939ca0890174d7ce9852473456059b9b7a85db8e4664fa67dbc2d729080df312f3d8970e28e7304510b4828624279cf789d9deebf08cce0c1cf84ffb2abc52784230a5b488fe167632882be37fd1564c22b0dc89cf86ea5ffe6d7880b7199eecb9957b28ba3035316b763a4c0a0691bfe0001a147010700010000000000000000000000000218d33da90930d78dfda97b94815fd8543f4485d0574727143932171cc4d796ab0afd01810200000000010223c6a41f797be480b870e70fbc0b567935abc812d5b0e9bdb57e9992dbb54d770000000000000000005b11e204242c6a81fdfaf3d00b8825ca2b3eb16548c572ff9d1d8eef653d7e240000000000000000000360e316000000000022002049205995639f7357505aed54f7b392d2662276bc5d8968da78e6d623d9e6c7fc0a77010000000000160014cf85091a7295547ebd2066aecb1a295440a9f6f0906e010000000000225120a4ac7358d683a4a296a84a196286ea8bf7c80683f3ea8c30be932eeb30b4cfac0140ec5f7239b44a84754b02b947a6210e6d76ab2e57ad71a68b4080b3e68e3d3355f426455a86dcd678677d62262352258d5a170621aeca6643b8f94677667fa44102483045022100ff4ba957db4a6f149ed4513bc8cd64c9cc430f4c5810c1d01d73df72926e492e022013495ad376bd22c6683cc334542192411369201e4eaef605b59d34403c1f5de0012103fcfbfa87a789958625832770826a2e391b87880a5698e601033942c75173460a801a0600061a8000002a0000ffb0d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2344fde6f10018b4cec394cce110bac5d84cce63839bed6752d3c1498c5662b9620001006c02483045022100ff4ba957db4a6f149ed4513bc8cd64c9cc430f4c5810c1d01d73df72926e492e022013495ad376bd22c6683cc334542192411369201e4eaef605b59d34403c1f5de0012103fcfbfa87a789958625832770826a2e391b87880a5698e601033942c75173460a000200000000000000020004ff0000000000000000ff0000000000000001000000000000000000000000000000000001000027100000000027a31840000000002de544802444fde6f10018b4cec394cce110bac5d84cce63839bed6752d3c1498c5662b962000000002b60e316000000000022002049205995639f7357505aed54f7b392d2662276bc5d8968da78e6d623d9e6c7fc00fd0129020000000144fde6f10018b4cec394cce110bac5d84cce63839bed6752d3c1498c5662b9620000000000521a7a8006983a000000000000220020016c1483322b7ef8721387e87a51948fa2e8ecc28dfe33a48327a9eaafd70376983a000000000000220020092dc847c0de4b3408a7b0dced30f091ef9d73e5f8e719794e4267399ed5286d983a0000000000002200209c01da2ecb83e740748c0120909a95cd06387b45902c49810e55f86d34189a85204e000000000000220020c5a1bd48f7ffbc9cd20dadd15f11380a4e878dac76e56080c555134940f2e66ca8250a00000000002200202b0c7734d2e70db4213bca81a2e46458f6e105ff6e2e66aaa3b76133134c3dfea8880b0000000000160014cf85091a7295547ebd2066aecb1a295440a9f6f080f0fc2021396d48be2b7fbc88fd5fe4ab91a19d7af24be98161251a487a8616cf6b425a7980d99b2a2105ccd02fcc73f3449fb169573f0d411ff78aa338eaa344cf549300041124d570c959ea1c621f803b28f343b929732718012795559fe1532e03c42b29e59b000000002b983a000000000000220020016c1483322b7ef8721387e87a51948fa2e8ecc28dfe33a48327a9eaafd70376005e0200000001d570c959ea1c621f803b28f343b929732718012795559fe1532e03c42b29e59b00000000000000000001221f0000000000002200202b0c7734d2e70db4213bca81a2e46458f6e105ff6e2e66aaa3b76133134c3dfe00000000175abe3cc14d94456c949372ed6ace0b5b92e58c595a6297e6c1cdfbe5bd750a000000000000000100061b100a7a13c66544686dfc48ad7fc7d5abfb15a1404f9cb3df4abbb78e810023923100d382bd7186e9d7b47a3be1d8bf7b54eca322931d7514a3260bc6dd4cbb56131124d570c959ea1c621f803b28f343b929732718012795559fe1532e03c42b29e59b010000002b983a000000000000220020092dc847c0de4b3408a7b0dced30f091ef9d73e5f8e719794e4267399ed5286d005e0200000001d570c959ea1c621f803b28f343b929732718012795559fe1532e03c42b29e59b01000000000000000001221f0000000000002200202b0c7734d2e70db4213bca81a2e46458f6e105ff6e2e66aaa3b76133134c3dfe00000000342bb8ca8412a52cbaa04072e0e285ff414a4bf36be1828482705a6bd13501a2000000000000000000061b10c5e470c9b1c309e76daf590ed9f1a7a1111ddef24029bef966622220d059acaf72d241dcade8b164a4309422d2d02130b50f81c0652cef0944d410b3a38a04531324d570c959ea1c621f803b28f343b929732718012795559fe1532e03c42b29e59b020000002b983a0000000000002200209c01da2ecb83e740748c0120909a95cd06387b45902c49810e55f86d34189a85005e0200000001d570c959ea1c621f803b28f343b929732718012795559fe1532e03c42b29e59b02000000000000000001b2200000000000002200202b0c7734d2e70db4213bca81a2e46458f6e105ff6e2e66aaa3b76133134c3dfe101b0600415149b9751963f87c1583f34e4fb1bc5599cb48dc36e0ab744c37fb0630f93e000000000000000100061b10cd3756e951fcd4e7638026ce54e7962dfa1d6cc829a0ee998281b23118f5c83e2d7ff2aef3c1842b2fb552f2d2783f3d8187db9021cf41450d4f1ca15c1582f71324d570c959ea1c621f803b28f343b929732718012795559fe1532e03c42b29e59b030000002b204e000000000000220020c5a1bd48f7ffbc9cd20dadd15f11380a4e878dac76e56080c555134940f2e66c005e0200000001d570c959ea1c621f803b28f343b929732718012795559fe1532e03c42b29e59b030000000000000000013a340000000000002200202b0c7734d2e70db4213bca81a2e46458f6e105ff6e2e66aaa3b76133134c3dfe101b0600af8cea7a0a1be6687852d7f2249be3b445ae0cddc097a05ea468ef797567856d000000000000000000061b10c2ed771e0c811887d21c9d5ad6b72039505e19d879892aa99b83b943374b380e188a459147347953f4a3c34a29d471309e672b9d72c97bd6e64a6e9ff26e31f300000000000000010004000000000000000000000000000000000001ff0000000000000000ff000000000000000100002710000000002de544800000000027a31840b315b404d6bae38833774438441fb36bd2925416d27eeec63326e772032c950f029ced6a1eb5263d34d0d6877178f5b9027cbd9014780197c22fb6fe6849d8c966000000ff0357ca5aa7f38f8ae405263cb1adf2e1469c7f39ee4f9c9b1e70ba2bd02b625478000100400000ffffffffffff0020a409b8983df586e5b71d21ad4824f82a2719d3cad0fdb071c20d0f265b9ba9d080007fffffffffff800002000000000000000000034273b111141c4f04ba55c7b2db01fb50000000000000000100039eb27ed9be0947c7be5941507f4f316200000101eecdd250c851e3ff0344421a4e791eaa008829834f77192587723797bbc77e9f05a886000220d741fabc6a2c7be66dde92517ef89f6ce9ee5288e02f46727ecc12038e9491ad408776ba708b6dafa314db2906226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f01eecdd250c851e368414f7c030100900000000000000000000858b800000014000000001dcd650000000003d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d230000000000000000000000000000061a80ff00012444fde6f10018b4cec394cce110bac5d84cce63839bed6752d3c1498c5662b962000000002b60e316000000000022002049205995639f7357505aed54f7b392d2662276bc5d8968da78e6d623d9e6c7fc00000000000218d33da90930d78dfda97b94815fd8543f4485d0574727143932171cc4d796ab02790a3f53b2f208b1ff65cbd654baf8c0f105b405e8e587438f88eb38bd081479000000061a80000000000000044c00002710000000000001ff0300000000000000022444fde6f10018b4cec394cce110bac5d84cce63839bed6752d3c1498c5662b9620000000022002049205995639f7357505aed54f7b392d2662276bc5d8968da78e6d623d9e6c7fcfffffffd0000000027a31840000000002de544800000000003dfd24002000000000000000622002029fedca3b62676f613aaaea53ff97b497f8799f40a5dc586e67f023c5b1a58750000000027a318400000000045bcc8800000000003dfd2400000000101000000000000000024f48b66833f2ecbd6f125eb8b4c38dd3836ea3e122b64fbe4e293fff54f32ffd0000000002b200b200000000000225120a4ac7358d683a4a296a84a196286ea8bf7c80683f3ea8c30be932eeb30b4cfac00000000000000020100000000000000040000000000183c10225120a4ac7358d683a4a296a84a196286ea8bf7c80683f3ea8c30be932eeb30b4cfac01000000000000000800000000000186a0220020aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa00061a8086d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2341f6c672e644f3aefdda12824a642ca2cce409ef69c5530dcbb2438d64a56ce70000fd025940e6616fcd9dce55776bd871840e88a65415dae895324ce3d454a59626ddc029474733535c6f675d83cade543d03cf7e422d8bb7f1dea3c9821d40cd55035691f10000000000000000020004fffd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000000000000000e4e1c0342bb8ca8412a52cbaa04072e0e285ff414a4bf36be1828482705a6bd13501a200061b100003e368b6d07bc35ff7e3475feddeb313912771046d96fa86ec9a5ba2e9024f5df44c2609468b1f86640de3863c1e3ff60042667392d43809462bc3d18d454ad70de3d85e738909aaa1930559a093e363449e7a00c85cb62562298a108131f6f88635d990d4a6de980d076bfee5281d62828c19da4db7843c304c6d2fe742a8421ffb46c26b5e06249fb8fb4ff0b4d1f08d44d8d7d87953db3da6154f8d31d5af9780f6e45f4867fc7d77a1e438dd87172758779414784275dd05b88eafca27ed4424f1249c14d0894202377b2a394e3cc063badd9bd57f66122bb391fff3718520fc9dffa0b225b1eba27be4f0a386aeda91f85b126771f356afa9d1c9aeed249ac100ece76fc8b32903eaa9caeb2c0e3e6d05f69ad025d7ba625d536e6ad06d8b77aceb6d7a01dbb17adee7e17eca234b251e353e26de28849876b8cf88bd57f4a04745d6a88d32f00b35afdf416efd8f84826840e9e974cbd8aa4a102507f0c7e441ad9e1c1275ccc89266bde55f4a3582b78fa8aafa1994ef36b35d3943d3cd326bc70a3ddd377e895b498d79cbc857b5bb2e61f30ac8ca48fe186e40326fd9fef0215e2ef31844bcaec6334490a89dcd42219d64bfeacec7978294ac964b66b49958130a77f7569b083b1c280d4e451e872403454a3947d2cb10ad2f96f92b070f886a5ed771332f51889d18cc2ad941c6638eebefe5fe6cc4b79468b1e469adb08c34982783d49be95f32a774f51db6788d34f26a8af1b31fc709d78338c6c4d9834fa25a910b4b63e05aa6d383d282bba31c9378e3f27f985088e682e11a46a9a1c8e18be9b7c7ef02d9faa8bb08a53d5ffe059d48d440c6364292fb42df68a04aacdfaaba029f1460912b11a6f8eb57578832fb3c10675c7989114177bdb3b587a80856f351177d01eefbb1de210839c9b8b9ba621942f250dc6b4bbf014899b77f52e8e40cb06f7063371251c419bb8aa9d3c0bd83ce651520b6e2911dd38db6c0fff7cc64550320c5cac621765175460cc8f54ee4a0026ec8e57fd789e0fef66c4940cabc3944ff4e3fe10229294e6fcb3059adb4f9868e53b52a7da60f5ae75d4e42e6a94c44e375960f9ed44a56c0b6c49e7c5775dcc1a819547bf3f146e8bdb13f305ea0d15b5616623db4c0657059d07df7acf1062069705ae5699ce86d9d0de16f3afae97f5327fd340693f756aba0bf338af880927cd7f976705c2b511077bbd19b9a842cb7a9b2f713f183c1e4eac23bee3ac081be66de275451773444e81536e183c8e2f033766b854cc92773205abb0f39d90e762c1f0673af8e4051205f752757d2deb7dce6adf8a55297a3a56fe1265368970e8d480b8ee61fa38313e5a7069579f10ac2db37c05c58b38544a8750e186c43a153c7780cb8ddfa309fac85b487b43b1452624afbc4005bbc0777eda63c96edc94016d1fe880dcb2faab472802921adc29dd3410628b8431186a7ef3a6839eb16123ffccf40c4c0907764e40d1dabbaa12a3a9ed3f341c070fb622600b6f4595f4601f67f2d03e7221d57a20bad2e0fcde738107a57fea1e1257a89970bc94517fecfe4990c67f7bfde2ad523f0f4dc760e0e8958eb32c6a7b0e7d952e75fdf08550ab72b6824122697846e599c8a2cd7f3066c8c444e2bb41aa38cb063493af2106eeae5228247706a53169f306d6b0d74867db69d29e9a6eca2b77aeb3353fa5a593b9f4fa379ffd01464a6929841f0fe934d68c8ae9e523a87d02ce5df5636cc5dcd72536d044cedd2a1d1e9047712a399627a44c8776cc5d0b2fa7e3dbef52102602f97090afab7a6070af4a1e50d19fb646f25212a7e750d6d0f765c0d5324721afe49d1070a686b05dafd39114bc65cc11cb30cda09b872c79521273ff2f075ebc4a2e5444c6880b3854d623bf1c42be1026d8b0409fe0001a1470107fffd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000010000000000e4e1c0175abe3cc14d94456c949372ed6ace0b5b92e58c595a6297e6c1cdfbe5bd750a00061b1000025b15002f7b27451dba144086e3ccebd9726c2e1af20883b676631e13908f2a8a0dcabd2fe4d39cd5b0c9d007a4dc3f1a5624208693e461d934fcbb48428eb1cfba41682d19ab1221e11648459eee58b855ea196e854047d82869b62b2efc978f520eb13e35e3752e53026a0f7b22ddf65de7a4e3fea56fc86b9c85cb8df4cfd0262cf3bbe1e16eb25574db8fd56cee33cbfdb73073f700b9992c19ac808354db41e488aa31d6b8a3be8d151732aaf6353f30a18cd773285cddbd3b279e6455f9b90aa37d7055ccd9fdafa66ab0ad216fd123e4a4aadb25f9e004c5a8e794abced52cd6e208d8d2657d03f5179b6353133a9eaf151cf5d47298058ad85ada4e42d29b9c61524ac20264d3b6551efef60789c6a91cf1ce4e069e3ad123fba8d515d705c0e2f3aec108ab317116d593100a4db4affd32b4091a1368cfe3c91b422ff53c75ade0ed61dd562e55cd674e5a0c5ca5952c2204c401d9c61c25b565c83c38cc09eaa6e25c42d234875f2a353813b8db46f031f0392f73a9cc8a88925347e04cfb477f3a7c5578edaa447927c048aa631b4f3dafcb608fafef2e125a549e029f2598cae566f93febee55ab1175359913732673a44fd2a8e28f71e281917c7d0fc04816a5774a3411d1db8f290fe442e3d3141ab366633f07e46528d44528a422c9225129b0ae4e7f3b76c9c8fb25b9c7722be9b5862ce866980003752c178e7a94224a2b0b84fa1e2635b5e2207ade589baa96372d4453868b3e01e9ac2d1d80bc0c7e06a22c205f85e578d55e3283e9b2defb70f46a6084ea40e92b25f35550e7f3fc3c9e764dd4eea7b85df10175a0799e6a46cae937ddb1fbc43795a88850870cdd0f5bfb4506f227b1420f6e8d39b7ccbdc888a7df7066cfcd8da6ba6bceb2aa80bf14fd5d096f604af41ae92b9f4e3c6a7f29208e8b3fb70cf81fd56ff17ee56adf35cb18b39a067f67f9fc3f94e8c30d44760da2322a77679bd6ca0da0e47f206ef7b644448e7330bf19455ca777418896f0e07e551ae4995c7ac5026a7b84c0cd07baf9569135ae4cc3893339ac886ab704b37d46aa3edcfeaa6c4c8aec3ec8bf8ffb51a591ff02f04367314c267a3152070069950d384b99235e5398547d5084c4afbe86ce6a6a60964b8cd89b20c690e8de12606f64ea559a36431dc98d6189a3f15d439dcafd831a9dab64bf8dd5722c91d59fab00fad5b27a89d5f4237cb20ca55648a8256e8a0ba18394608840336d15a84451821619a2363eb51c5ee83e1722476106248962ccb5f7bd843e7dd14620a2b40107d972fa6b1e4ed7bd6b13b659b012870b64906229c93c2625b02da587d2239e09d64bec81f40c50398eb666ff8912711c2377165cbb0ad16999c0b09bc36f354cd495d63fc6cd7b89e4063ff1b63b319afa4a7a64da69aa41ba9af3131df5cb384ce68ab74898d699ea09730b53d3d862521927b0cde151844a9eb11aef8e0e25a3fb607d57c2a9f83eaf01f1c387e09be5fc5ac0eb091359ca26c8fb36cea3ccae8a63e57dcbc60ee66975bfee8964cfdfc840b114dc6ed5eac0babd26f99ec251c45ec602a44fcc6965c0d7c065c186569be6426658b8a5b32fc2dee315a0457e963635795a0d699db9587eeacf8f893699410a87b6390d8c8736552e325f5ccec1b6d4b45002b678649b234301610ecd605f9e56c5dc45d81cccf73e442b872369cbbd3a03eaba79c0af36e23ed97cf73808dc5ed0f5fcd751772b57e2666c5a70b1a5af61a0dd461e8a383e8426369ee6a4e03cf256a6bc3570997c3c06879946ffdd2924aa329e006759c0d6b564c89198c0c09f88c2c9d0c2cd4e0163e8f98145456cc859f55aa8d11a9969ae253d93d56d9fc6a386ae7c6ee884f483913bd739124f69b4bd5238fdc33d860a9cd4670de5dafe4a24fe0001a147010700fd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000000000000001312d00af8cea7a0a1be6687852d7f2249be3b445ae0cddc097a05ea468ef797567856d00061b100003e5507553c137054ae33b5d7cdaf142fbdbfae51246ea3db4441251aa49dac2e8cac063650741353682da85f0df1b1db72997177b74940bcabcb961f364c1861a493066a9cf2533aca5f866b280419fe04f9e9807d3049b759f78d7ce322554ed7891184e73d934c62f88ca33f7cd75d56617664cc674d74f553b8674093e60f014a89e1ab4ea7adde5d54f28a9976d2a3ddb20854b3a1f3ecac8490a9243234c7ce9c5df1b0e45d862c847fd93b99ed1f4d5fd5b06ca416ed048433782d4bd867d36196a7ebe1106f687d0868cd2f2cc1fd94f999de8e59e9a9b333aac6304ac7c01cb91d20c09d4e4f38397ccc1a0cbb46865b7292b84d4f11a783abb0d5da2e31fdc7c2885c6c90053f716264d0b8ba1ce53dc72e8f04e702e319029ca234b8bf72f5321382723010153eaf113e0c823914bf6ea319a1083efe2f0736e1f9b36125c4b77b9e4a581e6fecec959be70f2e21b8c9bd568641d45feb3ffdd39491abc973f310cb1f9c46a874471e5edc3ea7d4063274a9e10e2d21a9e836e99d386d062ac31bf19a14ad6d39c19752eb749c4dacc64aa4591228c039a1f3c0dfd919a2576edf70cbe018075d90c94bebbb91290487cb9cc613eef312a87025891ea6d8b02435b26abf72fea504aad333100e4f14ee916c678c6c9a42b3421bb8f7389b642f89b5abaf99f0df25e5859bee216c3845bbc9fdb032acb88cc144306cc17fe754280bccfecd86670d6994833c4847c71913caed6d904525d22d28aadf3d4e867a617f20a9cf61e95152eb1cab90bae7885da01effee11b7780bd2398d37f04096e7d14252e2026f329422c077762996f45a7c4d546d7ad0422996e659c98f1feebc5c73eeecab837c025a44d94cb5ee39d5a1bbd95973e8e5cdd97cabb7b8f4f86a5bb228c0d12be6b9b2032c54795f4f7180828e94377bf6e0f56545dfcd5252da0936d79aef70caa8715e8416579ca1a928c2aecfa1e03b6cd795528a967fb3dbe524996be92b6168222112063077fb39811ebf9dbb97abafe1b315488c8812224faef56c1eb86837ffcf61bc22a60de7e4f6e0df67c332389556df7da838200dcceeafcb6e8a94aaad013c30934ec0ada06fa17afb5414de4812deb6fa96517df57d503e1e521562ed62a65073252dd08aafba343c029e04faf58b4c451061f156669be5ef68a2adff506cba6351e407d3c6077b71807f21e93d0ca69843f2d5fd6b7c7f415d740fac1f1f8c8bd8f95297ee1ffafbe1b41c548b69dd6d211352c556f14bb4e5af76244e7656f882452201e2bfd90a87413904081a78c2d6849a6a07ad77920465141da19034b70c3edac53a560e7b5c07923c9bc6e17b8091257fe7431e7cb18d770ba1457e62714a47beb8e503874f24405c49121d2cb024edee3458b31ebe1bf40a13f564b8ff95dc537ec926311f59150544fcc57efcef53d9f43c41b0dc32f93b91d2663e9f6b09f9483b4a788b0642d667c722d10ddf09a70886153fc8c943a9d6f3eecec3ab7144669c10b3c5864a174c7343a74e4b58da29f0ae480d5be8adb5d5b55e9cc3c11da1a935c2b1cb966d85612b6c4bbd42aaaeeebada9f062175f502c2cde7daa345239f99201be98ae989cd8a3cd0610779fd413ef1c6c48cd6e8bcb454b847815016e214f1e6162df1b388503b06bad1e5e35ca424fa1ac4407757504e311f4ee0f4d8e5dce45759a96031460842697d577205255b9ba2bbbe4eb08528a9224087a40129906655170405ad4491052ba71d6dde4000b7d7154634db60db540a16cc430b5998dc1465bc57b5a4125c967e4fc451d66026b650b9a70c26998f559793809df549ca1d5e730db0759ed40d3a030c8a1c6bccb28b5fd9230c55ef83d23b369938200099274978bcc017a96bd1b506c6d28b46788f9a483f3e0b234fe0001a147010700fd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000010000000000e4e1c0415149b9751963f87c1583f34e4fb1bc5599cb48dc36e0ab744c37fb0630f93e00061b1000033fabe908e0bb47028814d9361e2518822ec95a72c8e611b1562fb595ad452db3272b17a6f9280115fa6e17434cfc1639b3b1795ff6cfe4aaa80b3393100ba4a2308b9e42dbbb3cba55bec53bbf574b9307e2023f3e13e6d0424ce7b4376280ea0d3a187c8585212fe9effee9aa102873a48f1b71a2ae81cc97fdf6075b4ee17716bbcbcd930a23d483f04907dcf89d9d55d62ac6935ce45afe8549de48a363c595876e32e8ba33806671cb54a92a81409f9bc8964983552f709335efb962d17c2bcc348fcf9af666ae7cff45833b4b0449481a6cc869a6cc2d928ccba9499e6415a52c455ebce14cd94a36fa0bf127682e890be81a1dcc77dd2b94f886103d793ffce97430edf04f4b8a0aaf9622b34bfceaef96bd6848717430d2212f2df30105354306fe8d5d452c32e1b0f1872cb7c953295d2794805fcad0c50cebd26b14cc903fd995d4eb397d85d3bbcc92a4b640ec4448a16eaae0941f2f074ea2f292b55c8cd8051f5b43c4f0d14ae04038d06216cf01b344fb3b8b42ae2680d7998c5f24c16813dcfc521525b5bda78bf604edab7edcac3934cbb9e5e1baae044ea136613ac003e86e3caa9f0e93c38c5997a17ab472004726730f79248be824fbe894422e2b07f951d31ccc32c7f513a61c7e0a4f3790faf2dca5fc4afca1a0f14b0a7a07178402f1875430c905250a5d4d8d70521418cca1c82a2b3721831087e342d74bab5395c5099fe8710c667fb463e50f59da0b67d7f8b2a4b47896c84954a82d604a570f41fb1a5a9906592ee050b0cad8fdc652a411f8dd0d4aa5c46540479b202f7752985311671be2fca007d87b2f27f16be6a590357aeb87f9f75d9f2c798599f3e8bc2fe5b1e7b5d4e3a779761d983f52e5d4844c7eabbd4e543436954a9e361f2f099bcb603a94f2cfc1f1409aa50aee8644c5a1222e2df52fd8a1f884c0a1a800c92be47b7bed41ceff4ee44700c88e527b55c0abdd04f86ce1dae36f0c2802bc4cc74e0698092fed0e8ab7a95e1bfdf0b6fd160a7c80b0f1bba8dde89740e4caa6681f1d4c926a49ee2088282d18e3646e4ade293e1b2cb5a0155d3ee662d5c468cb5d2ac7557e7666396336e3608d84c97fb1f48dd8ca7f8528b218eeac9a825b161147849d1c2a1ee461150cf8984f492d02221ea43d994821790f5e7357e615ce721817e89495bc3422b98a7e6d6288d75c3b727d1669742b682560660079d8860ed65022d279ca414cd56e169e1147d27ad808290f7daeb665a949b793ae503c1dc9d3f1ae0e57b02d373357408d881e2354337143f7d0b99dd2adf48e6c4f4983bc1de85345892e246c6edbd9aee3747fcc2efa956e3e89e9f5a9be0401933b51d625e25c8296be8ef13720d15411d029a471387fa2c28a168f72e0802d84b3085937f9a83353e002b39596e1567d712e729aa55e648f87043be90b359d3e1fd439091d3ded3b72c0c0bdac8e3033a2b2d6e4e074094b7ff6eb631e2b62ec36e35ea434154e0189b1b1e8b9b60da771cf0077ff267e7e861045d24d0c3fa57b9408d3e2116bf75ba02b4d8cd78701ca9836074440c6c0c0ccdb2cd490ee616bc6d3f8d10ddaf60dfe77003ea326673193d21032997f8fb77c2393ce7af5f1d3d3abef5550e80c5a4e3b7ae75ae3f79950a2f8e5065e8d8fecd2f195aac557d7a4b4a5d1e0c5f1e60c0005d73df261401196c93384fcc271b0ebf0a9da2adee213319ed1fb5bec02202b4a0778bdd48420e701105a135b8bf2d4f50b6939ca0890174d7ce9852473456059b9b7a85db8e4664fa67dbc2d729080df312f3d8970e28e7304510b4828624279cf789d9deebf08cce0c1cf84ffb2abc52784230a5b488fe167632882be37fd1564c22b0dc89cf86ea5ffe6d7880b7199eecb9957b28ba3035316b763a4c0a0691bfe0001a1470107000027100000000027a318400000000045bcc8802441f6c672e644f3aefdda12824a642ca2cce409ef69c5530dcbb2438d64a56ce7010000002be0fd1c000000000022002029fedca3b62676f613aaaea53ff97b497f8799f40a5dc586e67f023c5b1a587500fd0129020000000141f6c672e644f3aefdda12824a642ca2cce409ef69c5530dcbb2438d64a56ce70100000000521a7a8006983a000000000000220020016c1483322b7ef8721387e87a51948fa2e8ecc28dfe33a48327a9eaafd70376983a000000000000220020092dc847c0de4b3408a7b0dced30f091ef9d73e5f8e719794e4267399ed5286d983a0000000000002200209c01da2ecb83e740748c0120909a95cd06387b45902c49810e55f86d34189a85204e000000000000220020c5a1bd48f7ffbc9cd20dadd15f11380a4e878dac76e56080c555134940f2e66ca8250a00000000002200202b0c7734d2e70db4213bca81a2e46458f6e105ff6e2e66aaa3b76133134c3dfe28a3110000000000160014cf85091a7295547ebd2066aecb1a295440a9f6f080f0fc2000000000000000000001000400fd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000000000000000e4e1c0342bb8ca8412a52cbaa04072e0e285ff414a4bf36be1828482705a6bd13501a200061b100003e368b6d07bc35ff7e3475feddeb313912771046d96fa86ec9a5ba2e9024f5df44c2609468b1f86640de3863c1e3ff60042667392d43809462bc3d18d454ad70de3d85e738909aaa1930559a093e363449e7a00c85cb62562298a108131f6f88635d990d4a6de980d076bfee5281d62828c19da4db7843c304c6d2fe742a8421ffb46c26b5e06249fb8fb4ff0b4d1f08d44d8d7d87953db3da6154f8d31d5af9780f6e45f4867fc7d77a1e438dd87172758779414784275dd05b88eafca27ed4424f1249c14d0894202377b2a394e3cc063badd9bd57f66122bb391fff3718520fc9dffa0b225b1eba27be4f0a386aeda91f85b126771f356afa9d1c9aeed249ac100ece76fc8b32903eaa9caeb2c0e3e6d05f69ad025d7ba625d536e6ad06d8b77aceb6d7a01dbb17adee7e17eca234b251e353e26de28849876b8cf88bd57f4a04745d6a88d32f00b35afdf416efd8f84826840e9e974cbd8aa4a102507f0c7e441ad9e1c1275ccc89266bde55f4a3582b78fa8aafa1994ef36b35d3943d3cd326bc70a3ddd377e895b498d79cbc857b5bb2e61f30ac8ca48fe186e40326fd9fef0215e2ef31844bcaec6334490a89dcd42219d64bfeacec7978294ac964b66b49958130a77f7569b083b1c280d4e451e872403454a3947d2cb10ad2f96f92b070f886a5ed771332f51889d18cc2ad941c6638eebefe5fe6cc4b79468b1e469adb08c34982783d49be95f32a774f51db6788d34f26a8af1b31fc709d78338c6c4d9834fa25a910b4b63e05aa6d383d282bba31c9378e3f27f985088e682e11a46a9a1c8e18be9b7c7ef02d9faa8bb08a53d5ffe059d48d440c6364292fb42df68a04aacdfaaba029f1460912b11a6f8eb57578832fb3c10675c7989114177bdb3b587a80856f351177d01eefbb1de210839c9b8b9ba621942f250dc6b4bbf014899b77f52e8e40cb06f7063371251c419bb8aa9d3c0bd83ce651520b6e2911dd38db6c0fff7cc64550320c5cac621765175460cc8f54ee4a0026ec8e57fd789e0fef66c4940cabc3944ff4e3fe10229294e6fcb3059adb4f9868e53b52a7da60f5ae75d4e42e6a94c44e375960f9ed44a56c0b6c49e7c5775dcc1a819547bf3f146e8bdb13f305ea0d15b5616623db4c0657059d07df7acf1062069705ae5699ce86d9d0de16f3afae97f5327fd340693f756aba0bf338af880927cd7f976705c2b511077bbd19b9a842cb7a9b2f713f183c1e4eac23bee3ac081be66de275451773444e81536e183c8e2f033766b854cc92773205abb0f39d90e762c1f0673af8e4051205f752757d2deb7dce6adf8a55297a3a56fe1265368970e8d480b8ee61fa38313e5a7069579f10ac2db37c05c58b38544a8750e186c43a153c7780cb8ddfa309fac85b487b43b1452624afbc4005bbc0777eda63c96edc94016d1fe880dcb2faab472802921adc29dd3410628b8431186a7ef3a6839eb16123ffccf40c4c0907764e40d1dabbaa12a3a9ed3f341c070fb622600b6f4595f4601f67f2d03e7221d57a20bad2e0fcde738107a57fea1e1257a89970bc94517fecfe4990c67f7bfde2ad523f0f4dc760e0e8958eb32c6a7b0e7d952e75fdf08550ab72b6824122697846e599c8a2cd7f3066c8c444e2bb41aa38cb063493af2106eeae5228247706a53169f306d6b0d74867db69d29e9a6eca2b77aeb3353fa5a593b9f4fa379ffd01464a6929841f0fe934d68c8ae9e523a87d02ce5df5636cc5dcd72536d044cedd2a1d1e9047712a399627a44c8776cc5d0b2fa7e3dbef52102602f97090afab7a6070af4a1e50d19fb646f25212a7e750d6d0f765c0d5324721afe49d1070a686b05dafd39114bc65cc11cb30cda09b872c79521273ff2f075ebc4a2e5444c6880b3854d623bf1c42be1026d8b0409fe0001a147010700fd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000010000000000e4e1c0175abe3cc14d94456c949372ed6ace0b5b92e58c595a6297e6c1cdfbe5bd750a00061b1000025b15002f7b27451dba144086e3ccebd9726c2e1af20883b676631e13908f2a8a0dcabd2fe4d39cd5b0c9d007a4dc3f1a5624208693e461d934fcbb48428eb1cfba41682d19ab1221e11648459eee58b855ea196e854047d82869b62b2efc978f520eb13e35e3752e53026a0f7b22ddf65de7a4e3fea56fc86b9c85cb8df4cfd0262cf3bbe1e16eb25574db8fd56cee33cbfdb73073f700b9992c19ac808354db41e488aa31d6b8a3be8d151732aaf6353f30a18cd773285cddbd3b279e6455f9b90aa37d7055ccd9fdafa66ab0ad216fd123e4a4aadb25f9e004c5a8e794abced52cd6e208d8d2657d03f5179b6353133a9eaf151cf5d47298058ad85ada4e42d29b9c61524ac20264d3b6551efef60789c6a91cf1ce4e069e3ad123fba8d515d705c0e2f3aec108ab317116d593100a4db4affd32b4091a1368cfe3c91b422ff53c75ade0ed61dd562e55cd674e5a0c5ca5952c2204c401d9c61c25b565c83c38cc09eaa6e25c42d234875f2a353813b8db46f031f0392f73a9cc8a88925347e04cfb477f3a7c5578edaa447927c048aa631b4f3dafcb608fafef2e125a549e029f2598cae566f93febee55ab1175359913732673a44fd2a8e28f71e281917c7d0fc04816a5774a3411d1db8f290fe442e3d3141ab366633f07e46528d44528a422c9225129b0ae4e7f3b76c9c8fb25b9c7722be9b5862ce866980003752c178e7a94224a2b0b84fa1e2635b5e2207ade589baa96372d4453868b3e01e9ac2d1d80bc0c7e06a22c205f85e578d55e3283e9b2defb70f46a6084ea40e92b25f35550e7f3fc3c9e764dd4eea7b85df10175a0799e6a46cae937ddb1fbc43795a88850870cdd0f5bfb4506f227b1420f6e8d39b7ccbdc888a7df7066cfcd8da6ba6bceb2aa80bf14fd5d096f604af41ae92b9f4e3c6a7f29208e8b3fb70cf81fd56ff17ee56adf35cb18b39a067f67f9fc3f94e8c30d44760da2322a77679bd6ca0da0e47f206ef7b644448e7330bf19455ca777418896f0e07e551ae4995c7ac5026a7b84c0cd07baf9569135ae4cc3893339ac886ab704b37d46aa3edcfeaa6c4c8aec3ec8bf8ffb51a591ff02f04367314c267a3152070069950d384b99235e5398547d5084c4afbe86ce6a6a60964b8cd89b20c690e8de12606f64ea559a36431dc98d6189a3f15d439dcafd831a9dab64bf8dd5722c91d59fab00fad5b27a89d5f4237cb20ca55648a8256e8a0ba18394608840336d15a84451821619a2363eb51c5ee83e1722476106248962ccb5f7bd843e7dd14620a2b40107d972fa6b1e4ed7bd6b13b659b012870b64906229c93c2625b02da587d2239e09d64bec81f40c50398eb666ff8912711c2377165cbb0ad16999c0b09bc36f354cd495d63fc6cd7b89e4063ff1b63b319afa4a7a64da69aa41ba9af3131df5cb384ce68ab74898d699ea09730b53d3d862521927b0cde151844a9eb11aef8e0e25a3fb607d57c2a9f83eaf01f1c387e09be5fc5ac0eb091359ca26c8fb36cea3ccae8a63e57dcbc60ee66975bfee8964cfdfc840b114dc6ed5eac0babd26f99ec251c45ec602a44fcc6965c0d7c065c186569be6426658b8a5b32fc2dee315a0457e963635795a0d699db9587eeacf8f893699410a87b6390d8c8736552e325f5ccec1b6d4b45002b678649b234301610ecd605f9e56c5dc45d81cccf73e442b872369cbbd3a03eaba79c0af36e23ed97cf73808dc5ed0f5fcd751772b57e2666c5a70b1a5af61a0dd461e8a383e8426369ee6a4e03cf256a6bc3570997c3c06879946ffdd2924aa329e006759c0d6b564c89198c0c09f88c2c9d0c2cd4e0163e8f98145456cc859f55aa8d11a9969ae253d93d56d9fc6a386ae7c6ee884f483913bd739124f69b4bd5238fdc33d860a9cd4670de5dafe4a24fe0001a1470107fffd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000000000000001312d00af8cea7a0a1be6687852d7f2249be3b445ae0cddc097a05ea468ef797567856d00061b100003e5507553c137054ae33b5d7cdaf142fbdbfae51246ea3db4441251aa49dac2e8cac063650741353682da85f0df1b1db72997177b74940bcabcb961f364c1861a493066a9cf2533aca5f866b280419fe04f9e9807d3049b759f78d7ce322554ed7891184e73d934c62f88ca33f7cd75d56617664cc674d74f553b8674093e60f014a89e1ab4ea7adde5d54f28a9976d2a3ddb20854b3a1f3ecac8490a9243234c7ce9c5df1b0e45d862c847fd93b99ed1f4d5fd5b06ca416ed048433782d4bd867d36196a7ebe1106f687d0868cd2f2cc1fd94f999de8e59e9a9b333aac6304ac7c01cb91d20c09d4e4f38397ccc1a0cbb46865b7292b84d4f11a783abb0d5da2e31fdc7c2885c6c90053f716264d0b8ba1ce53dc72e8f04e702e319029ca234b8bf72f5321382723010153eaf113e0c823914bf6ea319a1083efe2f0736e1f9b36125c4b77b9e4a581e6fecec959be70f2e21b8c9bd568641d45feb3ffdd39491abc973f310cb1f9c46a874471e5edc3ea7d4063274a9e10e2d21a9e836e99d386d062ac31bf19a14ad6d39c19752eb749c4dacc64aa4591228c039a1f3c0dfd919a2576edf70cbe018075d90c94bebbb91290487cb9cc613eef312a87025891ea6d8b02435b26abf72fea504aad333100e4f14ee916c678c6c9a42b3421bb8f7389b642f89b5abaf99f0df25e5859bee216c3845bbc9fdb032acb88cc144306cc17fe754280bccfecd86670d6994833c4847c71913caed6d904525d22d28aadf3d4e867a617f20a9cf61e95152eb1cab90bae7885da01effee11b7780bd2398d37f04096e7d14252e2026f329422c077762996f45a7c4d546d7ad0422996e659c98f1feebc5c73eeecab837c025a44d94cb5ee39d5a1bbd95973e8e5cdd97cabb7b8f4f86a5bb228c0d12be6b9b2032c54795f4f7180828e94377bf6e0f56545dfcd5252da0936d79aef70caa8715e8416579ca1a928c2aecfa1e03b6cd795528a967fb3dbe524996be92b6168222112063077fb39811ebf9dbb97abafe1b315488c8812224faef56c1eb86837ffcf61bc22a60de7e4f6e0df67c332389556df7da838200dcceeafcb6e8a94aaad013c30934ec0ada06fa17afb5414de4812deb6fa96517df57d503e1e521562ed62a65073252dd08aafba343c029e04faf58b4c451061f156669be5ef68a2adff506cba6351e407d3c6077b71807f21e93d0ca69843f2d5fd6b7c7f415d740fac1f1f8c8bd8f95297ee1ffafbe1b41c548b69dd6d211352c556f14bb4e5af76244e7656f882452201e2bfd90a87413904081a78c2d6849a6a07ad77920465141da19034b70c3edac53a560e7b5c07923c9bc6e17b8091257fe7431e7cb18d770ba1457e62714a47beb8e503874f24405c49121d2cb024edee3458b31ebe1bf40a13f564b8ff95dc537ec926311f59150544fcc57efcef53d9f43c41b0dc32f93b91d2663e9f6b09f9483b4a788b0642d667c722d10ddf09a70886153fc8c943a9d6f3eecec3ab7144669c10b3c5864a174c7343a74e4b58da29f0ae480d5be8adb5d5b55e9cc3c11da1a935c2b1cb966d85612b6c4bbd42aaaeeebada9f062175f502c2cde7daa345239f99201be98ae989cd8a3cd0610779fd413ef1c6c48cd6e8bcb454b847815016e214f1e6162df1b388503b06bad1e5e35ca424fa1ac4407757504e311f4ee0f4d8e5dce45759a96031460842697d577205255b9ba2bbbe4eb08528a9224087a40129906655170405ad4491052ba71d6dde4000b7d7154634db60db540a16cc430b5998dc1465bc57b5a4125c967e4fc451d66026b650b9a70c26998f559793809df549ca1d5e730db0759ed40d3a030c8a1c6bccb28b5fd9230c55ef83d23b369938200099274978bcc017a96bd1b506c6d28b46788f9a483f3e0b234fe0001a1470107fffd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000010000000000e4e1c0415149b9751963f87c1583f34e4fb1bc5599cb48dc36e0ab744c37fb0630f93e00061b1000033fabe908e0bb47028814d9361e2518822ec95a72c8e611b1562fb595ad452db3272b17a6f9280115fa6e17434cfc1639b3b1795ff6cfe4aaa80b3393100ba4a2308b9e42dbbb3cba55bec53bbf574b9307e2023f3e13e6d0424ce7b4376280ea0d3a187c8585212fe9effee9aa102873a48f1b71a2ae81cc97fdf6075b4ee17716bbcbcd930a23d483f04907dcf89d9d55d62ac6935ce45afe8549de48a363c595876e32e8ba33806671cb54a92a81409f9bc8964983552f709335efb962d17c2bcc348fcf9af666ae7cff45833b4b0449481a6cc869a6cc2d928ccba9499e6415a52c455ebce14cd94a36fa0bf127682e890be81a1dcc77dd2b94f886103d793ffce97430edf04f4b8a0aaf9622b34bfceaef96bd6848717430d2212f2df30105354306fe8d5d452c32e1b0f1872cb7c953295d2794805fcad0c50cebd26b14cc903fd995d4eb397d85d3bbcc92a4b640ec4448a16eaae0941f2f074ea2f292b55c8cd8051f5b43c4f0d14ae04038d06216cf01b344fb3b8b42ae2680d7998c5f24c16813dcfc521525b5bda78bf604edab7edcac3934cbb9e5e1baae044ea136613ac003e86e3caa9f0e93c38c5997a17ab472004726730f79248be824fbe894422e2b07f951d31ccc32c7f513a61c7e0a4f3790faf2dca5fc4afca1a0f14b0a7a07178402f1875430c905250a5d4d8d70521418cca1c82a2b3721831087e342d74bab5395c5099fe8710c667fb463e50f59da0b67d7f8b2a4b47896c84954a82d604a570f41fb1a5a9906592ee050b0cad8fdc652a411f8dd0d4aa5c46540479b202f7752985311671be2fca007d87b2f27f16be6a590357aeb87f9f75d9f2c798599f3e8bc2fe5b1e7b5d4e3a779761d983f52e5d4844c7eabbd4e543436954a9e361f2f099bcb603a94f2cfc1f1409aa50aee8644c5a1222e2df52fd8a1f884c0a1a800c92be47b7bed41ceff4ee44700c88e527b55c0abdd04f86ce1dae36f0c2802bc4cc74e0698092fed0e8ab7a95e1bfdf0b6fd160a7c80b0f1bba8dde89740e4caa6681f1d4c926a49ee2088282d18e3646e4ade293e1b2cb5a0155d3ee662d5c468cb5d2ac7557e7666396336e3608d84c97fb1f48dd8ca7f8528b218eeac9a825b161147849d1c2a1ee461150cf8984f492d02221ea43d994821790f5e7357e615ce721817e89495bc3422b98a7e6d6288d75c3b727d1669742b682560660079d8860ed65022d279ca414cd56e169e1147d27ad808290f7daeb665a949b793ae503c1dc9d3f1ae0e57b02d373357408d881e2354337143f7d0b99dd2adf48e6c4f4983bc1de85345892e246c6edbd9aee3747fcc2efa956e3e89e9f5a9be0401933b51d625e25c8296be8ef13720d15411d029a471387fa2c28a168f72e0802d84b3085937f9a83353e002b39596e1567d712e729aa55e648f87043be90b359d3e1fd439091d3ded3b72c0c0bdac8e3033a2b2d6e4e074094b7ff6eb631e2b62ec36e35ea434154e0189b1b1e8b9b60da771cf0077ff267e7e861045d24d0c3fa57b9408d3e2116bf75ba02b4d8cd78701ca9836074440c6c0c0ccdb2cd490ee616bc6d3f8d10ddaf60dfe77003ea326673193d21032997f8fb77c2393ce7af5f1d3d3abef5550e80c5a4e3b7ae75ae3f79950a2f8e5065e8d8fecd2f195aac557d7a4b4a5d1e0c5f1e60c0005d73df261401196c93384fcc271b0ebf0a9da2adee213319ed1fb5bec02202b4a0778bdd48420e701105a135b8bf2d4f50b6939ca0890174d7ce9852473456059b9b7a85db8e4664fa67dbc2d729080df312f3d8970e28e7304510b4828624279cf789d9deebf08cce0c1cf84ffb2abc52784230a5b488fe167632882be37fd1564c22b0dc89cf86ea5ffe6d7880b7199eecb9957b28ba3035316b763a4c0a0691bfe0001a1470107000027100000000045bcc8800000000027a318401078a5654313845429e9ff89f5848fb34caef8f89701d89c1f989a2169a7d1ea029ced6a1eb5263d34d0d6877178f5b9027cbd9014780197c22fb6fe6849d8c96600") val decoded = channelDataCodec.decode(bin.bits).require.value.asInstanceOf[DATA_NORMAL] - assert(decoded.channelParams.commitmentFormat == DefaultCommitmentFormat) + assert(decoded.channelParams.channelFeatures.commitmentFormat == DefaultCommitmentFormat) assert(decoded.channelParams.channelFeatures.features == Set(Features.DualFunding, Features.StaticRemoteKey)) + assert(decoded.spliceStatus.isInstanceOf[SpliceStatus.SpliceWaitingForSigs]) + val spliceStatus = decoded.spliceStatus.asInstanceOf[SpliceStatus.SpliceWaitingForSigs] // Local params. assert(!decoded.channelParams.localParams.isChannelOpener) - assert(decoded.channelParams.localParams.toSelfDelay == CltvExpiryDelta(144)) - assert(decoded.channelParams.localParams.dustLimit == 1000.sat) - assert(decoded.channelParams.localParams.htlcMinimum == 1000.msat) - assert(decoded.channelParams.localParams.maxAcceptedHtlcs == 30) - assert(decoded.channelParams.localParams.maxHtlcValueInFlightMsat == 1_000_000_000.msat) + assert(spliceStatus.signingSession.localCommitParams.toSelfDelay == CltvExpiryDelta(144)) + assert(spliceStatus.signingSession.localCommitParams.dustLimit == 1000.sat) + assert(spliceStatus.signingSession.localCommitParams.htlcMinimum == 1000.msat) + assert(spliceStatus.signingSession.localCommitParams.maxAcceptedHtlcs == 30) + assert(spliceStatus.signingSession.localCommitParams.maxHtlcValueInFlight == UInt64(1_000_000_000)) // Remote params. - assert(decoded.channelParams.remoteParams.toSelfDelay == CltvExpiryDelta(144)) - assert(decoded.channelParams.remoteParams.dustLimit == 1100.sat) - assert(decoded.channelParams.remoteParams.htlcMinimum == 0.msat) - assert(decoded.channelParams.remoteParams.maxAcceptedHtlcs == 100) - assert(decoded.channelParams.remoteParams.maxHtlcValueInFlightMsat == UInt64(500_000_000)) + assert(spliceStatus.signingSession.remoteCommitParams.toSelfDelay == CltvExpiryDelta(144)) + assert(spliceStatus.signingSession.remoteCommitParams.dustLimit == 1100.sat) + assert(spliceStatus.signingSession.remoteCommitParams.htlcMinimum == 0.msat) + assert(spliceStatus.signingSession.remoteCommitParams.maxAcceptedHtlcs == 100) + assert(spliceStatus.signingSession.remoteCommitParams.maxHtlcValueInFlight == UInt64(500_000_000)) // Signing session. - assert(decoded.spliceStatus.isInstanceOf[SpliceStatus.SpliceWaitingForSigs]) - val spliceStatus = decoded.spliceStatus.asInstanceOf[SpliceStatus.SpliceWaitingForSigs] assert(spliceStatus.signingSession.fundingTxIndex == 1) - assert(spliceStatus.signingSession.commitInput.outPoint == OutPoint(TxId.fromValidHex("e76ca5648d43b2cb0d53c569ef09e4cca22c644a8212dafdaef344e672c6f641"), 1)) - assert(spliceStatus.signingSession.commitInput.txOut == TxOut(1_900_000 sat, hex"002029fedca3b62676f613aaaea53ff97b497f8799f40a5dc586e67f023c5b1a5875")) + assert(spliceStatus.signingSession.fundingParams.commitmentFormat == DefaultCommitmentFormat) + assert(spliceStatus.signingSession.fundingTx.txId == TxId.fromValidHex("e76ca5648d43b2cb0d53c569ef09e4cca22c644a8212dafdaef344e672c6f641")) + assert(spliceStatus.signingSession.fundingTx.tx.sharedOutput.amount == 1_900_000.sat) + assert(spliceStatus.signingSession.fundingTx.tx.sharedOutput.pubkeyScript == hex"002029fedca3b62676f613aaaea53ff97b497f8799f40a5dc586e67f023c5b1a5875") + assert(spliceStatus.signingSession.fundingTx.tx.buildUnsignedTx().txOut.indexWhere(_.publicKeyScript == hex"002029fedca3b62676f613aaaea53ff97b497f8799f40a5dc586e67f023c5b1a5875") == 1) assert(spliceStatus.signingSession.localCommit.isLeft) val unsignedLocalCommit = spliceStatus.signingSession.localCommit.left.toOption.get assert(unsignedLocalCommit.index == 2) assert(unsignedLocalCommit.txId == TxId.fromValidHex("88d4950aff3bb218308d09ca650ac4648f4cbbadbd1d0f333c0e6fd0a483cf07")) - assert(unsignedLocalCommit.input == InputInfo(OutPoint(TxId.fromValidHex("e76ca5648d43b2cb0d53c569ef09e4cca22c644a8212dafdaef344e672c6f641"), 1), TxOut(1_900_000 sat, hex"002029fedca3b62676f613aaaea53ff97b497f8799f40a5dc586e67f023c5b1a5875"), ByteVector.empty)) } test("decode local commit with commit and htlc transactions") { // Before https://github.com/ACINQ/eclair/pull/3099, we stored the unsigned commit tx and HTLC txs in our local commit. val bin = ByteVector.fromValidHex("001801fbbf4f2af338d22194a95c045dd36c1eaf9c090f6668f85e9976a8cb2418ac5901010340100002aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa000999383d0b615906c5db6e5faf402f8773d6d1f0725eb0efe6f704b78f0325c95680000001000000000000044c000000001dcd65000000000000002710000000000000000000900064c0000000186b0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000180802aa698202bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e6300000000000003e8000000003b9aca000000000000004e2000000000000003e80090001e03d634673ed2604fc4bec7b755da75f47baf62f271710775c1f283b33f7ec9816e02c37e84f926a720ce810decfa0f080ca43aecfa9bb52464656532ece22361b8920395356cfcfdf4473db386008e6cacbc489d0cade2d59bc502d1fefa0e67652371036a529fc054ac492739b0a286456849a0bb2fe129bf858bcf603247c1f0ead492000000140800000000000000000000000000100802aa6982000000000000000000000000000000000000000000060000000000000000000300fd05b1fbbf4f2af338d22194a95c045dd36c1eaf9c090f6668f85e9976a8cb2418ac5900000000000000030000000000e4e1c036e6f4bd1403580346f7ad969d0b45f035220481207ddf9e43cbf0b49014e96800061b10000332c98f379daa236da56ac028f19b025e0b1fff57b4051ddecfb9aed7f4c676a7302ce27f88b74e6d77ee96dfc20b29b73d239bf369230407406dd3d23f3d55f9d6f16b0cb112acda00db9c8d6da811add226c8c673476eebc7b91b1995653afd9ab7693419809bbd44f32be98144911079cd5e0ce023519671fb12fea4b77ef14ec40dea3e1a13c0df50ec2133234fc55c0473105a0fa2ab10b9ea7a3db42f02a9270a9ae032b393ad7d192b96877a5770c3803a4a3eea32e61e9a2854affdf4754f0667bb95bf1c07bd63decd294a167fb7fdcd70e527b27aeaeb48689be4942f0ae3d9b49ae78adfac66d726c021ef0e33af84c4840281915c8ea4cd77ca0441e8810a27c517e9a62385c28590c221dc38f3eb05b0aa371d3250d4ba373e2b79adccf6751f8ae1eb3e363c2b01f9592b0ec35951580ab88eb5069a1e708461e37766af7c3d86c8ffb3ad1981c68d7ffa74f9d4379eb977eda42146871b367cec89afd8d9c0f434e1c132f28de409880f2fa540f16685326fa09e4e4ff48adcf7ec29150ab5b2f581841000401ab4412c1ab3dea8c904c7c5e24421359de09bce12a2a42830a699756530f75ba3febfd16cffc73dc06a4faabb01d7d8914f4a17a0e463ed824e52cd10fa6ddf4bb9b3f7dc57137b42d7fa2acc782414a65813af01b27217e80e69ec3e4b6c74e2dbc87f1cfbcc88e9f450361d40ee5dc4d0311618b01d3030d47a96a569b416a028f6f356f17348276cda2a68816f2a3ae09b5495b1f4d6d3eefcc4ef3572cff82e4ecc6a32adde0142b665123831db84511f3e878bb1e9c06ebfa73b19ced2dd905a51a7eabb4ef3c13c2a874dfc200e2408dddd8536b78d9a60929d47beed0c0dcce1c52ff1aa29a5e4446acd66aa887b36d63967b23ceaca0468f7ac5f616018264b2f66fde267cb9d5f3f9982297bb56e16b93d6f5f028bff9e9f76475cdce9a702747b24ffc91620282a1ddd6dc721225bbd203bff7e3851aa22d6f7dfdab44247728d5cd6ea6f5d80b9928cd28c40187b5753454f96ebd014981fcf72eab4fa1937ff9a958e50b40c4527913e5b9b30dabb8b2d695c19fdb1704540c70aec09f551df64ead2dbabf1917a038e263f0c0166a903f949da0b9a9b285a8ad2c3cfa7f5ad57f07a63b1383a55ed294fecb2225f9640901c7dc3102603c1adc574bbf43730f93d64cf36dc9ddbef0ae5a7939e84987be7ca88081567ef7ef89da7618658465cc05a115b62a25a2e14584e1fe83d5c9cc2b30f8675f7e51f256139f836d8a70a88a7fdec726ffdc6d67b4bb5dc186801ed4fea79efb93a03cd3f17f2411d69c8ae6f6aaefe777d44ec7199c267f14c6a60eb7071cf67f62d453eedb7d54e048dda18466a1c9b47e8920699473d3559c5e223a80e09f4a891fd68ab2cbc12ee0524052b773fd5d8a872754e1336883edea17990de729f7a119bd4f2ccecfed6ea22ae5de41193c6f51ed4c31ead31e5dd661f8dede9d03372645f5a3b5632075ef844f773a52559e46fc2714b5cb2600df2e74275fe1d95e8f3f44dc91840123a885edd2e2d972ca1a0917c54859670ed3091638a0dbc9580f5b25acb5b8688f6d19e00782fb7f2e965c60dfcc8e53b15a1ab399fd09e1c6bcccf6d35df8f69ed2854039041764e101b4e60fde246734971cd98493c45db7fa12b92969ba79ccfd35409f47c84b38f3b2b11e6614a278dc8717955b0500f3d9a88179be50ade3626472a821b15e413c69ea6367f8b56638ee34070adfb6edb8dbd03b595ba089317f9b7b93b317699284d7a562821f74ec92b9e55100e804232c2231d6400b70e2cc6463f17f778c7e98dbdb6da0646a23c6e90e07f80c42a61e23314464986b3cedc9d6a9255b002d209dd2e3576658692b2f3bf6036a263663f9b99074cd514fe0001a147010700fd05b1fbbf4f2af338d22194a95c045dd36c1eaf9c090f6668f85e9976a8cb2418ac5900000000000000040000000000e4e1c036e6f4bd1403580346f7ad969d0b45f035220481207ddf9e43cbf0b49014e96800061b1000034086ba1a46c484ed86af94562b643be22879d2dade698f04ec40dab91997e719e19f921b0bf7f276d42672953db62c9b8e4569ef8df2eaf0e4c9349e3b2691f38b6459430b08d288ec8a3d8dccd2e420cf892c8c2ef053d45c21e03dfb438f78041aaf0df9a11c2569476f8f3a525c412f37819f1784fa8f47a53f110f1432a4c55b684a6ad5920f74e378f07802907fd5f4dc6a6aa00bf93d88a3d087642ba67fdc0d8da3fc7b4463b760267d1e55d5ea5158c9b0633f704d57d9f72b847dc3efe35ef861066a9d8d3a555db9344e94fc428ecec960570cf208f3c690ebadc443a8464a74c4fcb32e7e53994d44426ef26402bf9317840b5ef6c41edaf43e9b70da123a06bc31ced8499cd08796cdbdc9b80e6a523b483928afc032439f8e0b47af48384c2004cbc9decb82ea36e62fd3ca07f20f7729684dfa4793ae24a4d1977a5a37f0ffb9f8e5860261372e5107ee58029ba6058141a39a3d4390a86f8a38554d87bfcbbaec495cab0939cff761a316956f6db23fcd3d552b2a6f620d3a2b01868f712784d5a8cd3c64d394f1f8abcf6184862419da136db11f53292df1863eed4e2a04a0ef59a573b800913e27e11fab79260f1a79c35e36026bd77ec852618ced1b5b24909197964331da3f687daa82a34d2ea5f73e2bcc6bc55dfa9e07187a146101c224755b1436e292b4df8597418ac9dc0f46b39a7b2630cdd73383c03f0905a25d3a52ab2b46b49e407fccb7e2fd943603710808b122195d22b2f2d022aea4b0dce9bef1b40e37600d3d652a28a144cf39a088f09d19948fae90142384066cd56541d2003b38ecb5050beb6f618dd3c619973ee64cb76a2565a2ed1ba6c6838dac3698385fc33ca4951f6ac6ae7dfa2c4bc027e3fefb520be24b5d26d8fc929ea1b14cd9427997b7634893928bcaa32600ea7b65a5e96fcd345962f5f4fc68905f9286db7bea219757399d11774491c5159663e9295bf5911eec5a1dc45505bd425dce8ae5e09fbef489276ced951e20d2154d7bfee8a125eb565e0b980abf53f1fd031412233bc0868568a7314945af9c276e52e09add4be6d0ed8ce30d8da4c62cbaac98f26a72bc15e0b5ef185431362698e6a1715a6768070f6216f1380dd1a643ee772c6b93b900afa8f05dccefabb46895402988e39b66839b5c4b0c53857d3ba67ecc279a29351b84677f98e23fca658413c0368227ce74ed9a4fc4918495312126514922b0e361df5c9cb8c4e44b418b885cb812d27958635864dcdaef25c82dcdec31b200a14e56e54de368738e83e8bf94dfe930b23d8544bc72f1a6ef0a74d2e8e84c786fbf01f48f30263d7a080a33fd7d9eeb432316127fed2bedc060ff55494ecc281a92675bde4f42ec73db25c1023c8fb616a78144518a240cf1873f4d7c61200e70e21d165c2bca5c50a0ab60bf4ccf4738f506e7110feb6c6e9782a5036a800e41c128c7c75aa59207d92e1e4ef0b83e3177c12fbe056c332b9db9b5903257c9b113812a485622a6e0fb7add7be65539d195af090288400df1ea795dd08222f2954e811742d3d8d11ad70196a695871ca39ec9e42c1c96bda5c4a03098afb7d46fc90bd9d0e62b2ea1b631dca3dcb9e47f6bbe17e26c04f15fc855120d843763d13972a78988445924a59b361591f249e4c95f1ecc6b994d107a7f47eed6aa4d88bc9bf259da17a7cef6c6aa4331211c56d4252f6ee1e7c7451ae30914c3e78aab657f4194226c79ba19a9569d9ad2eb88ce9d3f19384ae58619d655aefddcf44a1532ce9697368a42aa78b2c13361b323718e19636001ef8d83a78e5f956b9044828db98ef283524ea4e2f4f1564eff7e7ee9eec80d80372d2bae41da1910055e8a0b50b53c1474c188cface6edb9092f7caf99cf354260f95fc93151342113084192acaffe0001a147010700fd05b1fbbf4f2af338d22194a95c045dd36c1eaf9c090f6668f85e9976a8cb2418ac5900000000000000050000000001312d0036e6f4bd1403580346f7ad969d0b45f035220481207ddf9e43cbf0b49014e96800061b0f0002c711f0fdd00dc0d59cc9744be7844b230c3e6d896640c686f7d1c93fd005fc90d43ca381ddcc524d7c924ded0fddff49e674ff3fe851d356b17c73e361d7326917281c6ada5dee7b967504f0b3e030963bf12ff9449a0fdbd53a9248b58339507e29e19f5c2591970d6afb7abd49ca61222862d902a0de2417f471bc497842848a897cc7d3bb19c03816c0575b26d5b638fce6f15f0b546614748ec5d0e14bfbb2f5e4e2d65d17f98481b972066a3b7b97bd5e587ccebfc6e7e3d6885ebc9c6c0e09d3d4ddb58b9043fbccf297bd2a9783717a31ed0b39be3c14297a4970fe0a7f1154781cf315d9e4fff61edd2d47c32f1eed011860a4cc2c0e057f35afa6c92529be18b195fb0fd7577042e3f31d115571579959fd329c92fe0d68df2db844ab8296e95f5393810b2744fe0324c118e70c26cd912f4296d7bc2199e93ba20907f5713088d779e5177f16043baf8d59f4208229817acae5512f6eec8e9680204593c67508bb8246f99da80e53cf74147a911ef6891b1d61fcc43135e9a3b7d875faacbd051d277b275000f284cec9499a87a3ae5c62327b707e50cab7d7587b6c9aab7b556df9d3ab865e544615ad397d67063636c9d675a092ddf6ebf00110ec728ca8fa686882cd18fd6abc46f57978cda0d95fcf075c1b35cf08b151f6779b85f9a0ab7da7eae71ed0f8245372b7feb81b4d243a153b7330f90cddc9f18763373f4727780b2563899bdc5b8f79903c5a00cb1862e91981360c776ab503e46749344e5cda444c9a6bcce40aa2b4f0f5ea066a20171f9cfea53cf58fe426246acab1f5326b06e8baff1f2d76976e8a4d6c9001b7a2e8f739df41d80f54e965b5cfb80d811c31bc32b0d91041bf28f45aa5a6ab15abe6b44e9f27dc980e119f2865731e9c5dac0161bc3a320b30467fdc6cda7d105fce503dcf6b62d8e8dd61457b401b849990049926d3715b2131eed48652ce60ce3dd4eaebc7d19b0a35d4c561c2a488d1ec0d480ebaad47ff63987331b882842b36712e6ca9581532251e0ffd7647d62295b33bea95818d04ee37f5b7695b85dd28700c8d5dad87aa2786006590796c354804810f277404c2437c2b70de2d2b10ed91ec4305e80a2f18666c791039c0c3308884012a8a2c92ab76f69c0404ae784a9f748faae201b2a58db8598938978a159e14dfd05c2d10db3d6072274f5f92b7f8cb1d30fe73842272ab6d2bf3901b81e85ef6b1e0a4f0821cd25be5e6021f6702ee804f2ae51382110173a9b206f9340b891985c36eb64af00eaec166507524bc7a2b32ce1e8de40ec127cf8c3f38dbdb4e483e1869faf6513a131853c11e9bf1717f45d3af8be50eee21c8768adeadd9d79df49c862a0bcd1a2938ab43909ac155febd48468285266148c8dada96d85065251f18830299b8383fe694b9561d32a59e3a5f4a95324cb6ef2760abc947351d19882fc671d7ba36510139a186308b8d7ca5ce5c430860e21cb6c218452740e1a30f79541899bca6cd13414e003e847778171bf8ddae4ed9ad9dbc0ed6dacbb603d8f4255745ce5247508222c0d8f46a4a517e6ee30b02a7e690b7bc45e5d260bd4e7d0d27131f088351f116d85eb9e472d01d78d3440a3b6dc1d3da2a3c6ee57e78dd2f2138979c880df9cd94574929895e361a4d43fec2f5f4890eefad4bac1ef4d244d64641e600e4102c20921901b903d0ba382995aeb027906c0f1283315ad454f986734932fead46a7e06edc5ef3817f7b94a831fff268ab255aa251ad08df630790b100b153b809d31b68e6080586d4d9b271b36c67c86a68f4a9034658b7fe8cb7ba94af8c0503ba3df909a9ee8e394bde5e701013f92e3f45c2e67f27e8fbc206721430ba9aa3657ecb9dbf0691bfbefbcf3ee20981d06d8977a7c401f1bd1187d3abbe3c3e9ffe0001a147010700010000000000000000000000000322298336489c06b5e7fc7da4d6dc4f1ffd2a59b06bcc5a6e81feff828cb841d00a5e020000000101010101010101010101010101010101010101010101010101010101010101012a00000000ffffffff0140420f0000000000220020520f04b40b1ec2c985ede593cfff212cac1088275f72e9158a4d05e7f95f0c8800000000061a8000002a000000000200000000000000070003000000000000000003000000000000000004000000000000000005000009c40000000008f0d180000000002faf080024fbbf4f2af338d22194a95c045dd36c1eaf9c090f6668f85e9976a8cb2418ac59000000002b40420f0000000000220020520f04b40b1ec2c985ede593cfff212cac1088275f72e9158a4d05e7f95f0c8800fd01600200000001fbbf4f2af338d22194a95c045dd36c1eaf9c090f6668f85e9976a8cb2418ac590000000000da4aaf80074a01000000000000220020f76d112d2b74c50fe65bb3f151b3c98b737e72f02125dd3b0826baa5b596263c4a01000000000000220020ffdacc7e0491932fe22a1411c331e3588ead7849eb1fb2e11318656f276cb93e983a0000000000002200200af572135c9b725f663119cbeedbb7726c42878179f5f52d798d790ec9034abf983a0000000000002200200af572135c9b725f663119cbeedbb7726c42878179f5f52d798d790ec9034abf204e0000000000002200200af572135c9b725f663119cbeedbb7726c42878179f5f52d798d790ec9034abf58370200000000002200207ce427f9be2c35185ab7151f477327acb20657856b69cc652c1a802b6254ac3600350c0000000000220020c76babcbc3844154035e3c5b4138a4f20c06dff53dd04810a1d916f800d96599b2fa742006e35027e3a48a8c39f686e51c10c648d15d6c0543fa6328f78c7453bc09d8504e8883c2fc83471f799ce1f57b9591b9b4a95dc0cf9cb1ed878e0d67de7bf40200031324dc5602643922fb60fb2c94970db12260850742e9c0f9f026bf28119fcf67e9c6020000002b983a0000000000002200200af572135c9b725f663119cbeedbb7726c42878179f5f52d798d790ec9034abf005e0200000001dc5602643922fb60fb2c94970db12260850742e9c0f9f026bf28119fcf67e9c602000000000100000001983a0000000000002200207ce427f9be2c35185ab7151f477327acb20657856b69cc652c1a802b6254ac36101b060036e6f4bd1403580346f7ad969d0b45f035220481207ddf9e43cbf0b49014e968000000000000000300061b1065cffa80c72f9e291456fabc3771865f4993108f8a90deddf041a9bcd523d3ec2d5361430705447f87312b2b858327f5f0c4999b7bdd7da84dd270189304def11324dc5602643922fb60fb2c94970db12260850742e9c0f9f026bf28119fcf67e9c6030000002b983a0000000000002200200af572135c9b725f663119cbeedbb7726c42878179f5f52d798d790ec9034abf005e0200000001dc5602643922fb60fb2c94970db12260850742e9c0f9f026bf28119fcf67e9c603000000000100000001983a0000000000002200207ce427f9be2c35185ab7151f477327acb20657856b69cc652c1a802b6254ac36101b060036e6f4bd1403580346f7ad969d0b45f035220481207ddf9e43cbf0b49014e968000000000000000400061b10c5930b24a9e50aa0680daf5ab04d82e21fe1edfaf2455e07e08484660cf4c12d384c22a3aa8b32fad23ba7f451ea718c718439b0b256b120f22eeadc5b995f491324dc5602643922fb60fb2c94970db12260850742e9c0f9f026bf28119fcf67e9c6040000002b204e0000000000002200200af572135c9b725f663119cbeedbb7726c42878179f5f52d798d790ec9034abf005e0200000001dc5602643922fb60fb2c94970db12260850742e9c0f9f026bf28119fcf67e9c604000000000100000001204e0000000000002200207ce427f9be2c35185ab7151f477327acb20657856b69cc652c1a802b6254ac360f1b060036e6f4bd1403580346f7ad969d0b45f035220481207ddf9e43cbf0b49014e968000000000000000500061b0f03821c3180261d81126c2157282ea17be5131081c2d27c34f55a4000d06fc12b1a00bc7750d6e8d6765d1d53aae75cef20be8117cf7fc5c91c0ba017d3e214b100000000000000070003ff0000000000000003ff0000000000000004ff0000000000000005000009c4000000002faf08000000000008f0d1807a4b393c2c3f0677b66dec80d334b84644151a4373bba21436e873eeeebe9a61022ecd4622a2074bdf0ea45fc77632342eba413ae5a99afa1e57884176212716cb000000ff02557c4e960b3d9d9e0f73945a59a7cbad0f6f1b0ef9702d4e1b363a333c0362770003003e0000fffffffffffc0083c14c2553d85d117442efb0e845fdce17d61c8ab7215214f19b8857f57fa44a7800fc0003ffffffffffe80100830bc65499bcd444ee2554dc8a2e077dddf4caddea225a3383042e7edad3d11002000007ffffffffffc801022bb1088890b62d59d04fdd3907aa1f4a566019c687c4fd5c6eddde7b1bb746dc0003ffffffffffe40003000000000000000300038ee783097f5141649678366bf8596ed7000000000000000400032db7f6ee8f34459f84965d1a0bec8b8d000000000000000500035c4ac1741ba34f978d316fa4b05d974b00000101460f3d693d6660ff01e5e5b0cbb3ce11008877f608d74589ff7ee0e6cc675de4deeb42cba8e0a1fdd4ced5c0c51a98536631639717aa11043a699d13b2e98960ccc523109349f8ef0f9b0d9c74d84fa51d0806226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f01460f3d693d666068414d270300009000000000000003e8000858b800000014000000001dcd650000000001") val decoded = channelDataCodec.decode(bin.bits).require.value.asInstanceOf[DATA_NORMAL] + assert(decoded.commitments.latest.fundingInput == OutPoint(TxId.fromValidHex("59ac1824cba876995ef868660f099caf1e6cd35d045ca99421d238f32a4fbffb"), 0)) + assert(decoded.commitments.latest.capacity == 1_000_000.sat) val localCommit = decoded.commitments.latest.localCommit assert(localCommit.index == 7) assert(localCommit.txId == TxId.fromValidHex("c6e967cf9f1128bf26f0f9c0e94207856022b10d97942cfb60fb2239640256dc")) - assert(localCommit.input == InputInfo(OutPoint(TxId.fromValidHex("59ac1824cba876995ef868660f099caf1e6cd35d045ca99421d238f32a4fbffb"), 0), TxOut(1_000_000 sat, hex"0020520f04b40b1ec2c985ede593cfff212cac1088275f72e9158a4d05e7f95f0c88"), ByteVector.empty)) val htlcRemoteSigs = List( ByteVector64(hex"65cffa80c72f9e291456fabc3771865f4993108f8a90deddf041a9bcd523d3ec2d5361430705447f87312b2b858327f5f0c4999b7bdd7da84dd270189304def1"), ByteVector64(hex"c5930b24a9e50aa0680daf5ab04d82e21fe1edfaf2455e07e08484660cf4c12d384c22a3aa8b32fad23ba7f451ea718c718439b0b256b120f22eeadc5b995f49"), @@ -287,28 +215,28 @@ class ChannelCodecs4Spec extends AnyFunSuite { assert(decoded.closeStatus_opt.isEmpty) assert(decoded.commitments.active.size == 3) assert(decoded.commitments.all.size == 3) - assert(decoded.commitments.params.commitmentFormat == ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) - assert(decoded.commitments.params.channelFeatures.features == Set(Features.DualFunding, Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx)) + assert(decoded.commitments.latest.commitmentFormat == ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) + assert(decoded.commitments.channelParams.channelFeatures.features == Set(Features.DualFunding, Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx)) // Local params. - assert(!decoded.commitments.params.localParams.isChannelOpener) - assert(decoded.commitments.params.localParams.toSelfDelay == CltvExpiryDelta(144)) - assert(decoded.commitments.params.localParams.dustLimit == 1000.sat) - assert(decoded.commitments.params.localParams.htlcMinimum == 1000.msat) - assert(decoded.commitments.params.localParams.maxAcceptedHtlcs == 30) - assert(decoded.commitments.params.localParams.maxHtlcValueInFlightMsat == 1_000_000_000.msat) + assert(!decoded.commitments.localChannelParams.isChannelOpener) + assert(decoded.commitments.latest.localCommitParams.toSelfDelay == CltvExpiryDelta(144)) + assert(decoded.commitments.latest.localCommitParams.dustLimit == 1000.sat) + assert(decoded.commitments.latest.localCommitParams.htlcMinimum == 1000.msat) + assert(decoded.commitments.latest.localCommitParams.maxAcceptedHtlcs == 30) + assert(decoded.commitments.latest.localCommitParams.maxHtlcValueInFlight == UInt64(1_000_000_000)) // Remote params. - assert(decoded.commitments.params.remoteParams.toSelfDelay == CltvExpiryDelta(144)) - assert(decoded.commitments.params.remoteParams.dustLimit == 1100.sat) - assert(decoded.commitments.params.remoteParams.htlcMinimum == 0.msat) - assert(decoded.commitments.params.remoteParams.maxAcceptedHtlcs == 100) - assert(decoded.commitments.params.remoteParams.maxHtlcValueInFlightMsat == UInt64(500_000_000)) + assert(decoded.commitments.latest.remoteCommitParams.toSelfDelay == CltvExpiryDelta(144)) + assert(decoded.commitments.latest.remoteCommitParams.dustLimit == 1100.sat) + assert(decoded.commitments.latest.remoteCommitParams.htlcMinimum == 0.msat) + assert(decoded.commitments.latest.remoteCommitParams.maxAcceptedHtlcs == 100) + assert(decoded.commitments.latest.remoteCommitParams.maxHtlcValueInFlight == UInt64(500_000_000)) // The latest splice is unconfirmed. assert(decoded.commitments.active.head.fundingTxIndex == 2) assert(decoded.commitments.active.head.firstRemoteCommitIndex == 1) assert(decoded.commitments.active.head.remoteFundingPubKey == PublicKey(hex"02ab7a85d532b3cf955d440c7b92a368c65067cd05588385ae3bbb45d8187825da")) assert(decoded.commitments.active.head.fundingTxId == TxId.fromValidHex("56deed630c69b67f1982e1ea3d872081d780f830741823bcd5d19e43c266d115")) - assert(decoded.commitments.active.head.commitInput.outPoint == OutPoint(TxId.fromValidHex("56deed630c69b67f1982e1ea3d872081d780f830741823bcd5d19e43c266d115"), 0)) - assert(decoded.commitments.active.head.commitInput.txOut == TxOut(2_400_000 sat, hex"0020205f93e0d37d24c30b744c6557445988860ed0fc3ca24482a4c55b6388466104")) + assert(decoded.commitments.active.head.fundingInput == OutPoint(TxId.fromValidHex("56deed630c69b67f1982e1ea3d872081d780f830741823bcd5d19e43c266d115"), 0)) + assert(decoded.commitments.active.head.fundingAmount == 2_400_000.sat) assert(decoded.commitments.active.head.commitTxIds == CommitTxIds(TxId.fromValidHex("99db32761e7adfe0311fc55cd09e6b885b9da5306aec45f18a094fec153747a5"), TxId.fromValidHex("a46aa4f35b1c3ebd340deb44bf796b673f59c76d526a4559bd3de93368538715"), None)) assert(decoded.commitments.active.head.localFundingStatus.isInstanceOf[LocalFundingStatus.DualFundedUnconfirmedFundingTx]) assert(decoded.commitments.active.head.localFundingStatus.signedTx_opt.contains(Transaction.read("02000000000102a29b71d6f6d8bb043041ef7c26d5fc5424678d770a8e577e860d77e0128d7e0d000000000000000000a96e5c0f0fc3cd3d7b17e192c6dcb09c5bb1b5be0cfa3779c2c24cb37ffc4d340100000000fdffffff02009f240000000000220020205f93e0d37d24c30b744c6557445988860ed0fc3ca24482a4c55b63884661047a5d1e00000000001600141dabbcfbe5ad548fb216213f29a3c1a1425be1530247304402200e163c65da5d684d8a16f17377e0fa0afd13c82338de0d210c7a1a0f28a17ecb02204f2f1212f1b123e6536341a246d575750365e4c90f0d720bf176f0e4ef266313012102c26f5ff5c5a29adbe0cf8759332d602136094a0f11c4423bc251ee53aef87143040047304402206c23a27b31022ba82a5011ea1330b46f67b3d8b96de7fa2ae9d7fc0b1b2807e902207c1fa4e5a11e0cc7ec09810ba719af7b95c09a2ab1c5400723087d2f0c4a3f4a01483045022100f391ffededb5b824f85db92103f7f0a70903a18336c1ef6ee57759ee94fa8d200220358ab2eda8a7ed6f4b271ea1416007940cb694ff0c29ad712ad0a168e2c787b2014752210208ecc4fb85c80be134fe55d186aadc6223da2130c19e0054bd73f3e92188bc1b2102db80bf3ce539911d400ad7c8fc6455f2919171601cdb6dd1eaba9a13ec6fd29d52ae801a0600"))) @@ -318,22 +246,22 @@ class ChannelCodecs4Spec extends AnyFunSuite { assert(decoded.commitments.active(1).firstRemoteCommitIndex == 1) assert(decoded.commitments.active(1).remoteFundingPubKey == PublicKey(hex"02db80bf3ce539911d400ad7c8fc6455f2919171601cdb6dd1eaba9a13ec6fd29d")) assert(decoded.commitments.active(1).fundingTxId == TxId.fromValidHex("344dfc7fb34cc2c27937fa0cbeb5b15b9cb0dcc692e1177b3dcdc30f0f5c6ea9")) - assert(decoded.commitments.active(1).commitInput.outPoint == OutPoint(TxId.fromValidHex("344dfc7fb34cc2c27937fa0cbeb5b15b9cb0dcc692e1177b3dcdc30f0f5c6ea9"), 1)) - assert(decoded.commitments.active(1).commitInput.txOut == TxOut(1_900_000 sat, hex"00201af088d9988507e32444ab21a9f81616aea1c3bcd16ad4ccf2a5707255dcd1a9")) + assert(decoded.commitments.active(1).fundingInput == OutPoint(TxId.fromValidHex("344dfc7fb34cc2c27937fa0cbeb5b15b9cb0dcc692e1177b3dcdc30f0f5c6ea9"), 1)) + assert(decoded.commitments.active(1).fundingAmount == 1_900_000.sat) assert(decoded.commitments.active(1).commitTxIds == CommitTxIds(TxId.fromValidHex("7ba7b4e5bc38a5539fc4af56886665324dae2388a36062dbc02a3b8ad62abd2b"), TxId.fromValidHex("0edc489971adba4e677a7d5fef263e13620dcd03963aadb7c52bc164a66307ab"), None)) assert(decoded.commitments.active(1).localFundingStatus.isInstanceOf[LocalFundingStatus.ConfirmedFundingTx]) - assert(decoded.commitments.active(1).localFundingStatus.signedTx_opt.contains(Transaction.read("02000000000102296099d8ecfa948512f56c9bd7eafc3874e9b464790301211ff040d3c2bf65d60000000000000000004cb8b39d3b78c2333c7e074acf487458da3682aad6fda9a372255d3cc1514a1f0000000000fdffffff03a086010000000000220020aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaae0fd1c00000000002200201af088d9988507e32444ab21a9f81616aea1c3bcd16ad4ccf2a5707255dcd1a9103c1800000000002251209a59d6588f94df3775cc37fcf530dc76580286ad414d9b988331a71552d0fa590140857b2f29ff5384ea1ad85d6d076e9a8ec8258a88367d0d892b565cfca18f810e5d3e301f253012dac79545842f8bb59f91351deda07365ea40d6be71738f5db30400483045022100a4b4da4bef4d9584f68206adef9b493def56cea0de598488c194ff179d8c7b8002206e4b0eb365e4d43e95c349016b2b592a413ec5537c2ded6af0b13216535a9c5501483045022100d5fd961852c5ea9f326ee5696875cb69ed2ac082b93b881aac09fc2dad0ed79c0220392085519a39c9fd48996b2413cf8c816748004e7487ca0ad5bf26b1566182fb014752210210f9b3c19267dd4a2150f041096ab92b59db063fabfcb84bb1c3bf7da09cebe521039f967afce64ab9a5da4edbac9f3f0dddb2bdec02d12c124c415690e588eb50a052ae801a0600"))) + assert(decoded.commitments.active(1).localFundingStatus.asInstanceOf[LocalFundingStatus.ConfirmedFundingTx].txOut == Transaction.read("02000000000102296099d8ecfa948512f56c9bd7eafc3874e9b464790301211ff040d3c2bf65d60000000000000000004cb8b39d3b78c2333c7e074acf487458da3682aad6fda9a372255d3cc1514a1f0000000000fdffffff03a086010000000000220020aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaae0fd1c00000000002200201af088d9988507e32444ab21a9f81616aea1c3bcd16ad4ccf2a5707255dcd1a9103c1800000000002251209a59d6588f94df3775cc37fcf530dc76580286ad414d9b988331a71552d0fa590140857b2f29ff5384ea1ad85d6d076e9a8ec8258a88367d0d892b565cfca18f810e5d3e301f253012dac79545842f8bb59f91351deda07365ea40d6be71738f5db30400483045022100a4b4da4bef4d9584f68206adef9b493def56cea0de598488c194ff179d8c7b8002206e4b0eb365e4d43e95c349016b2b592a413ec5537c2ded6af0b13216535a9c5501483045022100d5fd961852c5ea9f326ee5696875cb69ed2ac082b93b881aac09fc2dad0ed79c0220392085519a39c9fd48996b2413cf8c816748004e7487ca0ad5bf26b1566182fb014752210210f9b3c19267dd4a2150f041096ab92b59db063fabfcb84bb1c3bf7da09cebe521039f967afce64ab9a5da4edbac9f3f0dddb2bdec02d12c124c415690e588eb50a052ae801a0600").txOut(1)) assert(decoded.commitments.active(1).remoteFundingStatus == RemoteFundingStatus.NotLocked) // The initial funding transaction is confirmed and locked. assert(decoded.commitments.active.last.fundingTxIndex == 0) assert(decoded.commitments.active.last.firstRemoteCommitIndex == 0) assert(decoded.commitments.active.last.remoteFundingPubKey == PublicKey(hex"039f967afce64ab9a5da4edbac9f3f0dddb2bdec02d12c124c415690e588eb50a0")) assert(decoded.commitments.active.last.fundingTxId == TxId.fromValidHex("1f4a51c13c5d2572a3a9fdd6aa8236da587448cf4a077e3c33c2783b9db3b84c")) - assert(decoded.commitments.active.last.commitInput.outPoint == OutPoint(TxId.fromValidHex("1f4a51c13c5d2572a3a9fdd6aa8236da587448cf4a077e3c33c2783b9db3b84c"), 0)) - assert(decoded.commitments.active.last.commitInput.txOut == TxOut(1_500_000 sat, hex"00206aa3d195b1c5bda9d5bbe9b9b481a9b2e17473fed97702704d21fd6e00f39c55")) + assert(decoded.commitments.active.last.fundingInput == OutPoint(TxId.fromValidHex("1f4a51c13c5d2572a3a9fdd6aa8236da587448cf4a077e3c33c2783b9db3b84c"), 0)) + assert(decoded.commitments.active.last.fundingAmount == 1_500_000.sat) assert(decoded.commitments.active.last.commitTxIds == CommitTxIds(TxId.fromValidHex("7637465daef2054492a18c48e33e4b67a5abb832fb4f36bc0de8006ed23ab72e"), TxId.fromValidHex("7628ac5ffaec424592e2982d06f0d33af3a11a433a49ae2a34d0ffbca4354af5"), None)) assert(decoded.commitments.active.last.localFundingStatus.isInstanceOf[LocalFundingStatus.ConfirmedFundingTx]) - assert(decoded.commitments.active.last.localFundingStatus.signedTx_opt.contains(Transaction.read("02000000000102549d7ace74fc0232a7e14f65cd5dac0dc4c68b9495f921c1493324c52c4bc9f700000000000000000035f42f05a50f23f7e81c8d3110871c8bd2b82d32a3e2c62779397e6df37995400000000000000000000360e31600000000002200206aa3d195b1c5bda9d5bbe9b9b481a9b2e17473fed97702704d21fd6e00f39c55d8760100000000002251209a59d6588f94df3775cc37fcf530dc76580286ad414d9b988331a71552d0fa59906e0100000000002251209a59d6588f94df3775cc37fcf530dc76580286ad414d9b988331a71552d0fa59014044fcfc2a2d35676ceb83c5217e55db7cfb30129b3350eb23abaf42d583d101817bcf14ce64513375b88acdd25eab5769b58b519e92306f0b2a29c6418aa73e8e0140e3d766cd2476e1c40c520d6a2c68a65ac5f25202a42a6bfd753958ff096aa78ba4aa029d5eabc899bb7828aeb800f8fe622803ebbddb10a882960aa332aa21a9801a0600"))) + assert(decoded.commitments.active.last.localFundingStatus.asInstanceOf[LocalFundingStatus.ConfirmedFundingTx].txOut == Transaction.read("02000000000102549d7ace74fc0232a7e14f65cd5dac0dc4c68b9495f921c1493324c52c4bc9f700000000000000000035f42f05a50f23f7e81c8d3110871c8bd2b82d32a3e2c62779397e6df37995400000000000000000000360e31600000000002200206aa3d195b1c5bda9d5bbe9b9b481a9b2e17473fed97702704d21fd6e00f39c55d8760100000000002251209a59d6588f94df3775cc37fcf530dc76580286ad414d9b988331a71552d0fa59906e0100000000002251209a59d6588f94df3775cc37fcf530dc76580286ad414d9b988331a71552d0fa59014044fcfc2a2d35676ceb83c5217e55db7cfb30129b3350eb23abaf42d583d101817bcf14ce64513375b88acdd25eab5769b58b519e92306f0b2a29c6418aa73e8e0140e3d766cd2476e1c40c520d6a2c68a65ac5f25202a42a6bfd753958ff096aa78ba4aa029d5eabc899bb7828aeb800f8fe622803ebbddb10a882960aa332aa21a9801a0600").txOut(0)) assert(decoded.commitments.active.last.remoteFundingStatus == RemoteFundingStatus.Locked) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5Spec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5Spec.scala new file mode 100644 index 0000000000..37b5e98fda --- /dev/null +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5Spec.scala @@ -0,0 +1,71 @@ +package fr.acinq.eclair.wire.internal.channel.version5 + +import fr.acinq.bitcoin.scalacompat.SatoshiLong +import fr.acinq.eclair.FeatureSupport.{Mandatory, Optional} +import fr.acinq.eclair.Features.{ChannelRangeQueries, PaymentSecret, VariableLengthOnion} +import fr.acinq.eclair.TestUtils.randomTxId +import fr.acinq.eclair.blockchain.fee.FeeratePerKw +import fr.acinq.eclair.channel.fund.InteractiveTxBuilder.{InteractiveTxParams, PartiallySignedSharedTransaction, RequireConfirmedInputs, SharedTransaction} +import fr.acinq.eclair.channel.fund.InteractiveTxSigningSession.UnsignedLocalCommit +import fr.acinq.eclair.channel.fund.{InteractiveTxBuilder, InteractiveTxSigningSession} +import fr.acinq.eclair.channel._ +import fr.acinq.eclair.transactions.CommitmentSpec +import fr.acinq.eclair.transactions.Transactions.ZeroFeeHtlcTxAnchorOutputsCommitmentFormat +import fr.acinq.eclair.wire.internal.channel.version5.ChannelCodecs5.Codecs.{dualFundingStatusCodec, remoteChannelParamsCodec} +import fr.acinq.eclair.wire.protocol.{LiquidityAds, TxSignatures} +import fr.acinq.eclair.{CltvExpiryDelta, Features, MilliSatoshiLong, UInt64, randomBytes32, randomKey} +import org.scalatest.funsuite.AnyFunSuite +import scodec.bits.ByteVector + +class ChannelCodecs5Spec extends AnyFunSuite { + + test("encode/decode rbf status") { + val channelId = randomBytes32() + val fundingTx = SharedTransaction( + sharedInput_opt = None, + sharedOutput = InteractiveTxBuilder.Output.Shared(UInt64(8), ByteVector.empty, 100_000_600 msat, 74_000_400 msat, 0 msat), + localInputs = Nil, remoteInputs = Nil, + localOutputs = Nil, remoteOutputs = Nil, + lockTime = 0 + ) + val waitingForSigs = InteractiveTxSigningSession.WaitingForSigs( + InteractiveTxParams(channelId, isInitiator = true, 100_000 sat, 75_000 sat, None, randomKey().publicKey, Nil, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat, 0, 330 sat, FeeratePerKw(500 sat), RequireConfirmedInputs(forLocal = false, forRemote = false)), + fundingTxIndex = 0, + PartiallySignedSharedTransaction(fundingTx, TxSignatures(channelId, randomTxId(), Nil)), + CommitParams(330 sat, 1 msat, UInt64.MaxValue, 30, CltvExpiryDelta(720)), + Left(UnsignedLocalCommit(0, CommitmentSpec(Set.empty, FeeratePerKw(1000 sat), 100_000_000 msat, 75_000_000 msat), randomTxId())), + CommitParams(500 sat, 1000 msat, UInt64.MaxValue, 483, CltvExpiryDelta(144)), + RemoteCommit(0, CommitmentSpec(Set.empty, FeeratePerKw(1000 sat), 75_000_000 msat, 100_000_000 msat), randomTxId(), randomKey().publicKey), + Some(LiquidityAds.PurchaseBasicInfo(isBuyer = true, 100_000 sat, LiquidityAds.Fees(1000 sat, 500 sat))), + ) + val testCases = Map( + DualFundingStatus.WaitingForConfirmations -> DualFundingStatus.WaitingForConfirmations, + DualFundingStatus.RbfRequested(CMD_BUMP_FUNDING_FEE(null, FeeratePerKw(750 sat), fundingFeeBudget = 100_000.sat, 0, None)) -> DualFundingStatus.WaitingForConfirmations, + DualFundingStatus.RbfInProgress(None, null, None) -> DualFundingStatus.WaitingForConfirmations, + DualFundingStatus.RbfWaitingForSigs(waitingForSigs) -> DualFundingStatus.RbfWaitingForSigs(waitingForSigs), + DualFundingStatus.RbfWaitingForSigs(waitingForSigs.copy(liquidityPurchase_opt = None)) -> DualFundingStatus.RbfWaitingForSigs(waitingForSigs.copy(liquidityPurchase_opt = None)), + DualFundingStatus.RbfAborted -> DualFundingStatus.WaitingForConfirmations, + ) + testCases.foreach { case (status, expected) => + val encoded = dualFundingStatusCodec.encode(status).require + val decoded = dualFundingStatusCodec.decode(encoded).require.value + assert(decoded == expected) + } + } + + test("encode/decode optional shutdown script") { + val remoteParams = RemoteChannelParams( + randomKey().publicKey, + Some(300_000 sat), + randomKey().publicKey, + randomKey().publicKey, + randomKey().publicKey, + randomKey().publicKey, + Features(ChannelRangeQueries -> Optional, VariableLengthOnion -> Mandatory, PaymentSecret -> Mandatory), + None) + assert(remoteChannelParamsCodec.decodeValue(remoteChannelParamsCodec.encode(remoteParams).require).require == remoteParams) + val remoteParams1 = remoteParams.copy(upfrontShutdownScript_opt = Some(ByteVector.fromValidHex("deadbeef"))) + assert(remoteChannelParamsCodec.decodeValue(remoteChannelParamsCodec.encode(remoteParams1).require).require == remoteParams1) + } + +} From 3872fedd21c14f21c3f13b8921c82c19c1b3a0b3 Mon Sep 17 00:00:00 2001 From: t-bast Date: Tue, 15 Jul 2025 16:38:22 +0200 Subject: [PATCH 3/6] Add commitment format to interactive-tx shared input And remove the unused sealed trait, the commitment format contains the information we need so we don't need multiple child classes. We still use a `discriminated` at the codec level though to allow changing the format in the future if needed. --- .../fr/acinq/eclair/channel/fsm/Channel.scala | 10 ++--- .../channel/fund/InteractiveTxBuilder.scala | 38 +++++++++---------- .../eclair/transactions/Transactions.scala | 7 +++- .../channel/version4/ChannelCodecs4.scala | 6 +-- .../channel/version4/ChannelTypes4.scala | 8 +++- .../channel/version5/ChannelCodecs5.scala | 7 ++-- .../channel/InteractiveTxBuilderSpec.scala | 12 +++--- .../transactions/TransactionsSpec.scala | 3 ++ .../channel/version4/ChannelCodecs4Spec.scala | 5 ++- 9 files changed, 53 insertions(+), 43 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala index 6a3dd1ec71..411ece8bf7 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala @@ -1112,7 +1112,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall isInitiator = false, localContribution = spliceAck.fundingContribution, remoteContribution = msg.fundingContribution, - sharedInput_opt = Some(Multisig2of2Input(channelKeys, parentCommitment)), + sharedInput_opt = Some(SharedFundingInput(channelKeys, parentCommitment)), remoteFundingPubKey = msg.fundingPubKey, localOutputs = Nil, commitmentFormat = commitmentFormat, @@ -1160,7 +1160,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall isInitiator = true, localContribution = spliceInit.fundingContribution, remoteContribution = msg.fundingContribution, - sharedInput_opt = Some(Multisig2of2Input(channelKeys, parentCommitment)), + sharedInput_opt = Some(SharedFundingInput(channelKeys, parentCommitment)), remoteFundingPubKey = msg.fundingPubKey, localOutputs = cmd.spliceOutputs, commitmentFormat = commitmentFormat, @@ -1239,7 +1239,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall isInitiator = false, localContribution = fundingContribution, remoteContribution = msg.fundingContribution, - sharedInput_opt = Some(Multisig2of2Input(channelKeys, rbf.parentCommitment)), + sharedInput_opt = Some(SharedFundingInput(channelKeys, rbf.parentCommitment)), remoteFundingPubKey = rbf.latestFundingTx.fundingParams.remoteFundingPubKey, localOutputs = rbf.latestFundingTx.fundingParams.localOutputs, commitmentFormat = rbf.latestFundingTx.fundingParams.commitmentFormat, @@ -1296,7 +1296,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall isInitiator = true, localContribution = txInitRbf.fundingContribution, remoteContribution = msg.fundingContribution, - sharedInput_opt = Some(Multisig2of2Input(channelKeys, rbf.parentCommitment)), + sharedInput_opt = Some(SharedFundingInput(channelKeys, rbf.parentCommitment)), remoteFundingPubKey = rbf.latestFundingTx.fundingParams.remoteFundingPubKey, localOutputs = rbf.latestFundingTx.fundingParams.localOutputs, commitmentFormat = rbf.latestFundingTx.fundingParams.commitmentFormat, @@ -3369,7 +3369,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall val targetFeerate = nodeParams.onChainFeeConf.getFundingFeerate(nodeParams.currentFeeratesForFundingClosing) val fundingContribution = InteractiveTxFunder.computeSpliceContribution( isInitiator = true, - sharedInput = Multisig2of2Input(channelKeys, parentCommitment), + sharedInput = SharedFundingInput(channelKeys, parentCommitment), spliceInAmount = cmd.additionalLocalFunding, spliceOut = cmd.spliceOutputs, targetFeerate = targetFeerate) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxBuilder.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxBuilder.scala index fd58c5654c..ec33147a65 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxBuilder.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxBuilder.scala @@ -101,15 +101,8 @@ object InteractiveTxBuilder { case class RequireConfirmedInputs(forLocal: Boolean, forRemote: Boolean) /** An input that is already shared between participants (e.g. the current funding output when doing a splice). */ - sealed trait SharedFundingInput { - // @formatter:off - def info: InputInfo - def weight: Int - // @formatter:on - } - - case class Multisig2of2Input(info: InputInfo, fundingTxIndex: Long, remoteFundingPubkey: PublicKey) extends SharedFundingInput { - override val weight: Int = 388 + case class SharedFundingInput(info: InputInfo, fundingTxIndex: Long, remoteFundingPubkey: PublicKey, commitmentFormat: CommitmentFormat) { + val weight: Int = commitmentFormat.fundingInputWeight def sign(channelKeys: ChannelKeys, tx: Transaction, spentUtxos: Map[OutPoint, TxOut]): ChannelSpendSignature.IndividualSignature = { val localFundingKey = channelKeys.fundingKey(fundingTxIndex) @@ -117,11 +110,12 @@ object InteractiveTxBuilder { } } - object Multisig2of2Input { - def apply(channelKeys: ChannelKeys, commitment: Commitment): Multisig2of2Input = Multisig2of2Input( + object SharedFundingInput { + def apply(channelKeys: ChannelKeys, commitment: Commitment): SharedFundingInput = SharedFundingInput( info = commitment.commitInput(channelKeys), fundingTxIndex = commitment.fundingTxIndex, - remoteFundingPubkey = commitment.remoteFundingPubKey + remoteFundingPubkey = commitment.remoteFundingPubKey, + commitmentFormat = commitment.commitmentFormat, ) } @@ -928,9 +922,10 @@ private class InteractiveTxBuilder(replyTo: ActorRef[InteractiveTxBuilder.Respon import fr.acinq.bitcoin.scalacompat.KotlinUtils._ val tx = unsignedTx.buildUnsignedTx() - val sharedSig_opt = fundingParams.sharedInput_opt.collect { - case i: Multisig2of2Input => i.sign(channelKeys, tx, unsignedTx.inputDetails).sig - } + val sharedSig_opt = fundingParams.sharedInput_opt.map(i => i.commitmentFormat match { + case _: SegwitV0CommitmentFormat => i.sign(channelKeys, tx, unsignedTx.inputDetails).sig + case _: SimpleTaprootChannelCommitmentFormat => ??? + }) if (unsignedTx.localInputs.isEmpty) { context.self ! SignTransactionResult(PartiallySignedSharedTransaction(unsignedTx, TxSignatures(fundingParams.channelId, tx, Nil, sharedSig_opt))) } else { @@ -1054,18 +1049,19 @@ object InteractiveTxSigningSession { log.info("invalid tx_signatures: witness count mismatch (expected={}, got={})", partiallySignedTx.tx.remoteInputs.length, remoteSigs.witnesses.length) return Left(InvalidFundingSignature(fundingParams.channelId, Some(partiallySignedTx.txId))) } - val sharedSigs_opt = fundingParams.sharedInput_opt match { - case Some(sharedInput: Multisig2of2Input) => - (partiallySignedTx.localSigs.previousFundingTxSig_opt, remoteSigs.previousFundingTxSig_opt) match { + val sharedSigs_opt = fundingParams.sharedInput_opt.map(sharedInput => { + sharedInput.commitmentFormat match { + case _: SegwitV0CommitmentFormat => (partiallySignedTx.localSigs.previousFundingTxSig_opt, remoteSigs.previousFundingTxSig_opt) match { case (Some(localSig), Some(remoteSig)) => val localFundingPubkey = channelKeys.fundingKey(sharedInput.fundingTxIndex).publicKey - Some(Scripts.witness2of2(localSig, remoteSig, localFundingPubkey, sharedInput.remoteFundingPubkey)) + Scripts.witness2of2(localSig, remoteSig, localFundingPubkey, sharedInput.remoteFundingPubkey) case _ => log.info("invalid tx_signatures: missing shared input signatures") return Left(InvalidFundingSignature(fundingParams.channelId, Some(partiallySignedTx.txId))) } - case None => None - } + case _: SimpleTaprootChannelCommitmentFormat => ??? + } + }) val txWithSigs = FullySignedSharedTransaction(partiallySignedTx.tx, partiallySignedTx.localSigs, remoteSigs, sharedSigs_opt) if (remoteSigs.txId != txWithSigs.signedTx.txid) { log.info("invalid tx_signatures: txId mismatch (expected={}, got={})", txWithSigs.signedTx.txid, remoteSigs.txId) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala index 60d699c21d..e9e7f5b3ba 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala @@ -58,6 +58,8 @@ object Transactions { sealed trait CommitmentFormat { // @formatter:off + /** Weight of a fully signed channel output, when spent by a [[ChannelSpendTransaction]]. */ + def fundingInputWeight: Int /** Weight of a fully signed [[CommitTx]] transaction without any HTLCs. */ def commitWeight: Int /** Weight of a fully signed [[ClaimLocalAnchorTx]] or [[ClaimRemoteAnchorTx]] input. */ @@ -93,7 +95,9 @@ object Transactions { // @formatter:on } - sealed trait SegwitV0CommitmentFormat extends CommitmentFormat + sealed trait SegwitV0CommitmentFormat extends CommitmentFormat { + override val fundingInputWeight = 384 + } /** * Commitment format as defined in the v1.0 specification (https://github.com/lightningnetwork/lightning-rfc/tree/v1.0). @@ -168,6 +172,7 @@ object Transactions { sealed trait SimpleTaprootChannelCommitmentFormat extends TaprootCommitmentFormat { // weights for taproot transactions are deterministic since signatures are encoded as 64 bytes and // not in variable length DER format (around 72 bytes) + override val fundingInputWeight = 230 override val commitWeight = 960 override val anchorInputWeight = 230 override val htlcOutputWeight = 172 diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4.scala index 36472da796..1290a3a7e9 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4.scala @@ -277,12 +277,12 @@ private[channel] object ChannelCodecs4 { val spentMapCodec: Codec[Map[OutPoint, Transaction]] = mapCodec(outPointCodec, txCodec) - private val multisig2of2InputCodec: Codec[InteractiveTxBuilder.Multisig2of2Input] = ( + private val multisig2of2InputCodec: Codec[ChannelTypes4.Multisig2of2Input] = ( ("info" | inputInfoCodec) :: ("fundingTxIndex" | uint32) :: - ("remoteFundingPubkey" | publicKey)).as[InteractiveTxBuilder.Multisig2of2Input] + ("remoteFundingPubkey" | publicKey)).as[ChannelTypes4.Multisig2of2Input] - private val sharedFundingInputCodec: Codec[InteractiveTxBuilder.SharedFundingInput] = discriminated[InteractiveTxBuilder.SharedFundingInput].by(uint16) + private val sharedFundingInputCodec: Codec[ChannelTypes4.Multisig2of2Input] = discriminated[ChannelTypes4.Multisig2of2Input].by(uint16) .typecase(0x01, multisig2of2InputCodec) private val requireConfirmedInputsCodec: Codec[InteractiveTxBuilder.RequireConfirmedInputs] = (("forLocal" | bool8) :: ("forRemote" | bool8)).as[InteractiveTxBuilder.RequireConfirmedInputs] diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelTypes4.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelTypes4.scala index 92d33c111b..08668307e3 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelTypes4.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelTypes4.scala @@ -72,12 +72,16 @@ private[channel] object ChannelTypes4 { def remoteCommitParams(): channel.CommitParams = channel.CommitParams(remoteParams.dustLimit, remoteParams.htlcMinimum, remoteParams.maxHtlcValueInFlightMsat, remoteParams.maxAcceptedHtlcs, localParams.toSelfDelay) } + case class Multisig2of2Input(info: InputInfo, fundingTxIndex: Long, remoteFundingPubkey: PublicKey) { + def migrate(commitmentFormat: CommitmentFormat): InteractiveTxBuilder.SharedFundingInput = InteractiveTxBuilder.SharedFundingInput(info, fundingTxIndex, remoteFundingPubkey, commitmentFormat) + } + // We added the commitment format when moving to channel codecs v5. case class InteractiveTxParams(channelId: ByteVector32, isInitiator: Boolean, localContribution: Satoshi, remoteContribution: Satoshi, - sharedInput_opt: Option[InteractiveTxBuilder.SharedFundingInput], + sharedInput_opt: Option[Multisig2of2Input], remoteFundingPubKey: PublicKey, localOutputs: List[TxOut], lockTime: Long, @@ -89,7 +93,7 @@ private[channel] object ChannelTypes4 { isInitiator = isInitiator, localContribution = localContribution, remoteContribution = remoteContribution, - sharedInput_opt = sharedInput_opt, + sharedInput_opt = sharedInput_opt.map(_.migrate(commitmentFormat)), remoteFundingPubKey = remoteFundingPubKey, localOutputs = localOutputs, commitmentFormat = commitmentFormat, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5.scala index c30d1c3de4..6c7f7fa62b 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5.scala @@ -119,13 +119,14 @@ private[channel] object ChannelCodecs5 { ("maxAcceptedHtlcs" | uint16) :: ("toSelfDelay" | cltvExpiryDelta)).as[CommitParams] - private val multisig2of2InputCodec: Codec[InteractiveTxBuilder.Multisig2of2Input] = ( + private val interactiveTxSharedFundingInputCodec: Codec[InteractiveTxBuilder.SharedFundingInput] = ( ("info" | inputInfoCodec) :: ("fundingTxIndex" | uint32) :: - ("remoteFundingPubkey" | publicKey)).as[InteractiveTxBuilder.Multisig2of2Input] + ("remoteFundingPubkey" | publicKey) :: + ("commitmentFormat" | commitmentFormatCodec)).as[InteractiveTxBuilder.SharedFundingInput] private val sharedFundingInputCodec: Codec[InteractiveTxBuilder.SharedFundingInput] = discriminated[InteractiveTxBuilder.SharedFundingInput].by(uint16) - .typecase(0x01, multisig2of2InputCodec) + .typecase(0x01, interactiveTxSharedFundingInputCodec) private val requireConfirmedInputsCodec: Codec[InteractiveTxBuilder.RequireConfirmedInputs] = (("forLocal" | bool8) :: ("forRemote" | bool8)).as[InteractiveTxBuilder.RequireConfirmedInputs] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala index 0e44c1d780..2fa9b1f8b6 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala @@ -105,18 +105,18 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val fundingPubkeyScript: ByteVector = Script.write(Script.pay2wsh(Scripts.multiSig2of2(fundingParamsB.remoteFundingPubKey, fundingParamsA.remoteFundingPubKey))) def sharedInputs(commitmentA: Commitment, commitmentB: Commitment): (SharedFundingInput, SharedFundingInput) = { - val sharedInputA = Multisig2of2Input(channelKeysA, commitmentA) - val sharedInputB = Multisig2of2Input(channelKeysB, commitmentB) + val sharedInputA = SharedFundingInput(channelKeysA, commitmentA) + val sharedInputB = SharedFundingInput(channelKeysB, commitmentB) (sharedInputA, sharedInputB) } def dummySharedInputB(amount: Satoshi): SharedFundingInput = { val inputInfo = InputInfo(OutPoint(randomTxId(), 3), TxOut(amount, fundingPubkeyScript)) val fundingTxIndex = fundingParamsA.sharedInput_opt match { - case Some(input: Multisig2of2Input) => input.fundingTxIndex + 1 + case Some(input) => input.fundingTxIndex + 1 case _ => 0 } - Multisig2of2Input(inputInfo, fundingTxIndex, fundingParamsA.remoteFundingPubKey) + SharedFundingInput(inputInfo, fundingTxIndex, fundingParamsA.remoteFundingPubKey, fundingParamsA.commitmentFormat) } def createSpliceFixtureParams(fundingTxIndex: Long, fundingAmountA: Satoshi, fundingAmountB: Satoshi, targetFeerate: FeeratePerKw, dustLimit: Satoshi, lockTime: Long, sharedInputA: SharedFundingInput, sharedInputB: SharedFundingInput, spliceOutputsA: List[TxOut] = Nil, spliceOutputsB: List[TxOut] = Nil, requireConfirmedInputs: RequireConfirmedInputs = RequireConfirmedInputs(forLocal = false, forRemote = false)): FixtureParams = { @@ -2606,7 +2606,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val wallet = new SingleKeyOnChainWallet() val params = createFixtureParams(100_000 sat, 0 sat, FeeratePerKw(5000 sat), 330 sat, 0) val previousCommitment = CommitmentsSpec.makeCommitments(25_000_000 msat, 50_000_000 msat).active.head - val fundingParams = params.fundingParamsB.copy(sharedInput_opt = Some(Multisig2of2Input(previousCommitment.commitInput(params.channelKeysB), 0, randomKey().publicKey))) + val fundingParams = params.fundingParamsB.copy(sharedInput_opt = Some(SharedFundingInput(previousCommitment.commitInput(params.channelKeysB), 0, randomKey().publicKey, previousCommitment.commitmentFormat))) val bob = params.spawnTxBuilderSpliceBob(fundingParams, previousCommitment, wallet) bob ! Start(probe.ref) // Alice --- tx_add_input --> Bob @@ -2622,7 +2622,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val params = createFixtureParams(100_000 sat, 0 sat, FeeratePerKw(5000 sat), 330 sat, 0) val previousCommitment = CommitmentsSpec.makeCommitments(25_000_000 msat, 50_000_000 msat).active.head val fundingTx = Transaction(2, Nil, Seq(TxOut(50_000 sat, Script.pay2wpkh(randomKey().publicKey)), TxOut(20_000 sat, Script.pay2wpkh(randomKey().publicKey))), 0) - val sharedInput = Multisig2of2Input(InputInfo(OutPoint(fundingTx, 0), fundingTx.txOut.head), 0, randomKey().publicKey) + val sharedInput = SharedFundingInput(InputInfo(OutPoint(fundingTx, 0), fundingTx.txOut.head), 0, randomKey().publicKey, previousCommitment.commitmentFormat) val bob = params.spawnTxBuilderSpliceBob(params.fundingParamsB.copy(sharedInput_opt = Some(sharedInput)), previousCommitment, wallet) bob ! Start(probe.ref) // Alice --- tx_add_input --> Bob diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala index b56570e47c..a8ae00261e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala @@ -315,6 +315,9 @@ class TransactionsSpec extends AnyFunSuite with Logging { commitTx } commitTx.correctlySpends(Seq(fundingTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + // We check the expected weight of the commit input: + val commitInputWeight = commitTx.copy(txIn = Seq(commitTx.txIn.head, commitTx.txIn.head)).weight() - commitTx.weight() + checkExpectedWeight(commitInputWeight, commitmentFormat.fundingInputWeight, commitmentFormat) val htlcTxs = makeHtlcTxs(commitTx, outputs, commitmentFormat) val expiries = htlcTxs.map(tx => tx.htlcId -> tx.htlcExpiry.toLong).toMap val htlcSuccessTxs = htlcTxs.collect { case tx: UnsignedHtlcSuccessTx => tx } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4Spec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4Spec.scala index 58b7829f7e..71a3d75410 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4Spec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4Spec.scala @@ -101,10 +101,11 @@ class ChannelCodecs4Spec extends AnyFunSuite { ), createdAt = BlockHeight(1000), fundingParams = InteractiveTxParams(channelId = channelId, isInitiator = true, localContribution = 100.sat, remoteContribution = 200.sat, - sharedInput_opt = Some(InteractiveTxBuilder.Multisig2of2Input( + sharedInput_opt = Some(InteractiveTxBuilder.SharedFundingInput( InputInfo(OutPoint(TxId(ByteVector32.Zeroes), 0), TxOut(1000.sat, Script.pay2wsh(script))), 0, - PrivateKey(ByteVector.fromValidHex("02" * 32)).publicKey + PrivateKey(ByteVector.fromValidHex("02" * 32)).publicKey, + ZeroFeeHtlcTxAnchorOutputsCommitmentFormat, )), remoteFundingPubKey = PrivateKey(ByteVector.fromValidHex("01" * 32)).publicKey, localOutputs = Nil, From f7fb22eda11e6dd7e158938c88e2bb4cfefd7b69 Mon Sep 17 00:00:00 2001 From: t-bast Date: Tue, 15 Jul 2025 14:41:28 +0200 Subject: [PATCH 4/6] Make channel type features non-permanent We want to be able to update channel types during splices, which means that channel type features will not be permanent anymore. We decouple permanent channel features (that apply to the lifetime of the channel) from channel type features to allow this. --- .../main/scala/fr/acinq/eclair/Features.scala | 15 +- .../fr/acinq/eclair/channel/ChannelData.scala | 9 +- .../eclair/channel/ChannelFeatures.scala | 35 ++--- .../fr/acinq/eclair/channel/Commitments.scala | 1 - .../fr/acinq/eclair/channel/Helpers.scala | 8 +- .../channel/fsm/ChannelOpenSingleFunded.scala | 6 +- .../crypto/keymanager/CommitmentKeys.scala | 2 +- .../channel/version0/ChannelTypes0.scala | 12 +- .../channel/version3/ChannelCodecs3.scala | 16 +- .../channel/version3/ChannelTypes3.scala | 22 ++- .../channel/version4/ChannelCodecs4.scala | 16 +- .../channel/version4/ChannelTypes4.scala | 5 +- .../020002-DATA_NORMAL/funder/data.json | 2 +- .../funder/data.json | 2 +- .../fundee/data.json | 2 +- .../funder/data.json | 2 +- .../fundee/data.json | 2 +- .../funder/data.json | 2 +- .../04000e-DATA_NORMAL/announced/data.json | 2 +- .../splicing-private/data.json | 2 +- .../eclair/channel/ChannelFeaturesSpec.scala | 56 ++----- .../channel/InteractiveTxBuilderSpec.scala | 7 +- .../publish/ReplaceableTxPublisherSpec.scala | 2 +- .../channel/states/h/ClosingStateSpec.scala | 140 +++++++++--------- .../eclair/payment/PaymentPacketSpec.scala | 4 +- .../eclair/transactions/TestVectorsSpec.scala | 26 ++-- .../channel/version2/ChannelCodecs2Spec.scala | 12 +- .../channel/version4/ChannelCodecs4Spec.scala | 24 +-- 28 files changed, 215 insertions(+), 219 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala index 080a2e4e27..f594e78934 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala @@ -36,17 +36,15 @@ object FeatureSupport { /** Not a sealed trait, so it can be extended by plugins. */ trait Feature { - def rfcName: String def mandatory: Int def optional: Int = mandatory + 1 - def supportBit(support: FeatureSupport): Int = support match { case Mandatory => mandatory case Optional => optional } - override def toString = rfcName + override def toString: String = rfcName } /** Feature scope as defined in Bolt 9. */ @@ -68,11 +66,9 @@ trait Bolt12Feature extends InvoiceFeature */ trait PermanentChannelFeature extends InitFeature // <- not in the spec /** - * Permanent channel feature negotiated in the channel type. Those features take precedence over permanent channel - * features negotiated in init messages. For example, if the channel type is option_static_remotekey, then even if - * the option_anchor_outputs feature is supported by both peers, it won't apply to the channel. + * Features that can be included in the [[fr.acinq.eclair.wire.protocol.ChannelTlv.ChannelTypeTlv]]. */ -trait ChannelTypeFeature extends PermanentChannelFeature +trait ChannelTypeFeature extends InitFeature // @formatter:on case class UnknownFeature(bitIndex: Int) @@ -275,6 +271,7 @@ object Features { val rfcName = "option_attribution_data" val mandatory = 36 } + case object OnionMessages extends Feature with InitFeature with NodeFeature { val rfcName = "option_onion_messages" val mandatory = 38 @@ -290,7 +287,7 @@ object Features { val mandatory = 44 } - case object ScidAlias extends Feature with InitFeature with NodeFeature with ChannelTypeFeature { + case object ScidAlias extends Feature with InitFeature with NodeFeature with ChannelTypeFeature with PermanentChannelFeature { val rfcName = "option_scid_alias" val mandatory = 46 } @@ -300,7 +297,7 @@ object Features { val mandatory = 48 } - case object ZeroConf extends Feature with InitFeature with NodeFeature with ChannelTypeFeature { + case object ZeroConf extends Feature with InitFeature with NodeFeature with ChannelTypeFeature with PermanentChannelFeature { val rfcName = "option_zeroconf" val mandatory = 50 } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala index 6d2a4a3f78..ccead6c9d1 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala @@ -567,6 +567,7 @@ final case class DATA_WAIT_FOR_ACCEPT_CHANNEL(initFunder: INPUT_INIT_CHANNEL_INI val channelId: ByteVector32 = initFunder.temporaryChannelId } final case class DATA_WAIT_FOR_FUNDING_INTERNAL(channelParams: ChannelParams, + channelType: SupportedChannelType, localCommitParams: CommitParams, remoteCommitParams: CommitParams, fundingAmount: Satoshi, @@ -576,9 +577,10 @@ final case class DATA_WAIT_FOR_FUNDING_INTERNAL(channelParams: ChannelParams, remoteFirstPerCommitmentPoint: PublicKey, replyTo: akka.actor.typed.ActorRef[Peer.OpenChannelResponse]) extends TransientChannelData { val channelId: ByteVector32 = channelParams.channelId - val commitmentFormat: CommitmentFormat = channelParams.channelFeatures.commitmentFormat + val commitmentFormat: CommitmentFormat = channelType.commitmentFormat } final case class DATA_WAIT_FOR_FUNDING_CREATED(channelParams: ChannelParams, + channelType: SupportedChannelType, localCommitParams: CommitParams, remoteCommitParams: CommitParams, fundingAmount: Satoshi, @@ -587,9 +589,10 @@ final case class DATA_WAIT_FOR_FUNDING_CREATED(channelParams: ChannelParams, remoteFundingPubKey: PublicKey, remoteFirstPerCommitmentPoint: PublicKey) extends TransientChannelData { val channelId: ByteVector32 = channelParams.channelId - val commitmentFormat: CommitmentFormat = channelParams.channelFeatures.commitmentFormat + val commitmentFormat: CommitmentFormat = channelType.commitmentFormat } final case class DATA_WAIT_FOR_FUNDING_SIGNED(channelParams: ChannelParams, + channelType: SupportedChannelType, localCommitParams: CommitParams, remoteCommitParams: CommitParams, remoteFundingPubKey: PublicKey, @@ -601,7 +604,7 @@ final case class DATA_WAIT_FOR_FUNDING_SIGNED(channelParams: ChannelParams, lastSent: FundingCreated, replyTo: akka.actor.typed.ActorRef[Peer.OpenChannelResponse]) extends TransientChannelData { val channelId: ByteVector32 = channelParams.channelId - val commitmentFormat: CommitmentFormat = channelParams.channelFeatures.commitmentFormat + val commitmentFormat: CommitmentFormat = channelType.commitmentFormat } final case class DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments: Commitments, waitingSince: BlockHeight, // how long have we been waiting for the funding tx to confirm diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala index 3ab4c20b0e..9b628d06d2 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala @@ -25,22 +25,10 @@ import fr.acinq.eclair.{ChannelTypeFeature, FeatureSupport, Features, InitFeatur /** * Subset of Bolt 9 features used to configure a channel and applicable over the lifetime of that channel. - * Even if one of these features is later disabled at the connection level, it will still apply to the channel until the - * channel is upgraded or closed. + * Even if one of these features is later disabled at the connection level, it will still apply to the channel. */ case class ChannelFeatures(features: Set[PermanentChannelFeature]) { - /** True if our main output in the remote commitment is directly sent (without any delay) to one of our wallet addresses. */ - val paysDirectlyToWallet: Boolean = hasFeature(Features.StaticRemoteKey) && !hasFeature(Features.AnchorOutputs) && !hasFeature(Features.AnchorOutputsZeroFeeHtlcTx) - /** Legacy option_anchor_outputs is used for Phoenix, because Phoenix doesn't have an on-chain wallet to pay for fees. */ - val commitmentFormat: CommitmentFormat = if (hasFeature(Features.AnchorOutputs)) { - UnsafeLegacyAnchorOutputsCommitmentFormat - } else if (hasFeature(Features.AnchorOutputsZeroFeeHtlcTx)) { - ZeroFeeHtlcTxAnchorOutputsCommitmentFormat - } else { - DefaultCommitmentFormat - } - def hasFeature(feature: PermanentChannelFeature): Boolean = features.contains(feature) override def toString: String = features.mkString(",") @@ -51,19 +39,24 @@ object ChannelFeatures { def apply(features: PermanentChannelFeature*): ChannelFeatures = ChannelFeatures(Set.from(features)) - /** Enrich the channel type with other permanent features that will be applied to the channel. */ + /** Configure permanent channel features based on local and remote feature. */ def apply(channelType: SupportedChannelType, localFeatures: Features[InitFeature], remoteFeatures: Features[InitFeature], announceChannel: Boolean): ChannelFeatures = { - val additionalPermanentFeatures = Features.knownFeatures.collect { + val permanentFeatures = Features.knownFeatures.collect { // If we both support 0-conf or scid_alias, we use it even if it wasn't in the channel-type. - case Features.ScidAlias if Features.canUseFeature(localFeatures, remoteFeatures, Features.ScidAlias) && !announceChannel => Some(Features.ScidAlias) + // Note that we cannot use scid_alias if the channel is announced. + case Features.ScidAlias => + if (Features.canUseFeature(localFeatures, remoteFeatures, Features.ScidAlias) && !announceChannel) { + Some(Features.ScidAlias) + } else { + None + } case Features.ZeroConf if Features.canUseFeature(localFeatures, remoteFeatures, Features.ZeroConf) => Some(Features.ZeroConf) - // Other channel-type features are negotiated in the channel-type, we ignore their value from the init message. - case _: ChannelTypeFeature => None - // We add all other permanent channel features that aren't negotiated as part of the channel-type. + // We add all other permanent channel features that we both support. case f: PermanentChannelFeature if Features.canUseFeature(localFeatures, remoteFeatures, f) => Some(f) }.flatten - val allPermanentFeatures = channelType.features.toSeq ++ additionalPermanentFeatures - ChannelFeatures(allPermanentFeatures: _*) + // Some permanent features can be negotiated as part of the channel-type. + val channelTypeFeatures = channelType.features.collect { case f: PermanentChannelFeature => f } + ChannelFeatures(permanentFeatures ++ channelTypeFeatures) } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index b8fb7e8cf8..729f9893ed 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -25,7 +25,6 @@ case class ChannelParams(channelId: ByteVector32, channelFeatures: ChannelFeatures, localParams: LocalChannelParams, remoteParams: RemoteChannelParams, channelFlags: ChannelFlags) { - require(channelFeatures.paysDirectlyToWallet == localParams.walletStaticPaymentBasepoint.isDefined, s"localParams.walletStaticPaymentBasepoint must be defined only for commitments that pay directly to our wallet (channel features: $channelFeatures") require(channelFeatures.hasFeature(Features.DualFunding) == localParams.initialRequestedChannelReserve_opt.isEmpty, "custom local channel reserve is incompatible with dual-funded channels") require(channelFeatures.hasFeature(Features.DualFunding) == remoteParams.initialRequestedChannelReserve_opt.isEmpty, "custom remote channel reserve is incompatible with dual-funded channels") diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 14fce97f89..23e6ba48a6 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -133,8 +133,8 @@ object Helpers { val channelFeatures = ChannelFeatures(channelType, localFeatures, remoteFeatures, open.channelFlags.announceChannel) // BOLT #2: The receiving node MUST fail the channel if: it considers feerate_per_kw too small for timely processing or unreasonably large. - val localFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, channelFeatures.commitmentFormat, open.fundingSatoshis) - if (nodeParams.onChainFeeConf.feerateToleranceFor(remoteNodeId).isFeeDiffTooHigh(channelFeatures.commitmentFormat, localFeeratePerKw, open.feeratePerKw)) return Left(FeerateTooDifferent(open.temporaryChannelId, localFeeratePerKw, open.feeratePerKw)) + val localFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, channelType.commitmentFormat, open.fundingSatoshis) + if (nodeParams.onChainFeeConf.feerateToleranceFor(remoteNodeId).isFeeDiffTooHigh(channelType.commitmentFormat, localFeeratePerKw, open.feeratePerKw)) return Left(FeerateTooDifferent(open.temporaryChannelId, localFeeratePerKw, open.feeratePerKw)) // we don't check that the funder's amount for the initial commitment transaction is sufficient for full fee payment // now, but it will be done later when we receive `funding_created` @@ -180,8 +180,8 @@ object Helpers { val channelFeatures = ChannelFeatures(channelType, localFeatures, remoteFeatures, open.channelFlags.announceChannel) // BOLT #2: The receiving node MUST fail the channel if: it considers feerate_per_kw too small for timely processing or unreasonably large. - val localFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, channelFeatures.commitmentFormat, open.fundingAmount) - if (nodeParams.onChainFeeConf.feerateToleranceFor(remoteNodeId).isFeeDiffTooHigh(channelFeatures.commitmentFormat, localFeeratePerKw, open.commitmentFeerate)) return Left(FeerateTooDifferent(open.temporaryChannelId, localFeeratePerKw, open.commitmentFeerate)) + val localFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, channelType.commitmentFormat, open.fundingAmount) + if (nodeParams.onChainFeeConf.feerateToleranceFor(remoteNodeId).isFeeDiffTooHigh(channelType.commitmentFormat, localFeeratePerKw, open.commitmentFeerate)) return Left(FeerateTooDifferent(open.temporaryChannelId, localFeeratePerKw, open.commitmentFeerate)) for { script_opt <- extractShutdownScript(open.temporaryChannelId, localFeatures, remoteFeatures, open.upfrontShutdownScript_opt) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunded.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunded.scala index 52948ca7b4..23d0b5b74a 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunded.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunded.scala @@ -142,7 +142,7 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { ChannelTlv.UpfrontShutdownScriptTlv(localShutdownScript), ChannelTlv.ChannelTypeTlv(d.initFundee.channelType) )) - goto(WAIT_FOR_FUNDING_CREATED) using DATA_WAIT_FOR_FUNDING_CREATED(channelParams, localCommitParams, remoteCommitParams, open.fundingSatoshis, open.pushMsat, open.feeratePerKw, open.fundingPubkey, open.firstPerCommitmentPoint) sending accept + goto(WAIT_FOR_FUNDING_CREATED) using DATA_WAIT_FOR_FUNDING_CREATED(channelParams, d.initFundee.channelType, localCommitParams, remoteCommitParams, open.fundingSatoshis, open.pushMsat, open.feeratePerKw, open.fundingPubkey, open.firstPerCommitmentPoint) sending accept } case Event(c: CloseCommand, d) => handleFastClose(c, d.channelId) @@ -175,7 +175,7 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { val channelParams = ChannelParams(d.initFunder.temporaryChannelId, d.initFunder.channelConfig, channelFeatures, d.initFunder.localChannelParams, remoteChannelParams, d.lastSent.channelFlags) val localCommitParams = CommitParams(d.initFunder.proposedCommitParams.localDustLimit, d.initFunder.proposedCommitParams.localHtlcMinimum, d.initFunder.proposedCommitParams.localMaxHtlcValueInFlight, d.initFunder.proposedCommitParams.localMaxAcceptedHtlcs, accept.toSelfDelay) val remoteCommitParams = CommitParams(accept.dustLimitSatoshis, accept.htlcMinimumMsat, accept.maxHtlcValueInFlightMsat, accept.maxAcceptedHtlcs, d.initFunder.proposedCommitParams.toRemoteDelay) - goto(WAIT_FOR_FUNDING_INTERNAL) using DATA_WAIT_FOR_FUNDING_INTERNAL(channelParams, localCommitParams, remoteCommitParams, d.initFunder.fundingAmount, d.initFunder.pushAmount_opt.getOrElse(0 msat), d.initFunder.commitTxFeerate, accept.fundingPubkey, accept.firstPerCommitmentPoint, d.initFunder.replyTo) + goto(WAIT_FOR_FUNDING_INTERNAL) using DATA_WAIT_FOR_FUNDING_INTERNAL(channelParams, d.initFunder.channelType, localCommitParams, remoteCommitParams, d.initFunder.fundingAmount, d.initFunder.pushAmount_opt.getOrElse(0 msat), d.initFunder.commitTxFeerate, accept.fundingPubkey, accept.firstPerCommitmentPoint, d.initFunder.replyTo) } case Event(c: CloseCommand, d: DATA_WAIT_FOR_ACCEPT_CHANNEL) => @@ -224,7 +224,7 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { txPublisher ! SetChannelId(remoteNodeId, channelId) context.system.eventStream.publish(ChannelIdAssigned(self, remoteNodeId, temporaryChannelId, channelId)) // NB: we don't send a ChannelSignatureSent for the first commit - goto(WAIT_FOR_FUNDING_SIGNED) using DATA_WAIT_FOR_FUNDING_SIGNED(channelParams1, d.localCommitParams, d.remoteCommitParams, d.remoteFundingPubKey, fundingTx, fundingTxFee, localSpec, localCommitTx, remoteCommit, fundingCreated, d.replyTo) sending fundingCreated + goto(WAIT_FOR_FUNDING_SIGNED) using DATA_WAIT_FOR_FUNDING_SIGNED(channelParams1, d.channelType, d.localCommitParams, d.remoteCommitParams, d.remoteFundingPubKey, fundingTx, fundingTxFee, localSpec, localCommitTx, remoteCommit, fundingCreated, d.replyTo) sending fundingCreated } case Event(Status.Failure(t), d: DATA_WAIT_FOR_FUNDING_INTERNAL) => diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/CommitmentKeys.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/CommitmentKeys.scala index 36c879b4c8..540650ce38 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/CommitmentKeys.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/CommitmentKeys.scala @@ -72,7 +72,7 @@ object LocalCommitmentKeys { LocalCommitmentKeys( ourDelayedPaymentKey = channelKeys.delayedPaymentKey(localPerCommitmentPoint), theirPaymentPublicKey = commitmentFormat match { - case DefaultCommitmentFormat if params.channelFeatures.hasFeature(Features.StaticRemoteKey) => params.remoteParams.paymentBasepoint + case DefaultCommitmentFormat if params.localParams.walletStaticPaymentBasepoint.nonEmpty => params.remoteParams.paymentBasepoint case DefaultCommitmentFormat => ChannelKeys.remotePerCommitmentPublicKey(params.remoteParams.paymentBasepoint, localPerCommitmentPoint) case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => params.remoteParams.paymentBasepoint }, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelTypes0.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelTypes0.scala index 80d0a19b81..568cafb65e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelTypes0.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelTypes0.scala @@ -230,12 +230,10 @@ private[channel] object ChannelTypes0 { } else { ChannelConfig() } - val channelFeatures = if (channelVersion.hasAnchorOutputs) { - ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputs) - } else if (channelVersion.hasStaticRemotekey) { - ChannelFeatures(Features.StaticRemoteKey) + val commitmentFormat = if (channelVersion.hasAnchorOutputs) { + UnsafeLegacyAnchorOutputsCommitmentFormat } else { - ChannelFeatures() + DefaultCommitmentFormat } val (localCommit1, commitInput) = localCommit.migrate(remoteParams.fundingPubKey) val localCommitParams = CommitParams(localParams.dustLimit, localParams.htlcMinimum, localParams.maxHtlcValueInFlightMsat, localParams.maxAcceptedHtlcs, remoteParams.toRemoteDelay) @@ -251,7 +249,7 @@ private[channel] object ChannelTypes0 { // funding tx when the channel is instantiated, and update the status (possibly immediately if it was confirmed). localFundingStatus = LocalFundingStatus.SingleFundedUnconfirmedFundingTx(None), remoteFundingStatus = RemoteFundingStatus.Locked, - commitmentFormat = channelFeatures.commitmentFormat, + commitmentFormat = commitmentFormat, localCommitParams = localCommitParams, localCommit = localCommit1, remoteCommitParams = remoteCommitParams, @@ -259,7 +257,7 @@ private[channel] object ChannelTypes0 { nextRemoteCommit_opt = remoteNextCommitInfo.left.toOption.map(w => NextRemoteCommit(w.sent, w.nextRemoteCommit)) ) channel.Commitments( - ChannelParams(channelId, channelConfig, channelFeatures, localParams.migrate(), remoteParams.migrate(), channelFlags), + ChannelParams(channelId, channelConfig, ChannelFeatures(), localParams.migrate(), remoteParams.migrate(), channelFlags), CommitmentChanges(localChanges, remoteChanges, localNextHtlcId, remoteNextHtlcId), Seq(commitment), inactive = Nil, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala index 6f9d5289da..9259953997 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala @@ -31,7 +31,7 @@ import fr.acinq.eclair.wire.internal.channel.version2.ChannelTypes2 import fr.acinq.eclair.wire.protocol.CommonCodecs._ import fr.acinq.eclair.wire.protocol.LightningMessageCodecs._ import fr.acinq.eclair.wire.protocol.UpdateMessage -import fr.acinq.eclair.{Alias, BlockHeight, CltvExpiry, CltvExpiryDelta, FeatureSupport, Features, MilliSatoshiLong, PermanentChannelFeature} +import fr.acinq.eclair.{Alias, BlockHeight, CltvExpiry, CltvExpiryDelta, FeatureSupport, Features, InitFeature, MilliSatoshiLong} import scodec.bits.{BitVector, ByteVector, HexStringSyntax} import scodec.codecs._ import scodec.{Attempt, Codec, Err} @@ -61,17 +61,17 @@ private[channel] object ChannelCodecs3 { }) /** We use the same encoding as init features, even if we don't need the distinction between mandatory and optional */ - val channelFeaturesCodec: Codec[ChannelFeatures] = lengthDelimited(bytes).xmap( - (b: ByteVector) => ChannelFeatures(Features(b).activated.keySet.collect { case f: PermanentChannelFeature => f }), // we make no difference between mandatory/optional, both are considered activated - (cf: ChannelFeatures) => Features(cf.features.map(f => f -> FeatureSupport.Mandatory).toMap).toByteVector // we encode features as mandatory, by convention + val channelFeaturesCodec: Codec[ChannelTypes3.ChannelFeatures] = lengthDelimited(bytes).xmap( + (b: ByteVector) => ChannelTypes3.ChannelFeatures(Features(b).activated.keySet.collect { case f: InitFeature => f }), // we make no difference between mandatory/optional, both are considered activated + (cf: ChannelTypes3.ChannelFeatures) => Features(cf.features.map(f => f -> FeatureSupport.Mandatory).toMap).toByteVector // we encode features as mandatory, by convention ) - def localParamsCodec(channelFeatures: ChannelFeatures): Codec[ChannelTypes0.LocalParams] = ( + def localParamsCodec(channelFeatures: ChannelTypes3.ChannelFeatures): Codec[ChannelTypes0.LocalParams] = ( ("nodeId" | publicKey) :: ("channelPath" | keyPathCodec) :: ("dustLimit" | satoshi) :: ("maxHtlcValueInFlightMsat" | uint64) :: - ("channelReserve" | conditional(!channelFeatures.hasFeature(Features.DualFunding), satoshi)) :: + ("channelReserve" | conditional(!channelFeatures.features.contains(Features.DualFunding), satoshi)) :: ("htlcMinimum" | millisatoshi) :: ("toSelfDelay" | cltvExpiryDelta) :: ("maxAcceptedHtlcs" | uint16) :: @@ -80,11 +80,11 @@ private[channel] object ChannelCodecs3 { ("walletStaticPaymentBasepoint" | optional(provide(channelFeatures.paysDirectlyToWallet), publicKey)) :: ("features" | combinedFeaturesCodec)).as[ChannelTypes0.LocalParams] - def remoteParamsCodec(channelFeatures: ChannelFeatures): Codec[ChannelTypes0.RemoteParams] = ( + def remoteParamsCodec(channelFeatures: ChannelTypes3.ChannelFeatures): Codec[ChannelTypes0.RemoteParams] = ( ("nodeId" | publicKey) :: ("dustLimit" | satoshi) :: ("maxHtlcValueInFlightMsat" | uint64) :: - ("channelReserve" | conditional(!channelFeatures.hasFeature(Features.DualFunding), satoshi)) :: + ("channelReserve" | conditional(!channelFeatures.features.contains(Features.DualFunding), satoshi)) :: ("htlcMinimum" | millisatoshi) :: ("toSelfDelay" | cltvExpiryDelta) :: ("maxAcceptedHtlcs" | uint16) :: diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelTypes3.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelTypes3.scala index ab0c54b217..5c8ee95590 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelTypes3.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelTypes3.scala @@ -18,17 +18,33 @@ package fr.acinq.eclair.wire.internal.channel.version3 import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64} -import fr.acinq.eclair.channel import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.fund.InteractiveTxSigningSession import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.transactions.CommitmentSpec -import fr.acinq.eclair.transactions.Transactions.{CommitTx, InputInfo, UnsignedHtlcTx} +import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.wire.internal.channel.version0.ChannelTypes0 import fr.acinq.eclair.wire.protocol.CommitSig +import fr.acinq.eclair.{Features, InitFeature, PermanentChannelFeature, channel} private[channel] object ChannelTypes3 { + // We previously stored channel type features inside our channel features. + case class ChannelFeatures(features: Set[InitFeature]) { + val paysDirectlyToWallet: Boolean = features.contains(Features.StaticRemoteKey) && !features.contains(Features.AnchorOutputs) && !features.contains(Features.AnchorOutputsZeroFeeHtlcTx) + + /** Legacy option_anchor_outputs is used for Phoenix, because Phoenix doesn't have an on-chain wallet to pay for fees. */ + val commitmentFormat: CommitmentFormat = if (features.contains(Features.AnchorOutputs)) { + UnsafeLegacyAnchorOutputsCommitmentFormat + } else if (features.contains(Features.AnchorOutputsZeroFeeHtlcTx)) { + ZeroFeeHtlcTxAnchorOutputsCommitmentFormat + } else { + DefaultCommitmentFormat + } + + def migrate(): channel.ChannelFeatures = channel.ChannelFeatures(features.collect { case f: PermanentChannelFeature => f }) + } + case class WaitingForRevocation(nextRemoteCommit: RemoteCommit, sent: CommitSig, sentAfterLocalCommitIndex: Long) case class HtlcTxAndRemoteSig(htlcTx: UnsignedHtlcTx, remoteSig: ByteVector64) @@ -77,7 +93,7 @@ private[channel] object ChannelTypes3 { remoteNextCommitInfo.left.toOption.map(w => NextRemoteCommit(w.sent, w.nextRemoteCommit)) ) channel.Commitments( - ChannelParams(channelId, channelConfig, channelFeatures, localParams.migrate(), remoteParams.migrate(), channelFlags), + ChannelParams(channelId, channelConfig, channelFeatures.migrate(), localParams.migrate(), remoteParams.migrate(), channelFlags), CommitmentChanges(localChanges, remoteChanges, localNextHtlcId, remoteNextHtlcId), active = Seq(commitment), inactive = Nil, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4.scala index 1290a3a7e9..75e9de1043 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4.scala @@ -17,7 +17,7 @@ import fr.acinq.eclair.wire.internal.channel.version3.ChannelTypes3 import fr.acinq.eclair.wire.protocol.CommonCodecs._ import fr.acinq.eclair.wire.protocol.LightningMessageCodecs._ import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{Alias, CltvExpiry, CltvExpiryDelta, FeatureSupport, Features, MilliSatoshiLong, PermanentChannelFeature, RealShortChannelId, channel} +import fr.acinq.eclair.{Alias, CltvExpiry, CltvExpiryDelta, FeatureSupport, Features, InitFeature, MilliSatoshiLong, RealShortChannelId, channel} import scodec.bits.{BitVector, ByteVector, HexStringSyntax} import scodec.codecs._ import scodec.{Attempt, Codec} @@ -47,17 +47,17 @@ private[channel] object ChannelCodecs4 { }) /** We use the same encoding as init features, even if we don't need the distinction between mandatory and optional */ - val channelFeaturesCodec: Codec[ChannelFeatures] = lengthDelimited(bytes).xmap( - (b: ByteVector) => ChannelFeatures(Features(b).activated.keySet.collect { case f: PermanentChannelFeature => f }), // we make no difference between mandatory/optional, both are considered activated - (cf: ChannelFeatures) => Features(cf.features.map(f => f -> FeatureSupport.Mandatory).toMap).toByteVector // we encode features as mandatory, by convention + val channelFeaturesCodec: Codec[ChannelTypes3.ChannelFeatures] = lengthDelimited(bytes).xmap( + (b: ByteVector) => ChannelTypes3.ChannelFeatures(Features(b).activated.keySet.collect { case f: InitFeature => f }), // we make no difference between mandatory/optional, both are considered activated + (cf: ChannelTypes3.ChannelFeatures) => Features(cf.features.map(f => f -> FeatureSupport.Mandatory).toMap).toByteVector // we encode features as mandatory, by convention ) - def localParamsCodec(channelFeatures: ChannelFeatures): Codec[ChannelTypes0.LocalParams] = ( + def localParamsCodec(channelFeatures: ChannelTypes3.ChannelFeatures): Codec[ChannelTypes0.LocalParams] = ( ("nodeId" | publicKey) :: ("channelPath" | keyPathCodec) :: ("dustLimit" | satoshi) :: ("maxHtlcValueInFlightMsat" | uint64) :: - ("channelReserve" | conditional(!channelFeatures.hasFeature(Features.DualFunding), satoshi)) :: + ("channelReserve" | conditional(!channelFeatures.features.contains(Features.DualFunding), satoshi)) :: ("htlcMinimum" | millisatoshi) :: ("toSelfDelay" | cltvExpiryDelta) :: ("maxAcceptedHtlcs" | uint16) :: @@ -67,11 +67,11 @@ private[channel] object ChannelCodecs4 { ("walletStaticPaymentBasepoint" | optional(provide(channelFeatures.paysDirectlyToWallet), publicKey)) :: ("features" | combinedFeaturesCodec)).as[ChannelTypes0.LocalParams] - def remoteParamsCodec(channelFeatures: ChannelFeatures): Codec[ChannelTypes4.RemoteParams] = ( + def remoteParamsCodec(channelFeatures: ChannelTypes3.ChannelFeatures): Codec[ChannelTypes4.RemoteParams] = ( ("nodeId" | publicKey) :: ("dustLimit" | satoshi) :: ("maxHtlcValueInFlightMsat" | uint64) :: - ("channelReserve" | conditional(!channelFeatures.hasFeature(Features.DualFunding), satoshi)) :: + ("channelReserve" | conditional(!channelFeatures.features.contains(Features.DualFunding), satoshi)) :: ("htlcMinimum" | millisatoshi) :: ("toSelfDelay" | cltvExpiryDelta) :: ("maxAcceptedHtlcs" | uint16) :: diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelTypes4.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelTypes4.scala index 08668307e3..c69621e24b 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelTypes4.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelTypes4.scala @@ -25,6 +25,7 @@ import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.transactions.CommitmentSpec import fr.acinq.eclair.transactions.Transactions.{CommitmentFormat, InputInfo} import fr.acinq.eclair.wire.internal.channel.version0.ChannelTypes0 +import fr.acinq.eclair.wire.internal.channel.version3.ChannelTypes3 import fr.acinq.eclair.wire.protocol.{ChannelAnnouncement, ChannelUpdate, LiquidityAds, Shutdown, TxSignatures} import fr.acinq.eclair.{Alias, BlockHeight, CltvExpiryDelta, Features, InitFeature, MilliSatoshi, RealShortChannelId, UInt64, channel} import scodec.bits.ByteVector @@ -62,10 +63,10 @@ private[channel] object ChannelTypes4 { case class ChannelParams(channelId: ByteVector32, channelConfig: channel.ChannelConfig, - channelFeatures: channel.ChannelFeatures, + channelFeatures: ChannelTypes3.ChannelFeatures, localParams: ChannelTypes0.LocalParams, remoteParams: RemoteParams, channelFlags: channel.ChannelFlags) { - def migrate(): channel.ChannelParams = channel.ChannelParams(channelId, channelConfig, channelFeatures, localParams.migrate(), remoteParams.migrate(), channelFlags) + def migrate(): channel.ChannelParams = channel.ChannelParams(channelId, channelConfig, channelFeatures.migrate(), localParams.migrate(), remoteParams.migrate(), channelFlags) def localCommitParams(): channel.CommitParams = channel.CommitParams(localParams.dustLimit, localParams.htlcMinimum, localParams.maxHtlcValueInFlightMsat, localParams.maxAcceptedHtlcs, remoteParams.toSelfDelay) diff --git a/eclair-core/src/test/resources/nonreg/codecs/020002-DATA_NORMAL/funder/data.json b/eclair-core/src/test/resources/nonreg/codecs/020002-DATA_NORMAL/funder/data.json index 85501e3877..1bf956287e 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/020002-DATA_NORMAL/funder/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/020002-DATA_NORMAL/funder/data.json @@ -4,7 +4,7 @@ "channelParams" : { "channelId" : "5986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b", "channelConfig" : [ "funding_pubkey_based_channel_keypath" ], - "channelFeatures" : [ "option_static_remotekey" ], + "channelFeatures" : [ ], "localParams" : { "nodeId" : "03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134", "fundingKeyPath" : [ 2353764507, 3184449568, 2809819526, 3258060413, 392846475, 1545000620, 720603293, 1808318336, 2147483649 ], diff --git a/eclair-core/src/test/resources/nonreg/codecs/03000c-DATA_WAIT_FOR_DUAL_FUNDING_READY/funder/data.json b/eclair-core/src/test/resources/nonreg/codecs/03000c-DATA_WAIT_FOR_DUAL_FUNDING_READY/funder/data.json index 23fff3527e..f79f5ff3bb 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/03000c-DATA_WAIT_FOR_DUAL_FUNDING_READY/funder/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/03000c-DATA_WAIT_FOR_DUAL_FUNDING_READY/funder/data.json @@ -4,7 +4,7 @@ "channelParams" : { "channelId" : "f5cafea10bc83c2fe9de16d04bb73c1ddaed2ce9d600dd91301a7d995f7b9134", "channelConfig" : [ "funding_pubkey_based_channel_keypath" ], - "channelFeatures" : [ "option_static_remotekey", "option_anchors_zero_fee_htlc_tx", "option_dual_fund" ], + "channelFeatures" : [ "option_dual_fund" ], "localParams" : { "nodeId" : "02aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa", "fundingKeyPath" : [ 3109590638, 2571039769, 2759029525, 192289746, 3879532998, 1343053922, 3645251601, 1767821717, 2147483649 ], diff --git a/eclair-core/src/test/resources/nonreg/codecs/04000b-DATA_WAIT_FOR_CHANNEL_READY/fundee/data.json b/eclair-core/src/test/resources/nonreg/codecs/04000b-DATA_WAIT_FOR_CHANNEL_READY/fundee/data.json index 3ae13daa2a..a7ba47483c 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/04000b-DATA_WAIT_FOR_CHANNEL_READY/fundee/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/04000b-DATA_WAIT_FOR_CHANNEL_READY/fundee/data.json @@ -4,7 +4,7 @@ "channelParams" : { "channelId" : "7d975ecb75e1497076150f745b83dace95a189eecb6172c20a9a4fe0f91b8d1d", "channelConfig" : [ "funding_pubkey_based_channel_keypath" ], - "channelFeatures" : [ "option_static_remotekey" ], + "channelFeatures" : [ ], "localParams" : { "nodeId" : "02bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e63", "fundingKeyPath" : [ 3266606892, 3454308995, 379409286, 2058386039, 150235166, 1337553882, 292124276, 1286028724, 2147483648 ], diff --git a/eclair-core/src/test/resources/nonreg/codecs/04000b-DATA_WAIT_FOR_CHANNEL_READY/funder/data.json b/eclair-core/src/test/resources/nonreg/codecs/04000b-DATA_WAIT_FOR_CHANNEL_READY/funder/data.json index 8cf7bd729f..5ef13291e8 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/04000b-DATA_WAIT_FOR_CHANNEL_READY/funder/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/04000b-DATA_WAIT_FOR_CHANNEL_READY/funder/data.json @@ -4,7 +4,7 @@ "channelParams" : { "channelId" : "7d975ecb75e1497076150f745b83dace95a189eecb6172c20a9a4fe0f91b8d1d", "channelConfig" : [ "funding_pubkey_based_channel_keypath" ], - "channelFeatures" : [ "option_static_remotekey" ], + "channelFeatures" : [ ], "localParams" : { "nodeId" : "02aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa", "fundingKeyPath" : [ 222163200, 3836794389, 3217943953, 1565449417, 2862146275, 3633046910, 2547274215, 4050695153, 2147483649 ], diff --git a/eclair-core/src/test/resources/nonreg/codecs/04000d-DATA_WAIT_FOR_DUAL_FUNDING_READY/fundee/data.json b/eclair-core/src/test/resources/nonreg/codecs/04000d-DATA_WAIT_FOR_DUAL_FUNDING_READY/fundee/data.json index b9a4775d1a..70363bb00e 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/04000d-DATA_WAIT_FOR_DUAL_FUNDING_READY/fundee/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/04000d-DATA_WAIT_FOR_DUAL_FUNDING_READY/fundee/data.json @@ -4,7 +4,7 @@ "channelParams" : { "channelId" : "ae58e31828b115b8a900a9d20b060ef2b2da2fcfe18991aff34168579d246b54", "channelConfig" : [ "funding_pubkey_based_channel_keypath" ], - "channelFeatures" : [ "option_static_remotekey", "option_anchors_zero_fee_htlc_tx", "option_dual_fund" ], + "channelFeatures" : [ "option_dual_fund" ], "localParams" : { "nodeId" : "02bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e63", "fundingKeyPath" : [ 2566431519, 2591595317, 1320214673, 2312083324, 3942626068, 2510065287, 1961707336, 236707474, 2147483648 ], diff --git a/eclair-core/src/test/resources/nonreg/codecs/04000d-DATA_WAIT_FOR_DUAL_FUNDING_READY/funder/data.json b/eclair-core/src/test/resources/nonreg/codecs/04000d-DATA_WAIT_FOR_DUAL_FUNDING_READY/funder/data.json index cd187a325c..80a7846555 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/04000d-DATA_WAIT_FOR_DUAL_FUNDING_READY/funder/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/04000d-DATA_WAIT_FOR_DUAL_FUNDING_READY/funder/data.json @@ -4,7 +4,7 @@ "channelParams" : { "channelId" : "ae58e31828b115b8a900a9d20b060ef2b2da2fcfe18991aff34168579d246b54", "channelConfig" : [ "funding_pubkey_based_channel_keypath" ], - "channelFeatures" : [ "option_static_remotekey", "option_anchors_zero_fee_htlc_tx", "option_dual_fund" ], + "channelFeatures" : [ "option_dual_fund" ], "localParams" : { "nodeId" : "02aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa", "fundingKeyPath" : [ 461130578, 2441458723, 3315200959, 1624559903, 421875145, 1108846792, 2605590614, 1621284536, 2147483649 ], diff --git a/eclair-core/src/test/resources/nonreg/codecs/04000e-DATA_NORMAL/announced/data.json b/eclair-core/src/test/resources/nonreg/codecs/04000e-DATA_NORMAL/announced/data.json index 6bdb19e9ba..2afb8de0d2 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/04000e-DATA_NORMAL/announced/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/04000e-DATA_NORMAL/announced/data.json @@ -4,7 +4,7 @@ "channelParams" : { "channelId" : "c380aa11700db0a6d797dfd0be8aecfadad9397e4975f0fa8c9d10db71feac38", "channelConfig" : [ "funding_pubkey_based_channel_keypath" ], - "channelFeatures" : [ "option_static_remotekey" ], + "channelFeatures" : [ ], "localParams" : { "nodeId" : "02aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa", "fundingKeyPath" : [ 1057736322, 487845715, 3068040012, 2828312867, 3790939166, 2388092911, 4276180524, 217832603, 2147483649 ], diff --git a/eclair-core/src/test/resources/nonreg/codecs/04000e-DATA_NORMAL/splicing-private/data.json b/eclair-core/src/test/resources/nonreg/codecs/04000e-DATA_NORMAL/splicing-private/data.json index 2c0f3a88f7..75b97674e7 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/04000e-DATA_NORMAL/splicing-private/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/04000e-DATA_NORMAL/splicing-private/data.json @@ -4,7 +4,7 @@ "channelParams" : { "channelId" : "9c5655e99c1ef6d219eb093ca9f29e642167b4c776928df11c73e2293086b749", "channelConfig" : [ "funding_pubkey_based_channel_keypath" ], - "channelFeatures" : [ "option_static_remotekey", "option_dual_fund" ], + "channelFeatures" : [ "option_dual_fund" ], "localParams" : { "nodeId" : "02bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e63", "fundingKeyPath" : [ 2707154742, 1913609705, 398200054, 2009144985, 2117492679, 302300795, 2676284185, 1690699462, 2147483648 ], diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelFeaturesSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelFeaturesSpec.scala index b9185cffee..f74a051fda 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelFeaturesSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelFeaturesSpec.scala @@ -19,41 +19,11 @@ package fr.acinq.eclair.channel import fr.acinq.eclair.FeatureSupport._ import fr.acinq.eclair.Features._ import fr.acinq.eclair.channel.states.ChannelStateTestsBase -import fr.acinq.eclair.transactions.Transactions -import fr.acinq.eclair.{Feature, Features, InitFeature, TestKitBaseClass} +import fr.acinq.eclair.{Features, InitFeature, PermanentChannelFeature, TestKitBaseClass} import org.scalatest.funsuite.AnyFunSuiteLike class ChannelFeaturesSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStateTestsBase { - test("channel features determines commitment format") { - val standardChannel = ChannelFeatures() - val staticRemoteKeyChannel = ChannelFeatures(Features.StaticRemoteKey) - val anchorOutputsChannel = ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputs) - val anchorOutputsZeroFeeHtlcsChannel = ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx) - - assert(!standardChannel.hasFeature(Features.StaticRemoteKey)) - assert(!standardChannel.hasFeature(Features.AnchorOutputs)) - assert(standardChannel.commitmentFormat == Transactions.DefaultCommitmentFormat) - assert(!standardChannel.paysDirectlyToWallet) - - assert(staticRemoteKeyChannel.hasFeature(Features.StaticRemoteKey)) - assert(!staticRemoteKeyChannel.hasFeature(Features.AnchorOutputs)) - assert(staticRemoteKeyChannel.commitmentFormat == Transactions.DefaultCommitmentFormat) - assert(staticRemoteKeyChannel.paysDirectlyToWallet) - - assert(anchorOutputsChannel.hasFeature(Features.StaticRemoteKey)) - assert(anchorOutputsChannel.hasFeature(Features.AnchorOutputs)) - assert(!anchorOutputsChannel.hasFeature(Features.AnchorOutputsZeroFeeHtlcTx)) - assert(anchorOutputsChannel.commitmentFormat == Transactions.UnsafeLegacyAnchorOutputsCommitmentFormat) - assert(!anchorOutputsChannel.paysDirectlyToWallet) - - assert(anchorOutputsZeroFeeHtlcsChannel.hasFeature(Features.StaticRemoteKey)) - assert(anchorOutputsZeroFeeHtlcsChannel.hasFeature(Features.AnchorOutputsZeroFeeHtlcTx)) - assert(!anchorOutputsZeroFeeHtlcsChannel.hasFeature(Features.AnchorOutputs)) - assert(anchorOutputsZeroFeeHtlcsChannel.commitmentFormat == Transactions.ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) - assert(!anchorOutputsZeroFeeHtlcsChannel.paysDirectlyToWallet) - } - test("pick channel type based on local and remote features") { case class TestCase(localFeatures: Features[InitFeature], remoteFeatures: Features[InitFeature], announceChannel: Boolean, expectedChannelType: ChannelType) val testCases = Seq( @@ -123,22 +93,22 @@ class ChannelFeaturesSpec extends TestKitBaseClass with AnyFunSuiteLike with Cha } test("enrich channel type with optional permanent channel features") { - case class TestCase(channelType: SupportedChannelType, localFeatures: Features[InitFeature], remoteFeatures: Features[InitFeature], announceChannel: Boolean, expected: Set[Feature]) + case class TestCase(channelType: SupportedChannelType, localFeatures: Features[InitFeature], remoteFeatures: Features[InitFeature], announceChannel: Boolean, expected: Set[PermanentChannelFeature]) val testCases = Seq( TestCase(ChannelTypes.Standard(), Features(UpfrontShutdownScript -> Optional), Features.empty, announceChannel = true, Set.empty), TestCase(ChannelTypes.Standard(), Features(UpfrontShutdownScript -> Optional), Features(UpfrontShutdownScript -> Optional), announceChannel = true, Set(UpfrontShutdownScript)), TestCase(ChannelTypes.Standard(), Features(UpfrontShutdownScript -> Mandatory), Features(UpfrontShutdownScript -> Optional), announceChannel = true, Set(UpfrontShutdownScript)), - TestCase(ChannelTypes.StaticRemoteKey(), Features(UpfrontShutdownScript -> Optional), Features.empty, announceChannel = true, Set(StaticRemoteKey)), - TestCase(ChannelTypes.StaticRemoteKey(), Features(UpfrontShutdownScript -> Optional), Features(UpfrontShutdownScript -> Optional), announceChannel = true, Set(StaticRemoteKey, UpfrontShutdownScript)), - TestCase(ChannelTypes.AnchorOutputs(), Features.empty, Features(UpfrontShutdownScript -> Optional), announceChannel = true, Set(StaticRemoteKey, AnchorOutputs)), - TestCase(ChannelTypes.AnchorOutputs(), Features(UpfrontShutdownScript -> Optional), Features(UpfrontShutdownScript -> Mandatory), announceChannel = true, Set(StaticRemoteKey, AnchorOutputs, UpfrontShutdownScript)), - TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), Features.empty, Features(UpfrontShutdownScript -> Optional), announceChannel = true, Set(StaticRemoteKey, AnchorOutputsZeroFeeHtlcTx)), - TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), Features(ScidAlias -> Optional, ZeroConf -> Optional), Features(ScidAlias -> Optional, ZeroConf -> Optional), announceChannel = true, Set(StaticRemoteKey, AnchorOutputsZeroFeeHtlcTx, ZeroConf)), - TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), Features(ScidAlias -> Optional, ZeroConf -> Optional), Features(ScidAlias -> Optional, ZeroConf -> Optional), announceChannel = false, Set(StaticRemoteKey, AnchorOutputsZeroFeeHtlcTx, ScidAlias, ZeroConf)), - TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true), Features.empty, Features(UpfrontShutdownScript -> Optional), announceChannel = false, Set(StaticRemoteKey, AnchorOutputsZeroFeeHtlcTx, ScidAlias)), - TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = true), Features.empty, Features(UpfrontShutdownScript -> Optional), announceChannel = false, Set(StaticRemoteKey, AnchorOutputsZeroFeeHtlcTx, ScidAlias, ZeroConf)), - TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), Features(UpfrontShutdownScript -> Optional), Features(UpfrontShutdownScript -> Mandatory), announceChannel = true, Set(StaticRemoteKey, AnchorOutputsZeroFeeHtlcTx, UpfrontShutdownScript)), - TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), Features(DualFunding -> Optional, UpfrontShutdownScript -> Optional), Features(DualFunding -> Optional, UpfrontShutdownScript -> Optional), announceChannel = true, Set(StaticRemoteKey, AnchorOutputsZeroFeeHtlcTx, UpfrontShutdownScript, DualFunding)), + TestCase(ChannelTypes.StaticRemoteKey(), Features(UpfrontShutdownScript -> Optional), Features.empty, announceChannel = true, Set.empty), + TestCase(ChannelTypes.StaticRemoteKey(), Features(UpfrontShutdownScript -> Optional), Features(UpfrontShutdownScript -> Optional), announceChannel = true, Set(UpfrontShutdownScript)), + TestCase(ChannelTypes.AnchorOutputs(), Features.empty, Features(UpfrontShutdownScript -> Optional), announceChannel = true, Set.empty), + TestCase(ChannelTypes.AnchorOutputs(), Features(UpfrontShutdownScript -> Optional), Features(UpfrontShutdownScript -> Mandatory), announceChannel = true, Set(UpfrontShutdownScript)), + TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), Features.empty, Features(UpfrontShutdownScript -> Optional), announceChannel = true, Set.empty), + TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), Features(ScidAlias -> Optional, ZeroConf -> Optional), Features(ScidAlias -> Optional, ZeroConf -> Optional), announceChannel = true, Set(ZeroConf)), + TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), Features(ScidAlias -> Optional, ZeroConf -> Optional), Features(ScidAlias -> Optional, ZeroConf -> Optional), announceChannel = false, Set(ScidAlias, ZeroConf)), + TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true), Features.empty, Features(UpfrontShutdownScript -> Optional), announceChannel = false, Set(ScidAlias)), + TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = true), Features.empty, Features(UpfrontShutdownScript -> Optional), announceChannel = false, Set(ScidAlias, ZeroConf)), + TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), Features(UpfrontShutdownScript -> Optional), Features(UpfrontShutdownScript -> Mandatory), announceChannel = true, Set(UpfrontShutdownScript)), + TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), Features(DualFunding -> Optional, UpfrontShutdownScript -> Optional), Features(DualFunding -> Optional, UpfrontShutdownScript -> Optional), announceChannel = true, Set(UpfrontShutdownScript, DualFunding)), ) testCases.foreach(t => assert(ChannelFeatures(t.channelType, t.localFeatures, t.remoteFeatures, t.announceChannel).features == t.expected, s"channelType=${t.channelType} localFeatures=${t.localFeatures} remoteFeatures=${t.remoteFeatures}")) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala index 2fa9b1f8b6..75ca2e099a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala @@ -217,7 +217,8 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit } private def createFixtureParams(fundingAmountA: Satoshi, fundingAmountB: Satoshi, targetFeerate: FeeratePerKw, dustLimit: Satoshi, lockTime: Long, requireConfirmedInputs: RequireConfirmedInputs = RequireConfirmedInputs(forLocal = false, forRemote = false), nonInitiatorPaysCommitTxFees: Boolean = false): FixtureParams = { - val channelFeatures = ChannelFeatures(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), Features[InitFeature](Features.DualFunding -> FeatureSupport.Optional), Features[InitFeature](Features.DualFunding -> FeatureSupport.Optional), announceChannel = true) + val channelType = ChannelTypes.AnchorOutputsZeroFeeHtlcTx() + val channelFeatures = ChannelFeatures(channelType, Features[InitFeature](Features.DualFunding -> FeatureSupport.Optional), Features[InitFeature](Features.DualFunding -> FeatureSupport.Optional), announceChannel = true) val Seq(nodeParamsA, nodeParamsB) = Seq(TestConstants.Alice.nodeParams, TestConstants.Bob.nodeParams).map(_.copy(features = Features(channelFeatures.features.map(f => f -> FeatureSupport.Optional).toMap[Feature, FeatureSupport]))) val localChannelParamsA = makeChannelParams(nodeParamsA, nodeParamsA.features.initFeatures(), None, None, isChannelOpener = true, paysCommitTxFees = !nonInitiatorPaysCommitTxFees, dualFunded = true, fundingAmountA) val commitParamsA = CommitParams(nodeParamsA.channelConf.dustLimit, nodeParamsA.channelConf.htlcMinimum, nodeParamsA.channelConf.maxHtlcValueInFlight(fundingAmountA + fundingAmountB, unlimited = false), nodeParamsA.channelConf.maxAcceptedHtlcs, nodeParamsB.channelConf.toRemoteDelay) @@ -242,8 +243,8 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val channelId = randomBytes32() val fundingPubKeyA = channelKeysA.fundingKey(fundingTxIndex = 0).publicKey val fundingPubKeyB = channelKeysB.fundingKey(fundingTxIndex = 0).publicKey - val fundingParamsA = InteractiveTxParams(channelId, isInitiator = true, fundingAmountA, fundingAmountB, None, fundingPubKeyB, Nil, channelFeatures.commitmentFormat, lockTime, dustLimit, targetFeerate, requireConfirmedInputs) - val fundingParamsB = InteractiveTxParams(channelId, isInitiator = false, fundingAmountB, fundingAmountA, None, fundingPubKeyA, Nil, channelFeatures.commitmentFormat, lockTime, dustLimit, targetFeerate, requireConfirmedInputs) + val fundingParamsA = InteractiveTxParams(channelId, isInitiator = true, fundingAmountA, fundingAmountB, None, fundingPubKeyB, Nil, channelType.commitmentFormat, lockTime, dustLimit, targetFeerate, requireConfirmedInputs) + val fundingParamsB = InteractiveTxParams(channelId, isInitiator = false, fundingAmountB, fundingAmountA, None, fundingPubKeyA, Nil, channelType.commitmentFormat, lockTime, dustLimit, targetFeerate, requireConfirmedInputs) val channelParamsA = ChannelParams(channelId, ChannelConfig.standard, channelFeatures, localChannelParamsA, remoteChannelParamsB, ChannelFlags(announceChannel = true)) val channelParamsB = ChannelParams(channelId, ChannelConfig.standard, channelFeatures, localChannelParamsB, remoteChannelParamsA, ChannelFlags(announceChannel = true)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala index 6c97e6c72f..90a32eb35a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala @@ -1616,7 +1616,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w case Transactions.DefaultCommitmentFormat => None case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => Some(alice2blockchain.expectReplaceableTxPublished[ClaimRemoteAnchorTx]) } - val mainTx_opt = if (!bob.stateData.asInstanceOf[DATA_NORMAL].commitments.channelParams.channelFeatures.paysDirectlyToWallet) Some(alice2blockchain.expectFinalTxPublished("remote-main-delayed")) else None + val mainTx_opt = if (bob.stateData.asInstanceOf[DATA_NORMAL].commitments.channelParams.localParams.walletStaticPaymentBasepoint.isEmpty) Some(alice2blockchain.expectFinalTxPublished("remote-main-delayed")) else None val claimHtlcSuccess = alice2blockchain.expectMsgType[PublishReplaceableTx].copy(confirmationTarget = ConfirmationTarget.Absolute(overrideHtlcTarget)) assert(claimHtlcSuccess.txInfo.isInstanceOf[ClaimHtlcSuccessTx]) val claimHtlcTimeout = alice2blockchain.expectMsgType[PublishReplaceableTx].copy(confirmationTarget = ConfirmationTarget.Absolute(overrideHtlcTarget)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala index 606ab2fb5b..792fea73db 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala @@ -37,7 +37,7 @@ import fr.acinq.eclair.testutils.PimpTestProbe.convert import fr.acinq.eclair.transactions.Transactions import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, Features, MilliSatoshiLong, TestConstants, TestKitBaseClass, TimestampSecond, randomBytes32, randomKey} +import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, MilliSatoshiLong, TestConstants, TestKitBaseClass, TimestampSecond, randomBytes32, randomKey} import org.scalatest.Inside.inside import org.scalatest.funsuite.FixtureAnyFunSuiteLike import org.scalatest.{Outcome, Tag} @@ -292,10 +292,10 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with sender.expectMsg(RES_FAILURE(c, UnknownHtlcId(channelId(alice), 42))) } - def testMutualCloseBeforeConverge(f: FixtureParam, channelFeatures: ChannelFeatures): Unit = { + def testMutualCloseBeforeConverge(f: FixtureParam, commitmentFormat: CommitmentFormat): Unit = { import f._ val sender = TestProbe() - assert(alice.commitments.channelParams.channelFeatures == channelFeatures) + assert(alice.commitments.latest.commitmentFormat == commitmentFormat) bob.setBitcoinCoreFeerates(FeeratesPerKw.single(FeeratePerKw(2500 sat)).copy(minimum = FeeratePerKw(250 sat), slow = FeeratePerKw(250 sat))) // alice initiates a closing with a low fee alice ! CMD_CLOSE(sender.ref, None, Some(ClosingFeerates(FeeratePerKw(500 sat), FeeratePerKw(250 sat), FeeratePerKw(1000 sat)))) @@ -323,11 +323,11 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } test("recv WatchFundingSpentTriggered (mutual close before converging)") { f => - testMutualCloseBeforeConverge(f, ChannelFeatures(Features.StaticRemoteKey)) + testMutualCloseBeforeConverge(f, DefaultCommitmentFormat) } test("recv WatchFundingSpentTriggered (mutual close before converging, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => - testMutualCloseBeforeConverge(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputs)) + testMutualCloseBeforeConverge(f, UnsafeLegacyAnchorOutputsCommitmentFormat) } test("recv WatchTxConfirmedTriggered (mutual close)") { f => @@ -666,10 +666,10 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } } - def testLocalCommitTxConfirmed(f: FixtureParam, channelFeatures: ChannelFeatures): Unit = { + def testLocalCommitTxConfirmed(f: FixtureParam, commitmentFormat: CommitmentFormat): Unit = { import f._ - assert(alice.commitments.channelParams.channelFeatures == channelFeatures) + assert(alice.commitments.latest.commitmentFormat == commitmentFormat) val listener = TestProbe() systemA.eventStream.subscribe(listener.ref, classOf[LocalCommitConfirmed]) @@ -723,11 +723,11 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } test("recv WatchTxConfirmedTriggered (local commit)") { f => - testLocalCommitTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey)) + testLocalCommitTxConfirmed(f, DefaultCommitmentFormat) } test("recv WatchTxConfirmedTriggered (local commit, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => - testLocalCommitTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputs)) + testLocalCommitTxConfirmed(f, UnsafeLegacyAnchorOutputsCommitmentFormat) } test("recv WatchTxConfirmedTriggered (local commit with multiple htlcs for the same payment)") { f => @@ -1413,7 +1413,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv WatchTxConfirmedTriggered (remote commit, option_static_remotekey)", Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.SimpleClose)) { f => import f._ mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain) - assert(alice.commitments.channelParams.channelFeatures == ChannelFeatures(Features.StaticRemoteKey)) + assert(alice.commitments.latest.commitmentFormat == DefaultCommitmentFormat) // bob publishes his last current commit tx, the one it had when entering NEGOTIATING state val bobCommitTx = bobCommitTxs.last assert(bobCommitTx.txOut.size == 2) // two main outputs @@ -1431,7 +1431,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with import f._ mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain) val initialState = alice.stateData.asInstanceOf[DATA_CLOSING] - assert(initialState.commitments.channelParams.channelFeatures == ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx)) + assert(initialState.commitments.latest.commitmentFormat == ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) // bob publishes his last current commit tx, the one it had when entering NEGOTIATING state val bobCommitTx = bobCommitTxs.last assert(bobCommitTx.txOut.size == 4) // two main outputs + two anchors @@ -1447,10 +1447,10 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(alice.stateName == CLOSED) } - def testRemoteCommitTxWithHtlcsConfirmed(f: FixtureParam, channelFeatures: ChannelFeatures): Unit = { + def testRemoteCommitTxWithHtlcsConfirmed(f: FixtureParam, commitmentFormat: CommitmentFormat): Unit = { import f._ - assert(alice.commitments.channelParams.channelFeatures == channelFeatures) + assert(alice.commitments.latest.commitmentFormat == commitmentFormat) // alice sends a first htlc to bob val (ra1, htlca1) = addHtlc(15_000_000 msat, alice, bob, alice2bob, bob2alice) @@ -1463,7 +1463,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // Bob publishes the latest commit tx. val bobCommitTx = bob.signCommitTx() - channelFeatures.commitmentFormat match { + commitmentFormat match { case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(bobCommitTx.txOut.length == 7) // two main outputs + two anchors + 3 HTLCs case DefaultCommitmentFormat => assert(bobCommitTx.txOut.length == 5) // two main outputs + 3 HTLCs } @@ -1489,15 +1489,15 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } test("recv WatchTxConfirmedTriggered (remote commit with multiple htlcs for the same payment)") { f => - testRemoteCommitTxWithHtlcsConfirmed(f, ChannelFeatures(Features.StaticRemoteKey)) + testRemoteCommitTxWithHtlcsConfirmed(f, DefaultCommitmentFormat) } test("recv WatchTxConfirmedTriggered (remote commit with multiple htlcs for the same payment, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => - testRemoteCommitTxWithHtlcsConfirmed(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputs)) + testRemoteCommitTxWithHtlcsConfirmed(f, UnsafeLegacyAnchorOutputsCommitmentFormat) } test("recv WatchTxConfirmedTriggered (remote commit with multiple htlcs for the same payment, anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => - testRemoteCommitTxWithHtlcsConfirmed(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx)) + testRemoteCommitTxWithHtlcsConfirmed(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } test("recv WatchTxConfirmedTriggered (remote commit) followed by htlc settlement", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => @@ -1589,10 +1589,10 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice2blockchain.expectWatchOutputSpent(htlcTimeout.input.outPoint) } - private def testNextRemoteCommitTxConfirmed(f: FixtureParam, channelFeatures: ChannelFeatures): (Transaction, PublishedForceCloseTxs, Set[UpdateAddHtlc]) = { + private def testNextRemoteCommitTxConfirmed(f: FixtureParam, commitmentFormat: CommitmentFormat): (Transaction, PublishedForceCloseTxs, Set[UpdateAddHtlc]) = { import f._ - assert(alice.commitments.channelParams.channelFeatures == channelFeatures) + assert(alice.commitments.latest.commitmentFormat == commitmentFormat) // alice sends a first htlc to bob val (ra1, htlca1) = addHtlc(15_000_000 msat, alice, bob, alice2bob, bob2alice) @@ -1611,7 +1611,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // Bob publishes the next commit tx. val bobCommitTx = bob.signCommitTx() - channelFeatures.commitmentFormat match { + commitmentFormat match { case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(bobCommitTx.txOut.length == 7) // two main outputs + two anchors + 3 HTLCs case DefaultCommitmentFormat => assert(bobCommitTx.txOut.length == 5) // two main outputs + 3 HTLCs } @@ -1623,7 +1623,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv WatchTxConfirmedTriggered (next remote commit)") { f => import f._ - val (bobCommitTx, closingTxs, htlcs) = testNextRemoteCommitTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey)) + val (bobCommitTx, closingTxs, htlcs) = testNextRemoteCommitTxConfirmed(f, DefaultCommitmentFormat) val txPublished = txListener.expectMsgType[TransactionPublished] assert(txPublished.tx == bobCommitTx) assert(txPublished.miningFee > 0.sat) // alice is funder, she pays the fee for the remote commit @@ -1646,7 +1646,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv WatchTxConfirmedTriggered (next remote commit, static_remotekey)", Tag(ChannelStateTestsTags.StaticRemoteKey)) { f => import f._ - val (bobCommitTx, closingTxs, htlcs) = testNextRemoteCommitTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey)) + val (bobCommitTx, closingTxs, htlcs) = testNextRemoteCommitTxConfirmed(f, DefaultCommitmentFormat) alice ! WatchTxConfirmedTriggered(BlockHeight(42), 0, bobCommitTx) assert(closingTxs.mainTx_opt.isEmpty) // with static_remotekey we don't claim out main output alice2relayer.expectNoMessage(100 millis) @@ -1665,7 +1665,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv WatchTxConfirmedTriggered (next remote commit, anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => import f._ - val (bobCommitTx, closingTxs, htlcs) = testNextRemoteCommitTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx)) + val (bobCommitTx, closingTxs, htlcs) = testNextRemoteCommitTxConfirmed(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) alice ! WatchTxConfirmedTriggered(BlockHeight(42), 0, bobCommitTx) closingTxs.mainTx_opt.foreach(tx => alice ! WatchTxConfirmedTriggered(BlockHeight(45), 0, tx)) alice2relayer.expectNoMessage(100 millis) @@ -1748,7 +1748,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv INPUT_RESTORED (next remote commit, anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => import f._ - val (bobCommitTx, closingTxs, _) = testNextRemoteCommitTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx)) + val (bobCommitTx, closingTxs, _) = testNextRemoteCommitTxConfirmed(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) // simulate a node restart val beforeRestart = alice.stateData.asInstanceOf[DATA_CLOSING] @@ -1770,10 +1770,10 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice2blockchain.expectWatchOutputsSpent(htlcTimeoutTxs.map(_.input.outPoint)) } - private def testFutureRemoteCommitTxConfirmed(f: FixtureParam, channelFeatures: ChannelFeatures): Transaction = { + private def testFutureRemoteCommitTxConfirmed(f: FixtureParam, commitmentFormat: CommitmentFormat): Transaction = { import f._ val oldStateData = alice.stateData - assert(oldStateData.asInstanceOf[DATA_NORMAL].commitments.channelParams.channelFeatures == channelFeatures) + assert(oldStateData.asInstanceOf[DATA_NORMAL].commitments.latest.commitmentFormat == commitmentFormat) // This HTLC will be fulfilled. val (ra1, htlca1) = addHtlc(25_000_000 msat, alice, bob, alice2bob, bob2alice) // These 2 HTLCs should timeout on-chain, but since alice lost data, she won't be able to claim them. @@ -1807,7 +1807,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(alice.stateName == WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT) // bob is nice and publishes its commitment val bobCommitTx = bob.signCommitTx() - channelFeatures.commitmentFormat match { + commitmentFormat match { case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(bobCommitTx.txOut.length == 6) // two main outputs + two anchors + 2 HTLCs case DefaultCommitmentFormat => assert(bobCommitTx.txOut.length == 4) // two main outputs + 2 HTLCs } @@ -1817,7 +1817,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv WatchTxConfirmedTriggered (future remote commit)") { f => import f._ - val bobCommitTx = testFutureRemoteCommitTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey)) + val bobCommitTx = testFutureRemoteCommitTxConfirmed(f, DefaultCommitmentFormat) val txPublished = txListener.expectMsgType[TransactionPublished] assert(txPublished.tx == bobCommitTx) assert(txPublished.miningFee > 0.sat) // alice is funder, she pays the fee for the remote commit @@ -1834,7 +1834,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv WatchTxConfirmedTriggered (future remote commit, option_static_remotekey)", Tag(ChannelStateTestsTags.StaticRemoteKey)) { f => import f._ - val bobCommitTx = testFutureRemoteCommitTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey)) + val bobCommitTx = testFutureRemoteCommitTxConfirmed(f, DefaultCommitmentFormat) // using option_static_remotekey alice doesn't need to sweep her output awaitCond(alice.stateName == CLOSING) alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, bobCommitTx) @@ -1844,7 +1844,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv WatchTxConfirmedTriggered (future remote commit, anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => import f._ - val bobCommitTx = testFutureRemoteCommitTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx)) + val bobCommitTx = testFutureRemoteCommitTxConfirmed(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) // alice is able to claim its main output val mainTx = alice2blockchain.expectFinalTxPublished("remote-main-delayed") Transaction.correctlySpends(mainTx.tx, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) @@ -1861,7 +1861,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv INPUT_RESTORED (future remote commit)") { f => import f._ - val bobCommitTx = testFutureRemoteCommitTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey)) + val bobCommitTx = testFutureRemoteCommitTxConfirmed(f, DefaultCommitmentFormat) // simulate a node restart val beforeRestart = alice.stateData.asInstanceOf[DATA_CLOSING] @@ -1879,12 +1879,12 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with case class RevokedCloseFixture(bobRevokedTxs: Seq[RevokedCommit], htlcsAlice: Seq[(UpdateAddHtlc, ByteVector32)], htlcsBob: Seq[(UpdateAddHtlc, ByteVector32)]) - private def prepareRevokedClose(f: FixtureParam, channelFeatures: ChannelFeatures): RevokedCloseFixture = { + private def prepareRevokedClose(f: FixtureParam, commitmentFormat: CommitmentFormat): RevokedCloseFixture = { import f._ // Bob's first commit tx doesn't contain any htlc val bobCommit1 = RevokedCommit(bob.signCommitTx(), Nil) - channelFeatures.commitmentFormat match { + commitmentFormat match { case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(bobCommit1.commitTx.txOut.size == 4) // 2 main outputs + 2 anchors case DefaultCommitmentFormat => assert(bobCommit1.commitTx.txOut.size == 2) // 2 main outputs } @@ -1900,7 +1900,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } assert(alice.signCommitTx().txOut.size == bobCommit2.commitTx.txOut.size) - channelFeatures.commitmentFormat match { + commitmentFormat match { case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(bobCommit2.commitTx.txOut.size == 6) case DefaultCommitmentFormat => assert(bobCommit2.commitTx.txOut.size == 4) } @@ -1916,7 +1916,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } assert(alice.signCommitTx().txOut.size == bobCommit3.commitTx.txOut.size) - channelFeatures.commitmentFormat match { + commitmentFormat match { case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(bobCommit3.commitTx.txOut.size == 8) case DefaultCommitmentFormat => assert(bobCommit3.commitTx.txOut.size == 6) } @@ -1930,7 +1930,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } assert(alice.signCommitTx().txOut.size == bobCommit4.commitTx.txOut.size) - channelFeatures.commitmentFormat match { + commitmentFormat match { case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(bobCommit4.commitTx.txOut.size == 4) case DefaultCommitmentFormat => assert(bobCommit4.commitTx.txOut.size == 2) } @@ -1940,11 +1940,11 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with case class RevokedCloseTxs(mainTx_opt: Option[Transaction], mainPenaltyTx: Transaction, htlcPenaltyTxs: Seq[Transaction]) - private def setupFundingSpentRevokedTx(f: FixtureParam, channelFeatures: ChannelFeatures): (Transaction, RevokedCloseTxs) = { + private def setupFundingSpentRevokedTx(f: FixtureParam, commitmentFormat: CommitmentFormat): (Transaction, RevokedCloseTxs) = { import f._ - val revokedCloseFixture = prepareRevokedClose(f, channelFeatures) - assert(alice.commitments.channelParams.channelFeatures == channelFeatures) + val revokedCloseFixture = prepareRevokedClose(f, commitmentFormat) + assert(alice.commitments.latest.commitmentFormat == commitmentFormat) // bob publishes one of his revoked txs val bobRevokedTx = revokedCloseFixture.bobRevokedTxs(1).commitTx @@ -1954,7 +1954,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.size == 1) val rvk = alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.head assert(rvk.commitTx == bobRevokedTx) - if (!channelFeatures.paysDirectlyToWallet) { + if (alice.stateData.asInstanceOf[DATA_CLOSING].channelParams.localParams.walletStaticPaymentBasepoint.isEmpty) { assert(rvk.localOutput_opt.nonEmpty) } assert(rvk.remoteOutput_opt.nonEmpty) @@ -1962,7 +1962,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(rvk.htlcDelayedOutputs.isEmpty) // alice publishes the penalty txs - val mainTx_opt = if (!channelFeatures.paysDirectlyToWallet) Some(alice2blockchain.expectFinalTxPublished("remote-main-delayed")) else None + val mainTx_opt = if (alice.stateData.asInstanceOf[DATA_CLOSING].channelParams.localParams.walletStaticPaymentBasepoint.isEmpty) Some(alice2blockchain.expectFinalTxPublished("remote-main-delayed")) else None val mainPenaltyTx = alice2blockchain.expectFinalTxPublished("main-penalty") Transaction.correctlySpends(mainPenaltyTx.tx, bobRevokedTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) val htlcPenaltyTxs = (0 until 2).map(_ => alice2blockchain.expectFinalTxPublished("htlc-penalty")) @@ -1971,8 +1971,8 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // alice spends all outpoints of the revoked tx, except her main output when it goes directly to our wallet val spentOutpoints = mainTx_opt.map(_.input) ++ Seq(mainPenaltyTx.input) ++ htlcPenaltyTxs.map(_.input) - channelFeatures.commitmentFormat match { - case DefaultCommitmentFormat if channelFeatures.paysDirectlyToWallet => assert(spentOutpoints.size == bobRevokedTx.txOut.size - 1) // we don't claim our main output, it directly goes to our wallet + commitmentFormat match { + case DefaultCommitmentFormat if mainTx_opt.isEmpty => assert(spentOutpoints.size == bobRevokedTx.txOut.size - 1) // we don't claim our main output, it directly goes to our wallet case DefaultCommitmentFormat => assert(spentOutpoints.size == bobRevokedTx.txOut.size) case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(spentOutpoints.size == bobRevokedTx.txOut.size - 2) // we don't claim the anchors } @@ -1985,10 +1985,10 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with (bobRevokedTx, RevokedCloseTxs(mainTx_opt.map(_.tx), mainPenaltyTx.tx, htlcPenaltyTxs.map(_.tx))) } - private def testFundingSpentRevokedTx(f: FixtureParam, channelFeatures: ChannelFeatures): Unit = { + private def testFundingSpentRevokedTx(f: FixtureParam, commitmentFormat: CommitmentFormat): Unit = { import f._ - val (bobRevokedTx, closingTxs) = setupFundingSpentRevokedTx(f, channelFeatures) + val (bobRevokedTx, closingTxs) = setupFundingSpentRevokedTx(f, commitmentFormat) val txPublished = txListener.expectMsgType[TransactionPublished] assert(txPublished.tx == bobRevokedTx) assert(txPublished.miningFee > 0.sat) // alice is funder, she pays the fee for the revoked commit @@ -2005,20 +2005,20 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } test("recv WatchFundingSpentTriggered (one revoked tx, option_static_remotekey)", Tag(ChannelStateTestsTags.StaticRemoteKey)) { f => - testFundingSpentRevokedTx(f, ChannelFeatures(Features.StaticRemoteKey)) + testFundingSpentRevokedTx(f, DefaultCommitmentFormat) } test("recv WatchFundingSpentTriggered (one revoked tx, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => - testFundingSpentRevokedTx(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputs)) + testFundingSpentRevokedTx(f, UnsafeLegacyAnchorOutputsCommitmentFormat) } test("recv WatchFundingSpentTriggered (one revoked tx, anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => - testFundingSpentRevokedTx(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx)) + testFundingSpentRevokedTx(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } test("recv WatchFundingSpentTriggered (multiple revoked tx)") { f => import f._ - val revokedCloseFixture = prepareRevokedClose(f, ChannelFeatures(Features.StaticRemoteKey)) + val revokedCloseFixture = prepareRevokedClose(f, DefaultCommitmentFormat) assert(revokedCloseFixture.bobRevokedTxs.map(_.commitTx.txid).toSet.size == revokedCloseFixture.bobRevokedTxs.size) // all commit txs are distinct def broadcastBobRevokedTx(revokedTx: Transaction, htlcCount: Int, revokedCount: Int): RevokedCloseTxs = { @@ -2055,10 +2055,10 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(alice.stateName == CLOSED) } - def testInputRestoredRevokedTx(f: FixtureParam, channelFeatures: ChannelFeatures): Unit = { + def testInputRestoredRevokedTx(f: FixtureParam, commitmentFormat: CommitmentFormat): Unit = { import f._ - val (bobRevokedTx, closingTxs) = setupFundingSpentRevokedTx(f, channelFeatures) + val (bobRevokedTx, closingTxs) = setupFundingSpentRevokedTx(f, commitmentFormat) // simulate a node restart val beforeRestart = alice.stateData.asInstanceOf[DATA_CLOSING] @@ -2081,21 +2081,21 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } test("recv INPUT_RESTORED (one revoked tx)") { f => - testInputRestoredRevokedTx(f, ChannelFeatures(Features.StaticRemoteKey)) + testInputRestoredRevokedTx(f, DefaultCommitmentFormat) } test("recv INPUT_RESTORED (one revoked tx, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => - testInputRestoredRevokedTx(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputs)) + testInputRestoredRevokedTx(f, UnsafeLegacyAnchorOutputsCommitmentFormat) } test("recv INPUT_RESTORED (one revoked tx, anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => - testInputRestoredRevokedTx(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx)) + testInputRestoredRevokedTx(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } - def testRevokedHtlcTxConfirmed(f: FixtureParam, channelFeatures: ChannelFeatures): Unit = { + def testRevokedHtlcTxConfirmed(f: FixtureParam, commitmentFormat: CommitmentFormat): Unit = { import f._ - val revokedCloseFixture = prepareRevokedClose(f, channelFeatures) - assert(alice.commitments.channelParams.channelFeatures == channelFeatures) + val revokedCloseFixture = prepareRevokedClose(f, commitmentFormat) + assert(alice.commitments.latest.commitmentFormat == commitmentFormat) // bob publishes one of his revoked txs val bobRevokedCommit = revokedCloseFixture.bobRevokedTxs(2) @@ -2105,7 +2105,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.size == 1) val rvk = alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.head assert(rvk.commitTx == bobRevokedCommit.commitTx) - if (channelFeatures.paysDirectlyToWallet) { + if (alice.stateData.asInstanceOf[DATA_CLOSING].channelParams.localParams.walletStaticPaymentBasepoint.nonEmpty) { assert(rvk.localOutput_opt.isEmpty) } else { assert(rvk.localOutput_opt.nonEmpty) @@ -2115,7 +2115,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(rvk.htlcDelayedOutputs.isEmpty) // alice publishes the penalty txs and watches outputs - val mainTx_opt = if (!channelFeatures.paysDirectlyToWallet) Some(alice2blockchain.expectFinalTxPublished("remote-main-delayed")) else None + val mainTx_opt = if (alice.stateData.asInstanceOf[DATA_CLOSING].channelParams.localParams.walletStaticPaymentBasepoint.isEmpty) Some(alice2blockchain.expectFinalTxPublished("remote-main-delayed")) else None val mainPenalty = alice2blockchain.expectFinalTxPublished("main-penalty") val htlcPenalty = (1 to 4).map(_ => alice2blockchain.expectFinalTxPublished("htlc-penalty")) alice2blockchain.expectWatchTxConfirmed(rvk.commitTx.txid) @@ -2176,22 +2176,22 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } test("recv WatchTxConfirmedTriggered (revoked htlc-success tx, option_static_remotekey)", Tag(ChannelStateTestsTags.StaticRemoteKey)) { f => - testRevokedHtlcTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey)) + testRevokedHtlcTxConfirmed(f, DefaultCommitmentFormat) } test("recv WatchTxConfirmedTriggered (revoked htlc-success tx, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => - testRevokedHtlcTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputs)) + testRevokedHtlcTxConfirmed(f, UnsafeLegacyAnchorOutputsCommitmentFormat) } test("recv WatchTxConfirmedTriggered (revoked htlc-success tx, anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => - testRevokedHtlcTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx)) + testRevokedHtlcTxConfirmed(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } test("recv WatchTxConfirmedTriggered (revoked aggregated htlc tx)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => import f._ // bob publishes one of his revoked txs - val revokedCloseFixture = prepareRevokedClose(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx)) + val revokedCloseFixture = prepareRevokedClose(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) val bobRevokedCommit = revokedCloseFixture.bobRevokedTxs(2) alice ! WatchFundingSpentTriggered(bobRevokedCommit.commitTx) awaitCond(alice.stateData.isInstanceOf[DATA_CLOSING]) @@ -2259,7 +2259,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // Bob publishes one of his revoked txs. alice.nodeParams.setBitcoinCoreFeerates(FeeratesPerKw.single(FeeratePerKw(2_500 sat))) - val revokedCloseFixture = prepareRevokedClose(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx)) + val revokedCloseFixture = prepareRevokedClose(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) val bobRevokedCommit = revokedCloseFixture.bobRevokedTxs(2) val commitTx = bobRevokedCommit.commitTx alice ! WatchFundingSpentTriggered(commitTx) @@ -2359,10 +2359,10 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(alice.stateName == CLOSED) } - private def testRevokedTxConfirmed(f: FixtureParam, channelFeatures: ChannelFeatures): Unit = { + private def testRevokedTxConfirmed(f: FixtureParam, commitmentFormat: CommitmentFormat): Unit = { import f._ - assert(alice.commitments.channelParams.channelFeatures == channelFeatures) - val initOutputCount = channelFeatures.commitmentFormat match { + assert(alice.commitments.latest.commitmentFormat == commitmentFormat) + val initOutputCount = commitmentFormat match { case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => 4 case DefaultCommitmentFormat => 2 } @@ -2409,15 +2409,15 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } test("recv WatchTxConfirmedTriggered (revoked commit tx, pending htlcs)") { f => - testRevokedTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey)) + testRevokedTxConfirmed(f, DefaultCommitmentFormat) } test("recv WatchTxConfirmedTriggered (revoked commit tx, pending htlcs, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => - testRevokedTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputs)) + testRevokedTxConfirmed(f, UnsafeLegacyAnchorOutputsCommitmentFormat) } test("recv WatchTxConfirmedTriggered (revoked commit tx, pending htlcs, anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => - testRevokedTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx)) + testRevokedTxConfirmed(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } test("recv ChannelReestablish") { f => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentPacketSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentPacketSpec.scala index 03269bf14e..785cd099d8 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentPacketSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentPacketSpec.scala @@ -35,6 +35,7 @@ import fr.acinq.eclair.reputation.Reputation import fr.acinq.eclair.router.BaseRouterSpec.{blindedRouteFromHops, channelHopFromUpdate} import fr.acinq.eclair.router.BlindedRouteCreation import fr.acinq.eclair.router.Router.{NodeHop, Route} +import fr.acinq.eclair.transactions.Transactions.DefaultCommitmentFormat import fr.acinq.eclair.wire.protocol.OfferTypes.{InvoiceRequest, Offer, PaymentInfo} import fr.acinq.eclair.wire.protocol.PaymentOnion.{FinalPayload, IntermediatePayload, OutgoingBlindedPerHopPayload} import fr.acinq.eclair.wire.protocol._ @@ -757,10 +758,11 @@ object PaymentPacketSpec { case None => LocalFundingStatus.SingleFundedUnconfirmedFundingTx(None) } val channelFlags = ChannelFlags(announceChannel = announcement_opt.nonEmpty) + val commitmentFormat = DefaultCommitmentFormat new Commitments( ChannelParams(channelId, ChannelConfig.standard, channelFeatures, localChannelParams, remoteChannelParams, channelFlags), CommitmentChanges(localChanges, remoteChanges, 0, 0), - List(Commitment(0, 0, OutPoint(fundingTx, 0), testCapacity, randomKey().publicKey, localFundingStatus, RemoteFundingStatus.Locked, channelFeatures.commitmentFormat, commitParams, localCommit, commitParams, remoteCommit, None)), + List(Commitment(0, 0, OutPoint(fundingTx, 0), testCapacity, randomKey().publicKey, localFundingStatus, RemoteFundingStatus.Locked, commitmentFormat, commitParams, localCommit, commitParams, remoteCommit, None)), inactive = Nil, Right(randomKey().publicKey), ShaChain.init, diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala index 38297eb98f..1931aa8b1b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala @@ -20,12 +20,11 @@ import fr.acinq.bitcoin.ScriptFlags import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.scalacompat.{ByteVector32, Crypto, Satoshi, SatoshiLong, Script, Transaction} import fr.acinq.eclair.blockchain.fee.FeeratePerKw -import fr.acinq.eclair.channel.ChannelFeatures import fr.acinq.eclair.crypto.keymanager.{ChannelKeys, LocalCommitmentKeys, RemoteCommitmentKeys} import fr.acinq.eclair.reputation.Reputation import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.wire.protocol.UpdateAddHtlc -import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, Features, MilliSatoshi, MilliSatoshiLong, TestConstants} +import fr.acinq.eclair.{ChannelTypeFeature, CltvExpiry, CltvExpiryDelta, Features, MilliSatoshi, MilliSatoshiLong, TestConstants} import grizzled.slf4j.Logging import org.scalatest.funsuite.AnyFunSuite import scodec.bits._ @@ -36,10 +35,17 @@ trait TestVectorsSpec extends AnyFunSuite with Logging { // @formatter:off def filename: String - def channelFeatures: ChannelFeatures - val commitmentFormat = channelFeatures.commitmentFormat + def channelFeatures: Set[ChannelTypeFeature] // @formatter:on + val commitmentFormat: CommitmentFormat = if (channelFeatures.contains(Features.AnchorOutputsZeroFeeHtlcTx)) { + ZeroFeeHtlcTxAnchorOutputsCommitmentFormat + } else if (channelFeatures.contains(Features.AnchorOutputs)) { + UnsafeLegacyAnchorOutputsCommitmentFormat + } else { + DefaultCommitmentFormat + } + val tests = { val tests = collection.mutable.HashMap.empty[String, Map[String, String]] val current = collection.mutable.HashMap.empty[String, String] @@ -98,7 +104,7 @@ trait TestVectorsSpec extends AnyFunSuite with Logging { val funding_pubkey = funding_privkey.publicKey val per_commitment_point = PublicKey(hex"025f7117a78150fe2ef97db7cfc83bd57b2e2c0d0dd25eaf467a4a1c2a45ce1486") val htlc_privkey = ChannelKeys.derivePerCommitmentKey(payment_basepoint_secret, per_commitment_point) - val payment_privkey = if (channelFeatures.hasFeature(Features.StaticRemoteKey)) payment_basepoint_secret else htlc_privkey + val payment_privkey = if (channelFeatures.contains(Features.StaticRemoteKey)) payment_basepoint_secret else htlc_privkey val delayed_payment_privkey = ChannelKeys.derivePerCommitmentKey(delayed_payment_basepoint_secret, per_commitment_point) val revocation_pubkey = PublicKey(hex"0212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b19") val feerate_per_kw = 15000 @@ -115,7 +121,7 @@ trait TestVectorsSpec extends AnyFunSuite with Logging { val funding_privkey = PrivateKey(hex"1552dfba4f6cf29a62a0af13c8d6981d36d0ef8d61ba10fb0fe90da7634d7e1301") val funding_pubkey = funding_privkey.publicKey val htlc_privkey = ChannelKeys.derivePerCommitmentKey(payment_basepoint_secret, Local.per_commitment_point) - val payment_privkey = if (channelFeatures.hasFeature(Features.StaticRemoteKey)) payment_basepoint_secret else htlc_privkey + val payment_privkey = if (channelFeatures.contains(Features.StaticRemoteKey)) payment_basepoint_secret else htlc_privkey } // Keys used by the local node to spend outputs of its local commitment. @@ -441,27 +447,27 @@ trait TestVectorsSpec extends AnyFunSuite with Logging { class DefaultCommitmentTestVectorSpec extends TestVectorsSpec { // @formatter:off override def filename: String = "/bolt3-tx-test-vectors-default-commitment-format.txt" - override def channelFeatures: ChannelFeatures = ChannelFeatures() + override def channelFeatures: Set[ChannelTypeFeature] = Set.empty // @formatter:on } class StaticRemoteKeyTestVectorSpec extends TestVectorsSpec { // @formatter:off override def filename: String = "/bolt3-tx-test-vectors-static-remotekey-format.txt" - override def channelFeatures: ChannelFeatures = ChannelFeatures(Features.StaticRemoteKey) + override def channelFeatures: Set[ChannelTypeFeature] = Set(Features.StaticRemoteKey) // @formatter:on } class AnchorOutputsTestVectorSpec extends TestVectorsSpec { // @formatter:off override def filename: String = "/bolt3-tx-test-vectors-anchor-outputs-format.txt" - override def channelFeatures: ChannelFeatures = ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputs) + override def channelFeatures: Set[ChannelTypeFeature] = Set(Features.StaticRemoteKey, Features.AnchorOutputs) // @formatter:on } class AnchorOutputsZeroFeeHtlcTxTestVectorSpec extends TestVectorsSpec { // @formatter:off override def filename: String = "/bolt3-tx-test-vectors-anchor-outputs-zero-fee-htlc-tx-format.txt" - override def channelFeatures: ChannelFeatures = ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx) + override def channelFeatures: Set[ChannelTypeFeature] = Set(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx) // @formatter:on } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2Spec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2Spec.scala index a4d9772623..48a558a8ab 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2Spec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2Spec.scala @@ -1,9 +1,9 @@ package fr.acinq.eclair.wire.internal.channel.version2 import fr.acinq.bitcoin.scalacompat.{OutPoint, Transaction} -import fr.acinq.eclair.Features import fr.acinq.eclair.TestUtils.randomTxId import fr.acinq.eclair.channel.{ChannelConfig, ChannelDataWithCommitments, ChannelFeatures} +import fr.acinq.eclair.transactions.Transactions.{DefaultCommitmentFormat, UnsafeLegacyAnchorOutputsCommitmentFormat} import fr.acinq.eclair.wire.internal.channel.version2.ChannelCodecs2.Codecs._ import fr.acinq.eclair.wire.internal.channel.version2.ChannelCodecs2.channelDataCodec import org.scalatest.funsuite.AnyFunSuite @@ -30,18 +30,24 @@ class ChannelCodecs2Spec extends AnyFunSuite { val commitments = channelDataCodec.decode(dataNormal.bits).require.value.asInstanceOf[ChannelDataWithCommitments].commitments assert(commitments.channelParams.channelConfig == ChannelConfig.standard) assert(commitments.channelParams.channelFeatures == ChannelFeatures()) + assert(commitments.channelParams.localParams.walletStaticPaymentBasepoint.isEmpty) + assert(commitments.latest.commitmentFormat == DefaultCommitmentFormat) } { val staticRemoteKeyChannel = hex"00020000000303af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d000090ef5e61dc12e5215dfcf8a1263f66b998a162fd80ea9aabf4fd1483d63fcd68280000001000000000000044c0000000008f0d1800000000000002710000000000000000000900064ff160014170e6de64a6d55c73f3ff657ef441d43739837ab028feba10d0eafd0fad8fe20e6d9206e6bd30242826de05c63f459a00aced24b120000186b0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000026982039dc0e0b1d25905e44fdf6f8e89755a5e219685840d0bc1d28d3308f9628a358500000000000003e8ffffffffffffffff0000000000004e2000000000000003e80090001e023da0b6ec447fbda75506f743247a7fb6f8e29c1e18cfff53f2f9136801c617a7022dc9905a27397dedf60ef915579f2464c8fa930fae14adb6563e5559905067e5028feba10d0eafd0fad8fe20e6d9206e6bd30242826de05c63f459a00aced24b12032ed22e583b835accc8b4b5bfc94331ce06b07c31952551fd9520e126b1ed43c703da7493b310a19286c452e3d1f8ee66afaf6f1656736f6464d00deedd0b58585b00000003026982000000000000000000000000002710000000002faf0800000000000bebc20024f452ff0b3c6f363530f2a203e8fd6fcd88d177a2dac14b4b045abead090e4f4f000000002b40420f000000000022002065adbae4db91fa9069e2c8dce1f716c65e6dbc302229772d62305bd2e0e6494e475221023da0b6ec447fbda75506f743247a7fb6f8e29c1e18cfff53f2f9136801c617a72103a62130179b4cb0451ddbcd4480c326d5343a691bc01e07bb6eee35fc91a7601752aefd015b02000000000101f452ff0b3c6f363530f2a203e8fd6fcd88d177a2dac14b4b045abead090e4f4f000000000036a2ab8002400d030000000000160014761879f7b274ce995f87150a02e75cc0c037e8e3b8180c00000000002200204b354f6e432cae820a572c8294423f4bc6a5b9a2509d3de22b93c6316b59e14b0400483045022100fe9a0106b51293216f200a5a0b17a783f7ace4edbd71b644dee3e25c2688834802203a1087649b2cba80f5a634c1c67fbce4355f6c5605dd873f12d0399a542e363d01483045022100e71334e385755ab65b7ac6be2709a0be39f98fde6a6c10a895fd6f90ecd3fd7d022036550ecd40ed942f0a4ae3d0e29b6142a3cc4492eb73c3a62ced6d934e48584d01475221023da0b6ec447fbda75506f743247a7fb6f8e29c1e18cfff53f2f9136801c617a72103a62130179b4cb0451ddbcd4480c326d5343a691bc01e07bb6eee35fc91a7601752aeac67892000000000000000000000000000002710000000000bebc200000000002faf080018fa9f3051c52bba85fc20763e8835aed58c763f52a1647c90cc850aeb62ac3d02eada0cd5618069ed848f3975a631f02c87beb9968df8bd99511fa08f75d64529000000000000000000000000000000000000000000000000000000000000ff03b960d87d264cc2c99f71ed6ba6dd1bdb06c03666f76d90a1670a73dc468428e924f452ff0b3c6f363530f2a203e8fd6fcd88d177a2dac14b4b045abead090e4f4f000000002b40420f000000000022002065adbae4db91fa9069e2c8dce1f716c65e6dbc302229772d62305bd2e0e6494e475221023da0b6ec447fbda75506f743247a7fb6f8e29c1e18cfff53f2f9136801c617a72103a62130179b4cb0451ddbcd4480c326d5343a691bc01e07bb6eee35fc91a7601752ae000000f452ff0b3c6f363530f2a203e8fd6fcd88d177a2dac14b4b045abead090e4f4f061a8000002a00000000884a5e841be07f8612321ece5e40b39d715ffe94978c06cf3bb3af1a0cadcefc4b4aaade3deb191662f00b304edeae18ae8c80ed3dc505e8c78876b6fb61682fcb06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f061a8000002a0000629dbe540101009000000000000003e8000854d00000000a000000003b9aca000000" val commitments = channelDataCodec.decode(staticRemoteKeyChannel.bits).require.value.asInstanceOf[ChannelDataWithCommitments].commitments assert(commitments.channelParams.channelConfig == ChannelConfig.standard) - assert(commitments.channelParams.channelFeatures == ChannelFeatures(Features.StaticRemoteKey)) + assert(commitments.channelParams.channelFeatures == ChannelFeatures()) + assert(commitments.channelParams.localParams.walletStaticPaymentBasepoint.nonEmpty) + assert(commitments.latest.commitmentFormat == DefaultCommitmentFormat) } { val anchorOutputsChannel = hex"00020000000703af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d000094b82816a3f15ab3e324ddf6f0f5a116cef8c3200a353240db684337d2dcaa81e80000001000000000000044c000000001dcd65000000000000002710000000000000000000900064ff160014f09f6f93de60c6af417ed31d52c2c883b6113f260000186b0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000225982039dc0e0b1d25905e44fdf6f8e89755a5e219685840d0bc1d28d3308f9628a358500000000000003e8ffffffffffffffff0000000000004e2000000000000003e80090001e02e9cd12509fbc345c10e2b2e10427ae43eabbf0788bda8118b34344e5576d5d5002eb112dd8d61a02bf3434d5417e1020ed2d951e75bd1e14615d473dd5f1991b4402ca8b6891d6a53aea035259fa0d57a1e672d3cc59a2c696a96247d3557ee726cd030f2aa0ca0ae0e00d6da847d897cf7288ae286dd12037f06250937dc7b37fe0b5028d59e68c5799980a98b120acb891dabfd091e80c6596b0c2a7de57a298d48512000000032259820000000000000000000000000009c4000000002faf0800000000000bebc2002432e16e6e875d1f02a9512943ba2eb9309f4715390b52e4965dd6e39a2989ae63000000002b40420f00000000002200204de81ee2e9850dabdb9c364a12cc2dd75dd20c21d4ca0a809a2e6af3caeeaf9647522102e9cd12509fbc345c10e2b2e10427ae43eabbf0788bda8118b34344e5576d5d502103683425bf640d5e4338553ca6f6afea948ff2bf378eed1b3e9497a3f18f477d5352aefd01bc0200000000010132e16e6e875d1f02a9512943ba2eb9309f4715390b52e4965dd6e39a2989ae63000000000089273f80044a010000000000002200202102815a0b14d50a2a8c028cd47413e743f02ad5460151bd4ae5930639cbbd484a010000000000002200204e81bd6470c45c349068c2b0ad544934b718ef49c1615f2a0b0cd8766400b562400d0300000000002200208eadab92c95168438acfb73f2d3d92f56d6fc30d3f79fc4527f4e77df88b8add72270c0000000000220020895c0fef7efb7aa0e4805381434363b30386d31f6cb621abb0ca4a52b269fbc304004830450221009f09a584b1af9ba86f1618aad1ca9817324f97c3d3789b2490c3b9435c7bf1dd02205e4dcca3d7937f6b80a0ed39084e4ea9e9c531b80698ebd0c116fd9319a0db1201473044022001e4104ec25ba9bc43bf3f623d6e98f371434442b134255667ddeedd9cd9cd1b02201f057d2c15ee4e5d62a73f1db262846ca7ad35deaed2496abdb5b16cd91c5cb80147522102e9cd12509fbc345c10e2b2e10427ae43eabbf0788bda8118b34344e5576d5d502103683425bf640d5e4338553ca6f6afea948ff2bf378eed1b3e9497a3f18f477d5352ae25899520000000000000000000000000000009c4000000000bebc200000000002faf0800acdb0bf90ed7350d429c2d70fce66300ac9b27d8dbb3bceb5bd2cc8bd443938f03f54d00601f1b95e82836540d760ab6b38bc6fc1701df5d5e3b410f1936212d54000000000000000000000000000000000000000000000000000000000000ff03658b1e02efd8464688f87a052518691ff4d818a7c36addf4c6520ae07b7e20912432e16e6e875d1f02a9512943ba2eb9309f4715390b52e4965dd6e39a2989ae63000000002b40420f00000000002200204de81ee2e9850dabdb9c364a12cc2dd75dd20c21d4ca0a809a2e6af3caeeaf9647522102e9cd12509fbc345c10e2b2e10427ae43eabbf0788bda8118b34344e5576d5d502103683425bf640d5e4338553ca6f6afea948ff2bf378eed1b3e9497a3f18f477d5352ae00000032e16e6e875d1f02a9512943ba2eb9309f4715390b52e4965dd6e39a2989ae63061a8000002a000000008833a9275befbedbf928e743a71c9476f46528baf6e832ad25478c2607e4108dea67657c51db7344032627c855518179dc7a94b59f306faf9a3b4eef7684d5d73106226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f061a8000002a000060e32a590101009000000000000003e8000854d00000000a000000003b9aca000000" val commitments = channelDataCodec.decode(anchorOutputsChannel.bits).require.value.asInstanceOf[ChannelDataWithCommitments].commitments assert(commitments.channelParams.channelConfig == ChannelConfig.standard) - assert(commitments.channelParams.channelFeatures == ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputs)) + assert(commitments.channelParams.channelFeatures == ChannelFeatures()) + assert(commitments.channelParams.localParams.walletStaticPaymentBasepoint.isEmpty) + assert(commitments.latest.commitmentFormat == UnsafeLegacyAnchorOutputsCommitmentFormat) } { val wumboChannel = hex"00000000000103af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d00009b3b6da18b7e5e43405415a43e61abd77edf4d300c131af17a955950837d7db4980000001000000000000044c000000001dcd65000000000000002710000000000000000000900064ff1600142946bdbb3141be9a40ed8133ef159ee0022176e90000186b02000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a4982039dc0e0b1d25905e44fdf6f8e89755a5e219685840d0bc1d28d3308f9628a358500000000000003e8ffffffffffffffff0000000000004e2000000000000003e80090001e03a6be2613a9ff761a769b077abb43d76c5f06944fc32010891156179bf8b81607025c24f0c3572631ea09dade913ccbb1cd8a55585fc152cc2165bf4f43af702c8b0306c241c667eefee031639052c4a35c4ae076ab56b49f76f7e12f63dfb7bb32a503fdbff1ca92c99c09a9c0eac3cfe1ca35deb658d5fdb815fe7a240df25da5e20f02c47d9e4e1fb501c81933cfc4d3ba7f0e8203003cda2683569f1f67aac2b6e20d000000030a4982000000000000000000000000002710000000745e66c600000000000bebc2002464fc06b43e94d9ce0a5a0e1ef13f63ba78e56d920e7d606d0c6e7a631c48c32c000000002b0065cd1d00000000220020d1b88cc50b09abce945ce6703f30dae3d71ec626be2201925db9379a3e8b981047522103044d393b0da6bcd92030f3131d0780b0d6fa017a0ff45b7fed708903c2950ced2103a6be2613a9ff761a769b077abb43d76c5f06944fc32010891156179bf8b8160752aefd01590200000000010164fc06b43e94d9ce0a5a0e1ef13f63ba78e56d920e7d606d0c6e7a631c48c32c000000000018e2678002400d030000000000160014cf3c9d08ec27d3ffefbf7e2640d3773e11a60ceb783bca1d00000000220020153e514ade07f347db9ed32cf6b2096d066d48e0cf8c504590cd1155de68be2204004730440220056ac83d5b2eb9eb9714e85294d080a9b64f73916d8206b15533613b4303ed3d022044da40ba433e27ddb8743c75acc0f6b050c346bc9d241ab3a1b65051820ce06a0147304402207683c10f33682e2389e5f02dd34090bbe4d786633d49e1777d5ec2ab71a7924902206d99dceb74ff809237ae8e06c118b38047e2d4a49f9956c538bedca47cb4b1da0147522103044d393b0da6bcd92030f3131d0780b0d6fa017a0ff45b7fed708903c2950ced2103a6be2613a9ff761a769b077abb43d76c5f06944fc32010891156179bf8b8160752aef5c4602000000000000000000000000000002710000000000bebc200000000745e66c6000bdebd6cf39db4c2ce1a675281b0a714e525525149032ba048d75c89e5a9b355021a87b53e12e4b36287635cdf887991a732c2fd56b12f5377d755b8b890795db6000000000000000000000000000000000000000000000000000000000000ff032c5f941f78a00105b995fba7f3c340afe53e287a60361135386ae1448d6f3d632464fc06b43e94d9ce0a5a0e1ef13f63ba78e56d920e7d606d0c6e7a631c48c32c000000002b0065cd1d00000000220020d1b88cc50b09abce945ce6703f30dae3d71ec626be2201925db9379a3e8b981047522103044d393b0da6bcd92030f3131d0780b0d6fa017a0ff45b7fed708903c2950ced2103a6be2613a9ff761a769b077abb43d76c5f06944fc32010891156179bf8b8160752ae00000064fc06b43e94d9ce0a5a0e1ef13f63ba78e56d920e7d606d0c6e7a631c48c32cff5e020000000101010101010101010101010101010101010101010101010101010101010101012a00000000ffffffff010065cd1d00000000220020d1b88cc50b09abce945ce6703f30dae3d71ec626be2201925db9379a3e8b9810000000000000000060e32bf9000082000000000000000000000000000000000000000000000000000000000000000064fc06b43e94d9ce0a5a0e1ef13f63ba78e56d920e7d606d0c6e7a631c48c32c0000f4957823b0a6b76697d7c545bcca66abec8522a0f6350a722e45f3f2830f7f824c84efed8daffa04bb0822ce1a08fb2de77dd20c23a91cced7a3966808956644" diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4Spec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4Spec.scala index 71a3d75410..a7bb87db0c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4Spec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4Spec.scala @@ -9,6 +9,7 @@ import fr.acinq.eclair.channel.fund.InteractiveTxBuilder.{InteractiveTxParams, P import fr.acinq.eclair.transactions.Scripts import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.wire.internal.channel.version0.ChannelTypes0 +import fr.acinq.eclair.wire.internal.channel.version3.ChannelTypes3 import fr.acinq.eclair.wire.internal.channel.version4.ChannelCodecs4.Codecs._ import fr.acinq.eclair.wire.internal.channel.version4.ChannelCodecs4.channelDataCodec import fr.acinq.eclair.wire.protocol.TxSignatures @@ -64,16 +65,16 @@ class ChannelCodecs4Spec extends AnyFunSuite { None) { - val localCodec = localParamsCodec(ChannelFeatures()) - val remoteCodec = remoteParamsCodec(ChannelFeatures()) + val localCodec = localParamsCodec(ChannelTypes3.ChannelFeatures(Set.empty)) + val remoteCodec = remoteParamsCodec(ChannelTypes3.ChannelFeatures(Set.empty)) val decodedLocalParams = localCodec.decode(localCodec.encode(localParams).require).require.value val decodedRemoteParams = remoteCodec.decode(remoteCodec.encode(remoteParams).require).require.value assert(decodedLocalParams == localParams) assert(decodedRemoteParams == remoteParams) } { - val localCodec = localParamsCodec(ChannelFeatures(Features.DualFunding)) - val remoteCodec = remoteParamsCodec(ChannelFeatures(Features.DualFunding)) + val localCodec = localParamsCodec(ChannelTypes3.ChannelFeatures(Set(Features.DualFunding))) + val remoteCodec = remoteParamsCodec(ChannelTypes3.ChannelFeatures(Set(Features.DualFunding))) val decodedLocalParams = localCodec.decode(localCodec.encode(localParams).require).require.value val decodedRemoteParams = remoteCodec.decode(remoteCodec.encode(remoteParams).require).require.value assert(decodedLocalParams == localParams.copy(initialRequestedChannelReserve_opt = None)) @@ -119,10 +120,10 @@ class ChannelCodecs4Spec extends AnyFunSuite { test("decode dual-funded unsigned local commit") { val bin = ByteVector.fromValidHex("00130d411da7b37693960ef0a495ffa8c0fcbf82c1fd8034ab3bf37f8a184242b7230101041000100002aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa0009f22f74ef88d63eb6ece1076957feb94a7cf7f20b07f3ee710a9f2ac024d3872180000001000000000000044c000000001dcd6500000000000000000000900064c0000392a15254566846b06ea693dbeedb1a6ded24b02d53ee40f091f628dc117a1a5d0000186b02000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000001808220a598202bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e6300000000000003e8000000003b9aca0000000000000003e80090001e021ef6e2ee1a5d15aba3c663f1b608ed66315fa8a855b91f53eefa8debec663b5e0392a15254566846b06ea693dbeedb1a6ded24b02d53ee40f091f628dc117a1a5d0289365d47702d068083d9444c9167be1addfe353d9466a9805215c89361aaf85903e60c20e9744d7a3ff07a72c0170569a9841ea2fb6e0dac7d90ee550a491e931b0000001408000000000000000000000000001008228a598200000262866d2d58787368382a8fc145e34c08f5fbfd54d582b582e088de5e32fd8736000000000000000000000000000000000d411da7b37693960ef0a495ffa8c0fcbf82c1fd8034ab3bf37f8a184242b723ff00000000000f4240000000000007a120000230d9b88fad260907c43f97b88bb9992bc334ef29015cae8d7a8aa5063539931d000000061a80000000000000044c000027100000000000000002000000000000000422002059106401877b19606b711f9980431fd80e830d2a30c59103d7f12e16a7e141d3000000003b9aca00000000001dcd6500000000000000000000010100000000000000005202000000014a12a0174ed233d1e65150f14ccb948a0508dbeaaa49236dce4c4fde7b69d31201000000000000000001e0c81000000000001600149e0bb5d89fbd10f3054bdde0fa3e22d42d1e7abc000000000000000000000000000101000000000000000124e3b1062400aaf012a8e25bb0932a3de9ab16a1accf7302b333f6020dfea5a22b000000002bc0270900000000002251202ee2991cf9a98cb8be01ed4ebc8279bcd84536bed6f73f4dd0012d1927d5d8250000000000010100000000000000020000000000016ec21600149e0bb5d89fbd10f3054bdde0fa3e22d42d1e7abc000101000000000000000300000000000176d82251202ee2991cf9a98cb8be01ed4ebc8279bcd84536bed6f73f4dd0012d1927d5d82500061a80af0d411da7b37693960ef0a495ffa8c0fcbf82c1fd8034ab3bf37f8a184242b723b0e1b56831c5dd8ef32c85df9896c057c36a7e8d3c95aeba8c9a4da79b0aa1430001006b0247304402203f4fdd467e2d27c89946d2851004c01988294b0071748c4acd315c90b9ab0b1902202c86d68228cf322d4b676ece89a5106d2518565cb859c436b9284d78f81b4d1201210392a15254566846b06ea693dbeedb1a6ded24b02d53ee40f091f628dc117a1a5d000000000000000000000000002710000000003b9aca00000000001dcd650024b0e1b56831c5dd8ef32c85df9896c057c36a7e8d3c95aeba8c9a4da79b0aa143020000002b60e316000000000022002059106401877b19606b711f9980431fd80e830d2a30c59103d7f12e16a7e141d3007d0200000001b0e1b56831c5dd8ef32c85df9896c057c36a7e8d3c95aeba8c9a4da79b0aa1430200000000a9d907800220a10700000000001600149e0bb5d89fbd10f3054bdde0fa3e22d42d1e7abcf8250f00000000002200203737db5c8aa01d33fd415cabd8b8680a16527c8d9c6fe32ab5cedbe610484aa187c9772000000000000000000000000000002710000000001dcd6500000000003b9aca007c6fd871c702806ae018d8609df56a1705d275b99994bb3563878d554fed62f4039fa3697fcaf99e44e3a5b601600667e00d22e940cba46708e3bf0beae9fda8e20000") val decoded = channelDataCodec.decode(bin.bits).require.value.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_SIGNED] - assert(decoded.channelParams.channelFeatures.commitmentFormat == DefaultCommitmentFormat) - assert(decoded.channelParams.channelFeatures.features == Set(Features.DualFunding, Features.StaticRemoteKey)) + assert(decoded.channelParams.channelFeatures.features == Set(Features.DualFunding)) // Local params. assert(decoded.channelParams.localParams.isChannelOpener) + assert(decoded.channelParams.localParams.walletStaticPaymentBasepoint.nonEmpty) assert(decoded.signingSession.localCommitParams.toSelfDelay == CltvExpiryDelta(144)) assert(decoded.signingSession.localCommitParams.dustLimit == 1100.sat) assert(decoded.signingSession.localCommitParams.htlcMinimum == 0.msat) @@ -136,6 +137,7 @@ class ChannelCodecs4Spec extends AnyFunSuite { assert(decoded.signingSession.remoteCommitParams.maxHtlcValueInFlight == UInt64(1_000_000_000)) // Signing session. assert(decoded.signingSession.fundingTxIndex == 0) + assert(decoded.signingSession.fundingParams.commitmentFormat == DefaultCommitmentFormat) assert(decoded.signingSession.fundingTx.txId == TxId.fromValidHex("43a10a9ba74d9a8cbaae953c8d7e6ac357c09698df852cf38eddc53168b5e1b0")) assert(decoded.signingSession.fundingTx.tx.sharedOutput.amount == 1_500_000.sat) assert(decoded.signingSession.fundingTx.tx.sharedOutput.pubkeyScript == hex"002059106401877b19606b711f9980431fd80e830d2a30c59103d7f12e16a7e141d3") @@ -149,12 +151,13 @@ class ChannelCodecs4Spec extends AnyFunSuite { test("decode splice unsigned local commit") { val bin = ByteVector.fromValidHex("001801d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d230101041000100002bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e630009439468f0eab4a6375ce444fc12eaf57df47aba7e2644be598e5249349f8172218000000000000000000003e8000000003b9aca0000000000000003e80090001e000003fcfbfa87a789958625832770826a2e391b87880a5698e601033942c75173460a0000001408000000000000000000000000001008228a598202aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa000000000000044c000000001dcd6500000000000000000000900064027800eedc36e641dff2b5cd5041895a8dbff6b52bf1d8fe58511fe0b2ae60c19503fcfbfa87a789958625832770826a2e391b87880a5698e601033942c75173460a03c6804903002dbb70e488fb53009ea0a024f87acafda1d96f8a0f1fd2f98b3e1302a26a217b4263be92199009db2b503e48272c0600d753a97ae34bd9827251c6700000186b02000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000001808220a59820000000000000000000000000000000000000000000200000000000000020004fffd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000000000000000e4e1c0342bb8ca8412a52cbaa04072e0e285ff414a4bf36be1828482705a6bd13501a200061b100003e368b6d07bc35ff7e3475feddeb313912771046d96fa86ec9a5ba2e9024f5df44c2609468b1f86640de3863c1e3ff60042667392d43809462bc3d18d454ad70de3d85e738909aaa1930559a093e363449e7a00c85cb62562298a108131f6f88635d990d4a6de980d076bfee5281d62828c19da4db7843c304c6d2fe742a8421ffb46c26b5e06249fb8fb4ff0b4d1f08d44d8d7d87953db3da6154f8d31d5af9780f6e45f4867fc7d77a1e438dd87172758779414784275dd05b88eafca27ed4424f1249c14d0894202377b2a394e3cc063badd9bd57f66122bb391fff3718520fc9dffa0b225b1eba27be4f0a386aeda91f85b126771f356afa9d1c9aeed249ac100ece76fc8b32903eaa9caeb2c0e3e6d05f69ad025d7ba625d536e6ad06d8b77aceb6d7a01dbb17adee7e17eca234b251e353e26de28849876b8cf88bd57f4a04745d6a88d32f00b35afdf416efd8f84826840e9e974cbd8aa4a102507f0c7e441ad9e1c1275ccc89266bde55f4a3582b78fa8aafa1994ef36b35d3943d3cd326bc70a3ddd377e895b498d79cbc857b5bb2e61f30ac8ca48fe186e40326fd9fef0215e2ef31844bcaec6334490a89dcd42219d64bfeacec7978294ac964b66b49958130a77f7569b083b1c280d4e451e872403454a3947d2cb10ad2f96f92b070f886a5ed771332f51889d18cc2ad941c6638eebefe5fe6cc4b79468b1e469adb08c34982783d49be95f32a774f51db6788d34f26a8af1b31fc709d78338c6c4d9834fa25a910b4b63e05aa6d383d282bba31c9378e3f27f985088e682e11a46a9a1c8e18be9b7c7ef02d9faa8bb08a53d5ffe059d48d440c6364292fb42df68a04aacdfaaba029f1460912b11a6f8eb57578832fb3c10675c7989114177bdb3b587a80856f351177d01eefbb1de210839c9b8b9ba621942f250dc6b4bbf014899b77f52e8e40cb06f7063371251c419bb8aa9d3c0bd83ce651520b6e2911dd38db6c0fff7cc64550320c5cac621765175460cc8f54ee4a0026ec8e57fd789e0fef66c4940cabc3944ff4e3fe10229294e6fcb3059adb4f9868e53b52a7da60f5ae75d4e42e6a94c44e375960f9ed44a56c0b6c49e7c5775dcc1a819547bf3f146e8bdb13f305ea0d15b5616623db4c0657059d07df7acf1062069705ae5699ce86d9d0de16f3afae97f5327fd340693f756aba0bf338af880927cd7f976705c2b511077bbd19b9a842cb7a9b2f713f183c1e4eac23bee3ac081be66de275451773444e81536e183c8e2f033766b854cc92773205abb0f39d90e762c1f0673af8e4051205f752757d2deb7dce6adf8a55297a3a56fe1265368970e8d480b8ee61fa38313e5a7069579f10ac2db37c05c58b38544a8750e186c43a153c7780cb8ddfa309fac85b487b43b1452624afbc4005bbc0777eda63c96edc94016d1fe880dcb2faab472802921adc29dd3410628b8431186a7ef3a6839eb16123ffccf40c4c0907764e40d1dabbaa12a3a9ed3f341c070fb622600b6f4595f4601f67f2d03e7221d57a20bad2e0fcde738107a57fea1e1257a89970bc94517fecfe4990c67f7bfde2ad523f0f4dc760e0e8958eb32c6a7b0e7d952e75fdf08550ab72b6824122697846e599c8a2cd7f3066c8c444e2bb41aa38cb063493af2106eeae5228247706a53169f306d6b0d74867db69d29e9a6eca2b77aeb3353fa5a593b9f4fa379ffd01464a6929841f0fe934d68c8ae9e523a87d02ce5df5636cc5dcd72536d044cedd2a1d1e9047712a399627a44c8776cc5d0b2fa7e3dbef52102602f97090afab7a6070af4a1e50d19fb646f25212a7e750d6d0f765c0d5324721afe49d1070a686b05dafd39114bc65cc11cb30cda09b872c79521273ff2f075ebc4a2e5444c6880b3854d623bf1c42be1026d8b0409fe0001a1470107fffd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000010000000000e4e1c0175abe3cc14d94456c949372ed6ace0b5b92e58c595a6297e6c1cdfbe5bd750a00061b1000025b15002f7b27451dba144086e3ccebd9726c2e1af20883b676631e13908f2a8a0dcabd2fe4d39cd5b0c9d007a4dc3f1a5624208693e461d934fcbb48428eb1cfba41682d19ab1221e11648459eee58b855ea196e854047d82869b62b2efc978f520eb13e35e3752e53026a0f7b22ddf65de7a4e3fea56fc86b9c85cb8df4cfd0262cf3bbe1e16eb25574db8fd56cee33cbfdb73073f700b9992c19ac808354db41e488aa31d6b8a3be8d151732aaf6353f30a18cd773285cddbd3b279e6455f9b90aa37d7055ccd9fdafa66ab0ad216fd123e4a4aadb25f9e004c5a8e794abced52cd6e208d8d2657d03f5179b6353133a9eaf151cf5d47298058ad85ada4e42d29b9c61524ac20264d3b6551efef60789c6a91cf1ce4e069e3ad123fba8d515d705c0e2f3aec108ab317116d593100a4db4affd32b4091a1368cfe3c91b422ff53c75ade0ed61dd562e55cd674e5a0c5ca5952c2204c401d9c61c25b565c83c38cc09eaa6e25c42d234875f2a353813b8db46f031f0392f73a9cc8a88925347e04cfb477f3a7c5578edaa447927c048aa631b4f3dafcb608fafef2e125a549e029f2598cae566f93febee55ab1175359913732673a44fd2a8e28f71e281917c7d0fc04816a5774a3411d1db8f290fe442e3d3141ab366633f07e46528d44528a422c9225129b0ae4e7f3b76c9c8fb25b9c7722be9b5862ce866980003752c178e7a94224a2b0b84fa1e2635b5e2207ade589baa96372d4453868b3e01e9ac2d1d80bc0c7e06a22c205f85e578d55e3283e9b2defb70f46a6084ea40e92b25f35550e7f3fc3c9e764dd4eea7b85df10175a0799e6a46cae937ddb1fbc43795a88850870cdd0f5bfb4506f227b1420f6e8d39b7ccbdc888a7df7066cfcd8da6ba6bceb2aa80bf14fd5d096f604af41ae92b9f4e3c6a7f29208e8b3fb70cf81fd56ff17ee56adf35cb18b39a067f67f9fc3f94e8c30d44760da2322a77679bd6ca0da0e47f206ef7b644448e7330bf19455ca777418896f0e07e551ae4995c7ac5026a7b84c0cd07baf9569135ae4cc3893339ac886ab704b37d46aa3edcfeaa6c4c8aec3ec8bf8ffb51a591ff02f04367314c267a3152070069950d384b99235e5398547d5084c4afbe86ce6a6a60964b8cd89b20c690e8de12606f64ea559a36431dc98d6189a3f15d439dcafd831a9dab64bf8dd5722c91d59fab00fad5b27a89d5f4237cb20ca55648a8256e8a0ba18394608840336d15a84451821619a2363eb51c5ee83e1722476106248962ccb5f7bd843e7dd14620a2b40107d972fa6b1e4ed7bd6b13b659b012870b64906229c93c2625b02da587d2239e09d64bec81f40c50398eb666ff8912711c2377165cbb0ad16999c0b09bc36f354cd495d63fc6cd7b89e4063ff1b63b319afa4a7a64da69aa41ba9af3131df5cb384ce68ab74898d699ea09730b53d3d862521927b0cde151844a9eb11aef8e0e25a3fb607d57c2a9f83eaf01f1c387e09be5fc5ac0eb091359ca26c8fb36cea3ccae8a63e57dcbc60ee66975bfee8964cfdfc840b114dc6ed5eac0babd26f99ec251c45ec602a44fcc6965c0d7c065c186569be6426658b8a5b32fc2dee315a0457e963635795a0d699db9587eeacf8f893699410a87b6390d8c8736552e325f5ccec1b6d4b45002b678649b234301610ecd605f9e56c5dc45d81cccf73e442b872369cbbd3a03eaba79c0af36e23ed97cf73808dc5ed0f5fcd751772b57e2666c5a70b1a5af61a0dd461e8a383e8426369ee6a4e03cf256a6bc3570997c3c06879946ffdd2924aa329e006759c0d6b564c89198c0c09f88c2c9d0c2cd4e0163e8f98145456cc859f55aa8d11a9969ae253d93d56d9fc6a386ae7c6ee884f483913bd739124f69b4bd5238fdc33d860a9cd4670de5dafe4a24fe0001a147010700fd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000000000000001312d00af8cea7a0a1be6687852d7f2249be3b445ae0cddc097a05ea468ef797567856d00061b100003e5507553c137054ae33b5d7cdaf142fbdbfae51246ea3db4441251aa49dac2e8cac063650741353682da85f0df1b1db72997177b74940bcabcb961f364c1861a493066a9cf2533aca5f866b280419fe04f9e9807d3049b759f78d7ce322554ed7891184e73d934c62f88ca33f7cd75d56617664cc674d74f553b8674093e60f014a89e1ab4ea7adde5d54f28a9976d2a3ddb20854b3a1f3ecac8490a9243234c7ce9c5df1b0e45d862c847fd93b99ed1f4d5fd5b06ca416ed048433782d4bd867d36196a7ebe1106f687d0868cd2f2cc1fd94f999de8e59e9a9b333aac6304ac7c01cb91d20c09d4e4f38397ccc1a0cbb46865b7292b84d4f11a783abb0d5da2e31fdc7c2885c6c90053f716264d0b8ba1ce53dc72e8f04e702e319029ca234b8bf72f5321382723010153eaf113e0c823914bf6ea319a1083efe2f0736e1f9b36125c4b77b9e4a581e6fecec959be70f2e21b8c9bd568641d45feb3ffdd39491abc973f310cb1f9c46a874471e5edc3ea7d4063274a9e10e2d21a9e836e99d386d062ac31bf19a14ad6d39c19752eb749c4dacc64aa4591228c039a1f3c0dfd919a2576edf70cbe018075d90c94bebbb91290487cb9cc613eef312a87025891ea6d8b02435b26abf72fea504aad333100e4f14ee916c678c6c9a42b3421bb8f7389b642f89b5abaf99f0df25e5859bee216c3845bbc9fdb032acb88cc144306cc17fe754280bccfecd86670d6994833c4847c71913caed6d904525d22d28aadf3d4e867a617f20a9cf61e95152eb1cab90bae7885da01effee11b7780bd2398d37f04096e7d14252e2026f329422c077762996f45a7c4d546d7ad0422996e659c98f1feebc5c73eeecab837c025a44d94cb5ee39d5a1bbd95973e8e5cdd97cabb7b8f4f86a5bb228c0d12be6b9b2032c54795f4f7180828e94377bf6e0f56545dfcd5252da0936d79aef70caa8715e8416579ca1a928c2aecfa1e03b6cd795528a967fb3dbe524996be92b6168222112063077fb39811ebf9dbb97abafe1b315488c8812224faef56c1eb86837ffcf61bc22a60de7e4f6e0df67c332389556df7da838200dcceeafcb6e8a94aaad013c30934ec0ada06fa17afb5414de4812deb6fa96517df57d503e1e521562ed62a65073252dd08aafba343c029e04faf58b4c451061f156669be5ef68a2adff506cba6351e407d3c6077b71807f21e93d0ca69843f2d5fd6b7c7f415d740fac1f1f8c8bd8f95297ee1ffafbe1b41c548b69dd6d211352c556f14bb4e5af76244e7656f882452201e2bfd90a87413904081a78c2d6849a6a07ad77920465141da19034b70c3edac53a560e7b5c07923c9bc6e17b8091257fe7431e7cb18d770ba1457e62714a47beb8e503874f24405c49121d2cb024edee3458b31ebe1bf40a13f564b8ff95dc537ec926311f59150544fcc57efcef53d9f43c41b0dc32f93b91d2663e9f6b09f9483b4a788b0642d667c722d10ddf09a70886153fc8c943a9d6f3eecec3ab7144669c10b3c5864a174c7343a74e4b58da29f0ae480d5be8adb5d5b55e9cc3c11da1a935c2b1cb966d85612b6c4bbd42aaaeeebada9f062175f502c2cde7daa345239f99201be98ae989cd8a3cd0610779fd413ef1c6c48cd6e8bcb454b847815016e214f1e6162df1b388503b06bad1e5e35ca424fa1ac4407757504e311f4ee0f4d8e5dce45759a96031460842697d577205255b9ba2bbbe4eb08528a9224087a40129906655170405ad4491052ba71d6dde4000b7d7154634db60db540a16cc430b5998dc1465bc57b5a4125c967e4fc451d66026b650b9a70c26998f559793809df549ca1d5e730db0759ed40d3a030c8a1c6bccb28b5fd9230c55ef83d23b369938200099274978bcc017a96bd1b506c6d28b46788f9a483f3e0b234fe0001a147010700fd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000010000000000e4e1c0415149b9751963f87c1583f34e4fb1bc5599cb48dc36e0ab744c37fb0630f93e00061b1000033fabe908e0bb47028814d9361e2518822ec95a72c8e611b1562fb595ad452db3272b17a6f9280115fa6e17434cfc1639b3b1795ff6cfe4aaa80b3393100ba4a2308b9e42dbbb3cba55bec53bbf574b9307e2023f3e13e6d0424ce7b4376280ea0d3a187c8585212fe9effee9aa102873a48f1b71a2ae81cc97fdf6075b4ee17716bbcbcd930a23d483f04907dcf89d9d55d62ac6935ce45afe8549de48a363c595876e32e8ba33806671cb54a92a81409f9bc8964983552f709335efb962d17c2bcc348fcf9af666ae7cff45833b4b0449481a6cc869a6cc2d928ccba9499e6415a52c455ebce14cd94a36fa0bf127682e890be81a1dcc77dd2b94f886103d793ffce97430edf04f4b8a0aaf9622b34bfceaef96bd6848717430d2212f2df30105354306fe8d5d452c32e1b0f1872cb7c953295d2794805fcad0c50cebd26b14cc903fd995d4eb397d85d3bbcc92a4b640ec4448a16eaae0941f2f074ea2f292b55c8cd8051f5b43c4f0d14ae04038d06216cf01b344fb3b8b42ae2680d7998c5f24c16813dcfc521525b5bda78bf604edab7edcac3934cbb9e5e1baae044ea136613ac003e86e3caa9f0e93c38c5997a17ab472004726730f79248be824fbe894422e2b07f951d31ccc32c7f513a61c7e0a4f3790faf2dca5fc4afca1a0f14b0a7a07178402f1875430c905250a5d4d8d70521418cca1c82a2b3721831087e342d74bab5395c5099fe8710c667fb463e50f59da0b67d7f8b2a4b47896c84954a82d604a570f41fb1a5a9906592ee050b0cad8fdc652a411f8dd0d4aa5c46540479b202f7752985311671be2fca007d87b2f27f16be6a590357aeb87f9f75d9f2c798599f3e8bc2fe5b1e7b5d4e3a779761d983f52e5d4844c7eabbd4e543436954a9e361f2f099bcb603a94f2cfc1f1409aa50aee8644c5a1222e2df52fd8a1f884c0a1a800c92be47b7bed41ceff4ee44700c88e527b55c0abdd04f86ce1dae36f0c2802bc4cc74e0698092fed0e8ab7a95e1bfdf0b6fd160a7c80b0f1bba8dde89740e4caa6681f1d4c926a49ee2088282d18e3646e4ade293e1b2cb5a0155d3ee662d5c468cb5d2ac7557e7666396336e3608d84c97fb1f48dd8ca7f8528b218eeac9a825b161147849d1c2a1ee461150cf8984f492d02221ea43d994821790f5e7357e615ce721817e89495bc3422b98a7e6d6288d75c3b727d1669742b682560660079d8860ed65022d279ca414cd56e169e1147d27ad808290f7daeb665a949b793ae503c1dc9d3f1ae0e57b02d373357408d881e2354337143f7d0b99dd2adf48e6c4f4983bc1de85345892e246c6edbd9aee3747fcc2efa956e3e89e9f5a9be0401933b51d625e25c8296be8ef13720d15411d029a471387fa2c28a168f72e0802d84b3085937f9a83353e002b39596e1567d712e729aa55e648f87043be90b359d3e1fd439091d3ded3b72c0c0bdac8e3033a2b2d6e4e074094b7ff6eb631e2b62ec36e35ea434154e0189b1b1e8b9b60da771cf0077ff267e7e861045d24d0c3fa57b9408d3e2116bf75ba02b4d8cd78701ca9836074440c6c0c0ccdb2cd490ee616bc6d3f8d10ddaf60dfe77003ea326673193d21032997f8fb77c2393ce7af5f1d3d3abef5550e80c5a4e3b7ae75ae3f79950a2f8e5065e8d8fecd2f195aac557d7a4b4a5d1e0c5f1e60c0005d73df261401196c93384fcc271b0ebf0a9da2adee213319ed1fb5bec02202b4a0778bdd48420e701105a135b8bf2d4f50b6939ca0890174d7ce9852473456059b9b7a85db8e4664fa67dbc2d729080df312f3d8970e28e7304510b4828624279cf789d9deebf08cce0c1cf84ffb2abc52784230a5b488fe167632882be37fd1564c22b0dc89cf86ea5ffe6d7880b7199eecb9957b28ba3035316b763a4c0a0691bfe0001a147010700010000000000000000000000000218d33da90930d78dfda97b94815fd8543f4485d0574727143932171cc4d796ab0afd01810200000000010223c6a41f797be480b870e70fbc0b567935abc812d5b0e9bdb57e9992dbb54d770000000000000000005b11e204242c6a81fdfaf3d00b8825ca2b3eb16548c572ff9d1d8eef653d7e240000000000000000000360e316000000000022002049205995639f7357505aed54f7b392d2662276bc5d8968da78e6d623d9e6c7fc0a77010000000000160014cf85091a7295547ebd2066aecb1a295440a9f6f0906e010000000000225120a4ac7358d683a4a296a84a196286ea8bf7c80683f3ea8c30be932eeb30b4cfac0140ec5f7239b44a84754b02b947a6210e6d76ab2e57ad71a68b4080b3e68e3d3355f426455a86dcd678677d62262352258d5a170621aeca6643b8f94677667fa44102483045022100ff4ba957db4a6f149ed4513bc8cd64c9cc430f4c5810c1d01d73df72926e492e022013495ad376bd22c6683cc334542192411369201e4eaef605b59d34403c1f5de0012103fcfbfa87a789958625832770826a2e391b87880a5698e601033942c75173460a801a0600061a8000002a0000ffb0d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2344fde6f10018b4cec394cce110bac5d84cce63839bed6752d3c1498c5662b9620001006c02483045022100ff4ba957db4a6f149ed4513bc8cd64c9cc430f4c5810c1d01d73df72926e492e022013495ad376bd22c6683cc334542192411369201e4eaef605b59d34403c1f5de0012103fcfbfa87a789958625832770826a2e391b87880a5698e601033942c75173460a000200000000000000020004ff0000000000000000ff0000000000000001000000000000000000000000000000000001000027100000000027a31840000000002de544802444fde6f10018b4cec394cce110bac5d84cce63839bed6752d3c1498c5662b962000000002b60e316000000000022002049205995639f7357505aed54f7b392d2662276bc5d8968da78e6d623d9e6c7fc00fd0129020000000144fde6f10018b4cec394cce110bac5d84cce63839bed6752d3c1498c5662b9620000000000521a7a8006983a000000000000220020016c1483322b7ef8721387e87a51948fa2e8ecc28dfe33a48327a9eaafd70376983a000000000000220020092dc847c0de4b3408a7b0dced30f091ef9d73e5f8e719794e4267399ed5286d983a0000000000002200209c01da2ecb83e740748c0120909a95cd06387b45902c49810e55f86d34189a85204e000000000000220020c5a1bd48f7ffbc9cd20dadd15f11380a4e878dac76e56080c555134940f2e66ca8250a00000000002200202b0c7734d2e70db4213bca81a2e46458f6e105ff6e2e66aaa3b76133134c3dfea8880b0000000000160014cf85091a7295547ebd2066aecb1a295440a9f6f080f0fc2021396d48be2b7fbc88fd5fe4ab91a19d7af24be98161251a487a8616cf6b425a7980d99b2a2105ccd02fcc73f3449fb169573f0d411ff78aa338eaa344cf549300041124d570c959ea1c621f803b28f343b929732718012795559fe1532e03c42b29e59b000000002b983a000000000000220020016c1483322b7ef8721387e87a51948fa2e8ecc28dfe33a48327a9eaafd70376005e0200000001d570c959ea1c621f803b28f343b929732718012795559fe1532e03c42b29e59b00000000000000000001221f0000000000002200202b0c7734d2e70db4213bca81a2e46458f6e105ff6e2e66aaa3b76133134c3dfe00000000175abe3cc14d94456c949372ed6ace0b5b92e58c595a6297e6c1cdfbe5bd750a000000000000000100061b100a7a13c66544686dfc48ad7fc7d5abfb15a1404f9cb3df4abbb78e810023923100d382bd7186e9d7b47a3be1d8bf7b54eca322931d7514a3260bc6dd4cbb56131124d570c959ea1c621f803b28f343b929732718012795559fe1532e03c42b29e59b010000002b983a000000000000220020092dc847c0de4b3408a7b0dced30f091ef9d73e5f8e719794e4267399ed5286d005e0200000001d570c959ea1c621f803b28f343b929732718012795559fe1532e03c42b29e59b01000000000000000001221f0000000000002200202b0c7734d2e70db4213bca81a2e46458f6e105ff6e2e66aaa3b76133134c3dfe00000000342bb8ca8412a52cbaa04072e0e285ff414a4bf36be1828482705a6bd13501a2000000000000000000061b10c5e470c9b1c309e76daf590ed9f1a7a1111ddef24029bef966622220d059acaf72d241dcade8b164a4309422d2d02130b50f81c0652cef0944d410b3a38a04531324d570c959ea1c621f803b28f343b929732718012795559fe1532e03c42b29e59b020000002b983a0000000000002200209c01da2ecb83e740748c0120909a95cd06387b45902c49810e55f86d34189a85005e0200000001d570c959ea1c621f803b28f343b929732718012795559fe1532e03c42b29e59b02000000000000000001b2200000000000002200202b0c7734d2e70db4213bca81a2e46458f6e105ff6e2e66aaa3b76133134c3dfe101b0600415149b9751963f87c1583f34e4fb1bc5599cb48dc36e0ab744c37fb0630f93e000000000000000100061b10cd3756e951fcd4e7638026ce54e7962dfa1d6cc829a0ee998281b23118f5c83e2d7ff2aef3c1842b2fb552f2d2783f3d8187db9021cf41450d4f1ca15c1582f71324d570c959ea1c621f803b28f343b929732718012795559fe1532e03c42b29e59b030000002b204e000000000000220020c5a1bd48f7ffbc9cd20dadd15f11380a4e878dac76e56080c555134940f2e66c005e0200000001d570c959ea1c621f803b28f343b929732718012795559fe1532e03c42b29e59b030000000000000000013a340000000000002200202b0c7734d2e70db4213bca81a2e46458f6e105ff6e2e66aaa3b76133134c3dfe101b0600af8cea7a0a1be6687852d7f2249be3b445ae0cddc097a05ea468ef797567856d000000000000000000061b10c2ed771e0c811887d21c9d5ad6b72039505e19d879892aa99b83b943374b380e188a459147347953f4a3c34a29d471309e672b9d72c97bd6e64a6e9ff26e31f300000000000000010004000000000000000000000000000000000001ff0000000000000000ff000000000000000100002710000000002de544800000000027a31840b315b404d6bae38833774438441fb36bd2925416d27eeec63326e772032c950f029ced6a1eb5263d34d0d6877178f5b9027cbd9014780197c22fb6fe6849d8c966000000ff0357ca5aa7f38f8ae405263cb1adf2e1469c7f39ee4f9c9b1e70ba2bd02b625478000100400000ffffffffffff0020a409b8983df586e5b71d21ad4824f82a2719d3cad0fdb071c20d0f265b9ba9d080007fffffffffff800002000000000000000000034273b111141c4f04ba55c7b2db01fb50000000000000000100039eb27ed9be0947c7be5941507f4f316200000101eecdd250c851e3ff0344421a4e791eaa008829834f77192587723797bbc77e9f05a886000220d741fabc6a2c7be66dde92517ef89f6ce9ee5288e02f46727ecc12038e9491ad408776ba708b6dafa314db2906226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f01eecdd250c851e368414f7c030100900000000000000000000858b800000014000000001dcd650000000003d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d230000000000000000000000000000061a80ff00012444fde6f10018b4cec394cce110bac5d84cce63839bed6752d3c1498c5662b962000000002b60e316000000000022002049205995639f7357505aed54f7b392d2662276bc5d8968da78e6d623d9e6c7fc00000000000218d33da90930d78dfda97b94815fd8543f4485d0574727143932171cc4d796ab02790a3f53b2f208b1ff65cbd654baf8c0f105b405e8e587438f88eb38bd081479000000061a80000000000000044c00002710000000000001ff0300000000000000022444fde6f10018b4cec394cce110bac5d84cce63839bed6752d3c1498c5662b9620000000022002049205995639f7357505aed54f7b392d2662276bc5d8968da78e6d623d9e6c7fcfffffffd0000000027a31840000000002de544800000000003dfd24002000000000000000622002029fedca3b62676f613aaaea53ff97b497f8799f40a5dc586e67f023c5b1a58750000000027a318400000000045bcc8800000000003dfd2400000000101000000000000000024f48b66833f2ecbd6f125eb8b4c38dd3836ea3e122b64fbe4e293fff54f32ffd0000000002b200b200000000000225120a4ac7358d683a4a296a84a196286ea8bf7c80683f3ea8c30be932eeb30b4cfac00000000000000020100000000000000040000000000183c10225120a4ac7358d683a4a296a84a196286ea8bf7c80683f3ea8c30be932eeb30b4cfac01000000000000000800000000000186a0220020aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa00061a8086d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2341f6c672e644f3aefdda12824a642ca2cce409ef69c5530dcbb2438d64a56ce70000fd025940e6616fcd9dce55776bd871840e88a65415dae895324ce3d454a59626ddc029474733535c6f675d83cade543d03cf7e422d8bb7f1dea3c9821d40cd55035691f10000000000000000020004fffd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000000000000000e4e1c0342bb8ca8412a52cbaa04072e0e285ff414a4bf36be1828482705a6bd13501a200061b100003e368b6d07bc35ff7e3475feddeb313912771046d96fa86ec9a5ba2e9024f5df44c2609468b1f86640de3863c1e3ff60042667392d43809462bc3d18d454ad70de3d85e738909aaa1930559a093e363449e7a00c85cb62562298a108131f6f88635d990d4a6de980d076bfee5281d62828c19da4db7843c304c6d2fe742a8421ffb46c26b5e06249fb8fb4ff0b4d1f08d44d8d7d87953db3da6154f8d31d5af9780f6e45f4867fc7d77a1e438dd87172758779414784275dd05b88eafca27ed4424f1249c14d0894202377b2a394e3cc063badd9bd57f66122bb391fff3718520fc9dffa0b225b1eba27be4f0a386aeda91f85b126771f356afa9d1c9aeed249ac100ece76fc8b32903eaa9caeb2c0e3e6d05f69ad025d7ba625d536e6ad06d8b77aceb6d7a01dbb17adee7e17eca234b251e353e26de28849876b8cf88bd57f4a04745d6a88d32f00b35afdf416efd8f84826840e9e974cbd8aa4a102507f0c7e441ad9e1c1275ccc89266bde55f4a3582b78fa8aafa1994ef36b35d3943d3cd326bc70a3ddd377e895b498d79cbc857b5bb2e61f30ac8ca48fe186e40326fd9fef0215e2ef31844bcaec6334490a89dcd42219d64bfeacec7978294ac964b66b49958130a77f7569b083b1c280d4e451e872403454a3947d2cb10ad2f96f92b070f886a5ed771332f51889d18cc2ad941c6638eebefe5fe6cc4b79468b1e469adb08c34982783d49be95f32a774f51db6788d34f26a8af1b31fc709d78338c6c4d9834fa25a910b4b63e05aa6d383d282bba31c9378e3f27f985088e682e11a46a9a1c8e18be9b7c7ef02d9faa8bb08a53d5ffe059d48d440c6364292fb42df68a04aacdfaaba029f1460912b11a6f8eb57578832fb3c10675c7989114177bdb3b587a80856f351177d01eefbb1de210839c9b8b9ba621942f250dc6b4bbf014899b77f52e8e40cb06f7063371251c419bb8aa9d3c0bd83ce651520b6e2911dd38db6c0fff7cc64550320c5cac621765175460cc8f54ee4a0026ec8e57fd789e0fef66c4940cabc3944ff4e3fe10229294e6fcb3059adb4f9868e53b52a7da60f5ae75d4e42e6a94c44e375960f9ed44a56c0b6c49e7c5775dcc1a819547bf3f146e8bdb13f305ea0d15b5616623db4c0657059d07df7acf1062069705ae5699ce86d9d0de16f3afae97f5327fd340693f756aba0bf338af880927cd7f976705c2b511077bbd19b9a842cb7a9b2f713f183c1e4eac23bee3ac081be66de275451773444e81536e183c8e2f033766b854cc92773205abb0f39d90e762c1f0673af8e4051205f752757d2deb7dce6adf8a55297a3a56fe1265368970e8d480b8ee61fa38313e5a7069579f10ac2db37c05c58b38544a8750e186c43a153c7780cb8ddfa309fac85b487b43b1452624afbc4005bbc0777eda63c96edc94016d1fe880dcb2faab472802921adc29dd3410628b8431186a7ef3a6839eb16123ffccf40c4c0907764e40d1dabbaa12a3a9ed3f341c070fb622600b6f4595f4601f67f2d03e7221d57a20bad2e0fcde738107a57fea1e1257a89970bc94517fecfe4990c67f7bfde2ad523f0f4dc760e0e8958eb32c6a7b0e7d952e75fdf08550ab72b6824122697846e599c8a2cd7f3066c8c444e2bb41aa38cb063493af2106eeae5228247706a53169f306d6b0d74867db69d29e9a6eca2b77aeb3353fa5a593b9f4fa379ffd01464a6929841f0fe934d68c8ae9e523a87d02ce5df5636cc5dcd72536d044cedd2a1d1e9047712a399627a44c8776cc5d0b2fa7e3dbef52102602f97090afab7a6070af4a1e50d19fb646f25212a7e750d6d0f765c0d5324721afe49d1070a686b05dafd39114bc65cc11cb30cda09b872c79521273ff2f075ebc4a2e5444c6880b3854d623bf1c42be1026d8b0409fe0001a1470107fffd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000010000000000e4e1c0175abe3cc14d94456c949372ed6ace0b5b92e58c595a6297e6c1cdfbe5bd750a00061b1000025b15002f7b27451dba144086e3ccebd9726c2e1af20883b676631e13908f2a8a0dcabd2fe4d39cd5b0c9d007a4dc3f1a5624208693e461d934fcbb48428eb1cfba41682d19ab1221e11648459eee58b855ea196e854047d82869b62b2efc978f520eb13e35e3752e53026a0f7b22ddf65de7a4e3fea56fc86b9c85cb8df4cfd0262cf3bbe1e16eb25574db8fd56cee33cbfdb73073f700b9992c19ac808354db41e488aa31d6b8a3be8d151732aaf6353f30a18cd773285cddbd3b279e6455f9b90aa37d7055ccd9fdafa66ab0ad216fd123e4a4aadb25f9e004c5a8e794abced52cd6e208d8d2657d03f5179b6353133a9eaf151cf5d47298058ad85ada4e42d29b9c61524ac20264d3b6551efef60789c6a91cf1ce4e069e3ad123fba8d515d705c0e2f3aec108ab317116d593100a4db4affd32b4091a1368cfe3c91b422ff53c75ade0ed61dd562e55cd674e5a0c5ca5952c2204c401d9c61c25b565c83c38cc09eaa6e25c42d234875f2a353813b8db46f031f0392f73a9cc8a88925347e04cfb477f3a7c5578edaa447927c048aa631b4f3dafcb608fafef2e125a549e029f2598cae566f93febee55ab1175359913732673a44fd2a8e28f71e281917c7d0fc04816a5774a3411d1db8f290fe442e3d3141ab366633f07e46528d44528a422c9225129b0ae4e7f3b76c9c8fb25b9c7722be9b5862ce866980003752c178e7a94224a2b0b84fa1e2635b5e2207ade589baa96372d4453868b3e01e9ac2d1d80bc0c7e06a22c205f85e578d55e3283e9b2defb70f46a6084ea40e92b25f35550e7f3fc3c9e764dd4eea7b85df10175a0799e6a46cae937ddb1fbc43795a88850870cdd0f5bfb4506f227b1420f6e8d39b7ccbdc888a7df7066cfcd8da6ba6bceb2aa80bf14fd5d096f604af41ae92b9f4e3c6a7f29208e8b3fb70cf81fd56ff17ee56adf35cb18b39a067f67f9fc3f94e8c30d44760da2322a77679bd6ca0da0e47f206ef7b644448e7330bf19455ca777418896f0e07e551ae4995c7ac5026a7b84c0cd07baf9569135ae4cc3893339ac886ab704b37d46aa3edcfeaa6c4c8aec3ec8bf8ffb51a591ff02f04367314c267a3152070069950d384b99235e5398547d5084c4afbe86ce6a6a60964b8cd89b20c690e8de12606f64ea559a36431dc98d6189a3f15d439dcafd831a9dab64bf8dd5722c91d59fab00fad5b27a89d5f4237cb20ca55648a8256e8a0ba18394608840336d15a84451821619a2363eb51c5ee83e1722476106248962ccb5f7bd843e7dd14620a2b40107d972fa6b1e4ed7bd6b13b659b012870b64906229c93c2625b02da587d2239e09d64bec81f40c50398eb666ff8912711c2377165cbb0ad16999c0b09bc36f354cd495d63fc6cd7b89e4063ff1b63b319afa4a7a64da69aa41ba9af3131df5cb384ce68ab74898d699ea09730b53d3d862521927b0cde151844a9eb11aef8e0e25a3fb607d57c2a9f83eaf01f1c387e09be5fc5ac0eb091359ca26c8fb36cea3ccae8a63e57dcbc60ee66975bfee8964cfdfc840b114dc6ed5eac0babd26f99ec251c45ec602a44fcc6965c0d7c065c186569be6426658b8a5b32fc2dee315a0457e963635795a0d699db9587eeacf8f893699410a87b6390d8c8736552e325f5ccec1b6d4b45002b678649b234301610ecd605f9e56c5dc45d81cccf73e442b872369cbbd3a03eaba79c0af36e23ed97cf73808dc5ed0f5fcd751772b57e2666c5a70b1a5af61a0dd461e8a383e8426369ee6a4e03cf256a6bc3570997c3c06879946ffdd2924aa329e006759c0d6b564c89198c0c09f88c2c9d0c2cd4e0163e8f98145456cc859f55aa8d11a9969ae253d93d56d9fc6a386ae7c6ee884f483913bd739124f69b4bd5238fdc33d860a9cd4670de5dafe4a24fe0001a147010700fd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000000000000001312d00af8cea7a0a1be6687852d7f2249be3b445ae0cddc097a05ea468ef797567856d00061b100003e5507553c137054ae33b5d7cdaf142fbdbfae51246ea3db4441251aa49dac2e8cac063650741353682da85f0df1b1db72997177b74940bcabcb961f364c1861a493066a9cf2533aca5f866b280419fe04f9e9807d3049b759f78d7ce322554ed7891184e73d934c62f88ca33f7cd75d56617664cc674d74f553b8674093e60f014a89e1ab4ea7adde5d54f28a9976d2a3ddb20854b3a1f3ecac8490a9243234c7ce9c5df1b0e45d862c847fd93b99ed1f4d5fd5b06ca416ed048433782d4bd867d36196a7ebe1106f687d0868cd2f2cc1fd94f999de8e59e9a9b333aac6304ac7c01cb91d20c09d4e4f38397ccc1a0cbb46865b7292b84d4f11a783abb0d5da2e31fdc7c2885c6c90053f716264d0b8ba1ce53dc72e8f04e702e319029ca234b8bf72f5321382723010153eaf113e0c823914bf6ea319a1083efe2f0736e1f9b36125c4b77b9e4a581e6fecec959be70f2e21b8c9bd568641d45feb3ffdd39491abc973f310cb1f9c46a874471e5edc3ea7d4063274a9e10e2d21a9e836e99d386d062ac31bf19a14ad6d39c19752eb749c4dacc64aa4591228c039a1f3c0dfd919a2576edf70cbe018075d90c94bebbb91290487cb9cc613eef312a87025891ea6d8b02435b26abf72fea504aad333100e4f14ee916c678c6c9a42b3421bb8f7389b642f89b5abaf99f0df25e5859bee216c3845bbc9fdb032acb88cc144306cc17fe754280bccfecd86670d6994833c4847c71913caed6d904525d22d28aadf3d4e867a617f20a9cf61e95152eb1cab90bae7885da01effee11b7780bd2398d37f04096e7d14252e2026f329422c077762996f45a7c4d546d7ad0422996e659c98f1feebc5c73eeecab837c025a44d94cb5ee39d5a1bbd95973e8e5cdd97cabb7b8f4f86a5bb228c0d12be6b9b2032c54795f4f7180828e94377bf6e0f56545dfcd5252da0936d79aef70caa8715e8416579ca1a928c2aecfa1e03b6cd795528a967fb3dbe524996be92b6168222112063077fb39811ebf9dbb97abafe1b315488c8812224faef56c1eb86837ffcf61bc22a60de7e4f6e0df67c332389556df7da838200dcceeafcb6e8a94aaad013c30934ec0ada06fa17afb5414de4812deb6fa96517df57d503e1e521562ed62a65073252dd08aafba343c029e04faf58b4c451061f156669be5ef68a2adff506cba6351e407d3c6077b71807f21e93d0ca69843f2d5fd6b7c7f415d740fac1f1f8c8bd8f95297ee1ffafbe1b41c548b69dd6d211352c556f14bb4e5af76244e7656f882452201e2bfd90a87413904081a78c2d6849a6a07ad77920465141da19034b70c3edac53a560e7b5c07923c9bc6e17b8091257fe7431e7cb18d770ba1457e62714a47beb8e503874f24405c49121d2cb024edee3458b31ebe1bf40a13f564b8ff95dc537ec926311f59150544fcc57efcef53d9f43c41b0dc32f93b91d2663e9f6b09f9483b4a788b0642d667c722d10ddf09a70886153fc8c943a9d6f3eecec3ab7144669c10b3c5864a174c7343a74e4b58da29f0ae480d5be8adb5d5b55e9cc3c11da1a935c2b1cb966d85612b6c4bbd42aaaeeebada9f062175f502c2cde7daa345239f99201be98ae989cd8a3cd0610779fd413ef1c6c48cd6e8bcb454b847815016e214f1e6162df1b388503b06bad1e5e35ca424fa1ac4407757504e311f4ee0f4d8e5dce45759a96031460842697d577205255b9ba2bbbe4eb08528a9224087a40129906655170405ad4491052ba71d6dde4000b7d7154634db60db540a16cc430b5998dc1465bc57b5a4125c967e4fc451d66026b650b9a70c26998f559793809df549ca1d5e730db0759ed40d3a030c8a1c6bccb28b5fd9230c55ef83d23b369938200099274978bcc017a96bd1b506c6d28b46788f9a483f3e0b234fe0001a147010700fd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000010000000000e4e1c0415149b9751963f87c1583f34e4fb1bc5599cb48dc36e0ab744c37fb0630f93e00061b1000033fabe908e0bb47028814d9361e2518822ec95a72c8e611b1562fb595ad452db3272b17a6f9280115fa6e17434cfc1639b3b1795ff6cfe4aaa80b3393100ba4a2308b9e42dbbb3cba55bec53bbf574b9307e2023f3e13e6d0424ce7b4376280ea0d3a187c8585212fe9effee9aa102873a48f1b71a2ae81cc97fdf6075b4ee17716bbcbcd930a23d483f04907dcf89d9d55d62ac6935ce45afe8549de48a363c595876e32e8ba33806671cb54a92a81409f9bc8964983552f709335efb962d17c2bcc348fcf9af666ae7cff45833b4b0449481a6cc869a6cc2d928ccba9499e6415a52c455ebce14cd94a36fa0bf127682e890be81a1dcc77dd2b94f886103d793ffce97430edf04f4b8a0aaf9622b34bfceaef96bd6848717430d2212f2df30105354306fe8d5d452c32e1b0f1872cb7c953295d2794805fcad0c50cebd26b14cc903fd995d4eb397d85d3bbcc92a4b640ec4448a16eaae0941f2f074ea2f292b55c8cd8051f5b43c4f0d14ae04038d06216cf01b344fb3b8b42ae2680d7998c5f24c16813dcfc521525b5bda78bf604edab7edcac3934cbb9e5e1baae044ea136613ac003e86e3caa9f0e93c38c5997a17ab472004726730f79248be824fbe894422e2b07f951d31ccc32c7f513a61c7e0a4f3790faf2dca5fc4afca1a0f14b0a7a07178402f1875430c905250a5d4d8d70521418cca1c82a2b3721831087e342d74bab5395c5099fe8710c667fb463e50f59da0b67d7f8b2a4b47896c84954a82d604a570f41fb1a5a9906592ee050b0cad8fdc652a411f8dd0d4aa5c46540479b202f7752985311671be2fca007d87b2f27f16be6a590357aeb87f9f75d9f2c798599f3e8bc2fe5b1e7b5d4e3a779761d983f52e5d4844c7eabbd4e543436954a9e361f2f099bcb603a94f2cfc1f1409aa50aee8644c5a1222e2df52fd8a1f884c0a1a800c92be47b7bed41ceff4ee44700c88e527b55c0abdd04f86ce1dae36f0c2802bc4cc74e0698092fed0e8ab7a95e1bfdf0b6fd160a7c80b0f1bba8dde89740e4caa6681f1d4c926a49ee2088282d18e3646e4ade293e1b2cb5a0155d3ee662d5c468cb5d2ac7557e7666396336e3608d84c97fb1f48dd8ca7f8528b218eeac9a825b161147849d1c2a1ee461150cf8984f492d02221ea43d994821790f5e7357e615ce721817e89495bc3422b98a7e6d6288d75c3b727d1669742b682560660079d8860ed65022d279ca414cd56e169e1147d27ad808290f7daeb665a949b793ae503c1dc9d3f1ae0e57b02d373357408d881e2354337143f7d0b99dd2adf48e6c4f4983bc1de85345892e246c6edbd9aee3747fcc2efa956e3e89e9f5a9be0401933b51d625e25c8296be8ef13720d15411d029a471387fa2c28a168f72e0802d84b3085937f9a83353e002b39596e1567d712e729aa55e648f87043be90b359d3e1fd439091d3ded3b72c0c0bdac8e3033a2b2d6e4e074094b7ff6eb631e2b62ec36e35ea434154e0189b1b1e8b9b60da771cf0077ff267e7e861045d24d0c3fa57b9408d3e2116bf75ba02b4d8cd78701ca9836074440c6c0c0ccdb2cd490ee616bc6d3f8d10ddaf60dfe77003ea326673193d21032997f8fb77c2393ce7af5f1d3d3abef5550e80c5a4e3b7ae75ae3f79950a2f8e5065e8d8fecd2f195aac557d7a4b4a5d1e0c5f1e60c0005d73df261401196c93384fcc271b0ebf0a9da2adee213319ed1fb5bec02202b4a0778bdd48420e701105a135b8bf2d4f50b6939ca0890174d7ce9852473456059b9b7a85db8e4664fa67dbc2d729080df312f3d8970e28e7304510b4828624279cf789d9deebf08cce0c1cf84ffb2abc52784230a5b488fe167632882be37fd1564c22b0dc89cf86ea5ffe6d7880b7199eecb9957b28ba3035316b763a4c0a0691bfe0001a1470107000027100000000027a318400000000045bcc8802441f6c672e644f3aefdda12824a642ca2cce409ef69c5530dcbb2438d64a56ce7010000002be0fd1c000000000022002029fedca3b62676f613aaaea53ff97b497f8799f40a5dc586e67f023c5b1a587500fd0129020000000141f6c672e644f3aefdda12824a642ca2cce409ef69c5530dcbb2438d64a56ce70100000000521a7a8006983a000000000000220020016c1483322b7ef8721387e87a51948fa2e8ecc28dfe33a48327a9eaafd70376983a000000000000220020092dc847c0de4b3408a7b0dced30f091ef9d73e5f8e719794e4267399ed5286d983a0000000000002200209c01da2ecb83e740748c0120909a95cd06387b45902c49810e55f86d34189a85204e000000000000220020c5a1bd48f7ffbc9cd20dadd15f11380a4e878dac76e56080c555134940f2e66ca8250a00000000002200202b0c7734d2e70db4213bca81a2e46458f6e105ff6e2e66aaa3b76133134c3dfe28a3110000000000160014cf85091a7295547ebd2066aecb1a295440a9f6f080f0fc2000000000000000000001000400fd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000000000000000e4e1c0342bb8ca8412a52cbaa04072e0e285ff414a4bf36be1828482705a6bd13501a200061b100003e368b6d07bc35ff7e3475feddeb313912771046d96fa86ec9a5ba2e9024f5df44c2609468b1f86640de3863c1e3ff60042667392d43809462bc3d18d454ad70de3d85e738909aaa1930559a093e363449e7a00c85cb62562298a108131f6f88635d990d4a6de980d076bfee5281d62828c19da4db7843c304c6d2fe742a8421ffb46c26b5e06249fb8fb4ff0b4d1f08d44d8d7d87953db3da6154f8d31d5af9780f6e45f4867fc7d77a1e438dd87172758779414784275dd05b88eafca27ed4424f1249c14d0894202377b2a394e3cc063badd9bd57f66122bb391fff3718520fc9dffa0b225b1eba27be4f0a386aeda91f85b126771f356afa9d1c9aeed249ac100ece76fc8b32903eaa9caeb2c0e3e6d05f69ad025d7ba625d536e6ad06d8b77aceb6d7a01dbb17adee7e17eca234b251e353e26de28849876b8cf88bd57f4a04745d6a88d32f00b35afdf416efd8f84826840e9e974cbd8aa4a102507f0c7e441ad9e1c1275ccc89266bde55f4a3582b78fa8aafa1994ef36b35d3943d3cd326bc70a3ddd377e895b498d79cbc857b5bb2e61f30ac8ca48fe186e40326fd9fef0215e2ef31844bcaec6334490a89dcd42219d64bfeacec7978294ac964b66b49958130a77f7569b083b1c280d4e451e872403454a3947d2cb10ad2f96f92b070f886a5ed771332f51889d18cc2ad941c6638eebefe5fe6cc4b79468b1e469adb08c34982783d49be95f32a774f51db6788d34f26a8af1b31fc709d78338c6c4d9834fa25a910b4b63e05aa6d383d282bba31c9378e3f27f985088e682e11a46a9a1c8e18be9b7c7ef02d9faa8bb08a53d5ffe059d48d440c6364292fb42df68a04aacdfaaba029f1460912b11a6f8eb57578832fb3c10675c7989114177bdb3b587a80856f351177d01eefbb1de210839c9b8b9ba621942f250dc6b4bbf014899b77f52e8e40cb06f7063371251c419bb8aa9d3c0bd83ce651520b6e2911dd38db6c0fff7cc64550320c5cac621765175460cc8f54ee4a0026ec8e57fd789e0fef66c4940cabc3944ff4e3fe10229294e6fcb3059adb4f9868e53b52a7da60f5ae75d4e42e6a94c44e375960f9ed44a56c0b6c49e7c5775dcc1a819547bf3f146e8bdb13f305ea0d15b5616623db4c0657059d07df7acf1062069705ae5699ce86d9d0de16f3afae97f5327fd340693f756aba0bf338af880927cd7f976705c2b511077bbd19b9a842cb7a9b2f713f183c1e4eac23bee3ac081be66de275451773444e81536e183c8e2f033766b854cc92773205abb0f39d90e762c1f0673af8e4051205f752757d2deb7dce6adf8a55297a3a56fe1265368970e8d480b8ee61fa38313e5a7069579f10ac2db37c05c58b38544a8750e186c43a153c7780cb8ddfa309fac85b487b43b1452624afbc4005bbc0777eda63c96edc94016d1fe880dcb2faab472802921adc29dd3410628b8431186a7ef3a6839eb16123ffccf40c4c0907764e40d1dabbaa12a3a9ed3f341c070fb622600b6f4595f4601f67f2d03e7221d57a20bad2e0fcde738107a57fea1e1257a89970bc94517fecfe4990c67f7bfde2ad523f0f4dc760e0e8958eb32c6a7b0e7d952e75fdf08550ab72b6824122697846e599c8a2cd7f3066c8c444e2bb41aa38cb063493af2106eeae5228247706a53169f306d6b0d74867db69d29e9a6eca2b77aeb3353fa5a593b9f4fa379ffd01464a6929841f0fe934d68c8ae9e523a87d02ce5df5636cc5dcd72536d044cedd2a1d1e9047712a399627a44c8776cc5d0b2fa7e3dbef52102602f97090afab7a6070af4a1e50d19fb646f25212a7e750d6d0f765c0d5324721afe49d1070a686b05dafd39114bc65cc11cb30cda09b872c79521273ff2f075ebc4a2e5444c6880b3854d623bf1c42be1026d8b0409fe0001a147010700fd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000010000000000e4e1c0175abe3cc14d94456c949372ed6ace0b5b92e58c595a6297e6c1cdfbe5bd750a00061b1000025b15002f7b27451dba144086e3ccebd9726c2e1af20883b676631e13908f2a8a0dcabd2fe4d39cd5b0c9d007a4dc3f1a5624208693e461d934fcbb48428eb1cfba41682d19ab1221e11648459eee58b855ea196e854047d82869b62b2efc978f520eb13e35e3752e53026a0f7b22ddf65de7a4e3fea56fc86b9c85cb8df4cfd0262cf3bbe1e16eb25574db8fd56cee33cbfdb73073f700b9992c19ac808354db41e488aa31d6b8a3be8d151732aaf6353f30a18cd773285cddbd3b279e6455f9b90aa37d7055ccd9fdafa66ab0ad216fd123e4a4aadb25f9e004c5a8e794abced52cd6e208d8d2657d03f5179b6353133a9eaf151cf5d47298058ad85ada4e42d29b9c61524ac20264d3b6551efef60789c6a91cf1ce4e069e3ad123fba8d515d705c0e2f3aec108ab317116d593100a4db4affd32b4091a1368cfe3c91b422ff53c75ade0ed61dd562e55cd674e5a0c5ca5952c2204c401d9c61c25b565c83c38cc09eaa6e25c42d234875f2a353813b8db46f031f0392f73a9cc8a88925347e04cfb477f3a7c5578edaa447927c048aa631b4f3dafcb608fafef2e125a549e029f2598cae566f93febee55ab1175359913732673a44fd2a8e28f71e281917c7d0fc04816a5774a3411d1db8f290fe442e3d3141ab366633f07e46528d44528a422c9225129b0ae4e7f3b76c9c8fb25b9c7722be9b5862ce866980003752c178e7a94224a2b0b84fa1e2635b5e2207ade589baa96372d4453868b3e01e9ac2d1d80bc0c7e06a22c205f85e578d55e3283e9b2defb70f46a6084ea40e92b25f35550e7f3fc3c9e764dd4eea7b85df10175a0799e6a46cae937ddb1fbc43795a88850870cdd0f5bfb4506f227b1420f6e8d39b7ccbdc888a7df7066cfcd8da6ba6bceb2aa80bf14fd5d096f604af41ae92b9f4e3c6a7f29208e8b3fb70cf81fd56ff17ee56adf35cb18b39a067f67f9fc3f94e8c30d44760da2322a77679bd6ca0da0e47f206ef7b644448e7330bf19455ca777418896f0e07e551ae4995c7ac5026a7b84c0cd07baf9569135ae4cc3893339ac886ab704b37d46aa3edcfeaa6c4c8aec3ec8bf8ffb51a591ff02f04367314c267a3152070069950d384b99235e5398547d5084c4afbe86ce6a6a60964b8cd89b20c690e8de12606f64ea559a36431dc98d6189a3f15d439dcafd831a9dab64bf8dd5722c91d59fab00fad5b27a89d5f4237cb20ca55648a8256e8a0ba18394608840336d15a84451821619a2363eb51c5ee83e1722476106248962ccb5f7bd843e7dd14620a2b40107d972fa6b1e4ed7bd6b13b659b012870b64906229c93c2625b02da587d2239e09d64bec81f40c50398eb666ff8912711c2377165cbb0ad16999c0b09bc36f354cd495d63fc6cd7b89e4063ff1b63b319afa4a7a64da69aa41ba9af3131df5cb384ce68ab74898d699ea09730b53d3d862521927b0cde151844a9eb11aef8e0e25a3fb607d57c2a9f83eaf01f1c387e09be5fc5ac0eb091359ca26c8fb36cea3ccae8a63e57dcbc60ee66975bfee8964cfdfc840b114dc6ed5eac0babd26f99ec251c45ec602a44fcc6965c0d7c065c186569be6426658b8a5b32fc2dee315a0457e963635795a0d699db9587eeacf8f893699410a87b6390d8c8736552e325f5ccec1b6d4b45002b678649b234301610ecd605f9e56c5dc45d81cccf73e442b872369cbbd3a03eaba79c0af36e23ed97cf73808dc5ed0f5fcd751772b57e2666c5a70b1a5af61a0dd461e8a383e8426369ee6a4e03cf256a6bc3570997c3c06879946ffdd2924aa329e006759c0d6b564c89198c0c09f88c2c9d0c2cd4e0163e8f98145456cc859f55aa8d11a9969ae253d93d56d9fc6a386ae7c6ee884f483913bd739124f69b4bd5238fdc33d860a9cd4670de5dafe4a24fe0001a1470107fffd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000000000000001312d00af8cea7a0a1be6687852d7f2249be3b445ae0cddc097a05ea468ef797567856d00061b100003e5507553c137054ae33b5d7cdaf142fbdbfae51246ea3db4441251aa49dac2e8cac063650741353682da85f0df1b1db72997177b74940bcabcb961f364c1861a493066a9cf2533aca5f866b280419fe04f9e9807d3049b759f78d7ce322554ed7891184e73d934c62f88ca33f7cd75d56617664cc674d74f553b8674093e60f014a89e1ab4ea7adde5d54f28a9976d2a3ddb20854b3a1f3ecac8490a9243234c7ce9c5df1b0e45d862c847fd93b99ed1f4d5fd5b06ca416ed048433782d4bd867d36196a7ebe1106f687d0868cd2f2cc1fd94f999de8e59e9a9b333aac6304ac7c01cb91d20c09d4e4f38397ccc1a0cbb46865b7292b84d4f11a783abb0d5da2e31fdc7c2885c6c90053f716264d0b8ba1ce53dc72e8f04e702e319029ca234b8bf72f5321382723010153eaf113e0c823914bf6ea319a1083efe2f0736e1f9b36125c4b77b9e4a581e6fecec959be70f2e21b8c9bd568641d45feb3ffdd39491abc973f310cb1f9c46a874471e5edc3ea7d4063274a9e10e2d21a9e836e99d386d062ac31bf19a14ad6d39c19752eb749c4dacc64aa4591228c039a1f3c0dfd919a2576edf70cbe018075d90c94bebbb91290487cb9cc613eef312a87025891ea6d8b02435b26abf72fea504aad333100e4f14ee916c678c6c9a42b3421bb8f7389b642f89b5abaf99f0df25e5859bee216c3845bbc9fdb032acb88cc144306cc17fe754280bccfecd86670d6994833c4847c71913caed6d904525d22d28aadf3d4e867a617f20a9cf61e95152eb1cab90bae7885da01effee11b7780bd2398d37f04096e7d14252e2026f329422c077762996f45a7c4d546d7ad0422996e659c98f1feebc5c73eeecab837c025a44d94cb5ee39d5a1bbd95973e8e5cdd97cabb7b8f4f86a5bb228c0d12be6b9b2032c54795f4f7180828e94377bf6e0f56545dfcd5252da0936d79aef70caa8715e8416579ca1a928c2aecfa1e03b6cd795528a967fb3dbe524996be92b6168222112063077fb39811ebf9dbb97abafe1b315488c8812224faef56c1eb86837ffcf61bc22a60de7e4f6e0df67c332389556df7da838200dcceeafcb6e8a94aaad013c30934ec0ada06fa17afb5414de4812deb6fa96517df57d503e1e521562ed62a65073252dd08aafba343c029e04faf58b4c451061f156669be5ef68a2adff506cba6351e407d3c6077b71807f21e93d0ca69843f2d5fd6b7c7f415d740fac1f1f8c8bd8f95297ee1ffafbe1b41c548b69dd6d211352c556f14bb4e5af76244e7656f882452201e2bfd90a87413904081a78c2d6849a6a07ad77920465141da19034b70c3edac53a560e7b5c07923c9bc6e17b8091257fe7431e7cb18d770ba1457e62714a47beb8e503874f24405c49121d2cb024edee3458b31ebe1bf40a13f564b8ff95dc537ec926311f59150544fcc57efcef53d9f43c41b0dc32f93b91d2663e9f6b09f9483b4a788b0642d667c722d10ddf09a70886153fc8c943a9d6f3eecec3ab7144669c10b3c5864a174c7343a74e4b58da29f0ae480d5be8adb5d5b55e9cc3c11da1a935c2b1cb966d85612b6c4bbd42aaaeeebada9f062175f502c2cde7daa345239f99201be98ae989cd8a3cd0610779fd413ef1c6c48cd6e8bcb454b847815016e214f1e6162df1b388503b06bad1e5e35ca424fa1ac4407757504e311f4ee0f4d8e5dce45759a96031460842697d577205255b9ba2bbbe4eb08528a9224087a40129906655170405ad4491052ba71d6dde4000b7d7154634db60db540a16cc430b5998dc1465bc57b5a4125c967e4fc451d66026b650b9a70c26998f559793809df549ca1d5e730db0759ed40d3a030c8a1c6bccb28b5fd9230c55ef83d23b369938200099274978bcc017a96bd1b506c6d28b46788f9a483f3e0b234fe0001a1470107fffd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000010000000000e4e1c0415149b9751963f87c1583f34e4fb1bc5599cb48dc36e0ab744c37fb0630f93e00061b1000033fabe908e0bb47028814d9361e2518822ec95a72c8e611b1562fb595ad452db3272b17a6f9280115fa6e17434cfc1639b3b1795ff6cfe4aaa80b3393100ba4a2308b9e42dbbb3cba55bec53bbf574b9307e2023f3e13e6d0424ce7b4376280ea0d3a187c8585212fe9effee9aa102873a48f1b71a2ae81cc97fdf6075b4ee17716bbcbcd930a23d483f04907dcf89d9d55d62ac6935ce45afe8549de48a363c595876e32e8ba33806671cb54a92a81409f9bc8964983552f709335efb962d17c2bcc348fcf9af666ae7cff45833b4b0449481a6cc869a6cc2d928ccba9499e6415a52c455ebce14cd94a36fa0bf127682e890be81a1dcc77dd2b94f886103d793ffce97430edf04f4b8a0aaf9622b34bfceaef96bd6848717430d2212f2df30105354306fe8d5d452c32e1b0f1872cb7c953295d2794805fcad0c50cebd26b14cc903fd995d4eb397d85d3bbcc92a4b640ec4448a16eaae0941f2f074ea2f292b55c8cd8051f5b43c4f0d14ae04038d06216cf01b344fb3b8b42ae2680d7998c5f24c16813dcfc521525b5bda78bf604edab7edcac3934cbb9e5e1baae044ea136613ac003e86e3caa9f0e93c38c5997a17ab472004726730f79248be824fbe894422e2b07f951d31ccc32c7f513a61c7e0a4f3790faf2dca5fc4afca1a0f14b0a7a07178402f1875430c905250a5d4d8d70521418cca1c82a2b3721831087e342d74bab5395c5099fe8710c667fb463e50f59da0b67d7f8b2a4b47896c84954a82d604a570f41fb1a5a9906592ee050b0cad8fdc652a411f8dd0d4aa5c46540479b202f7752985311671be2fca007d87b2f27f16be6a590357aeb87f9f75d9f2c798599f3e8bc2fe5b1e7b5d4e3a779761d983f52e5d4844c7eabbd4e543436954a9e361f2f099bcb603a94f2cfc1f1409aa50aee8644c5a1222e2df52fd8a1f884c0a1a800c92be47b7bed41ceff4ee44700c88e527b55c0abdd04f86ce1dae36f0c2802bc4cc74e0698092fed0e8ab7a95e1bfdf0b6fd160a7c80b0f1bba8dde89740e4caa6681f1d4c926a49ee2088282d18e3646e4ade293e1b2cb5a0155d3ee662d5c468cb5d2ac7557e7666396336e3608d84c97fb1f48dd8ca7f8528b218eeac9a825b161147849d1c2a1ee461150cf8984f492d02221ea43d994821790f5e7357e615ce721817e89495bc3422b98a7e6d6288d75c3b727d1669742b682560660079d8860ed65022d279ca414cd56e169e1147d27ad808290f7daeb665a949b793ae503c1dc9d3f1ae0e57b02d373357408d881e2354337143f7d0b99dd2adf48e6c4f4983bc1de85345892e246c6edbd9aee3747fcc2efa956e3e89e9f5a9be0401933b51d625e25c8296be8ef13720d15411d029a471387fa2c28a168f72e0802d84b3085937f9a83353e002b39596e1567d712e729aa55e648f87043be90b359d3e1fd439091d3ded3b72c0c0bdac8e3033a2b2d6e4e074094b7ff6eb631e2b62ec36e35ea434154e0189b1b1e8b9b60da771cf0077ff267e7e861045d24d0c3fa57b9408d3e2116bf75ba02b4d8cd78701ca9836074440c6c0c0ccdb2cd490ee616bc6d3f8d10ddaf60dfe77003ea326673193d21032997f8fb77c2393ce7af5f1d3d3abef5550e80c5a4e3b7ae75ae3f79950a2f8e5065e8d8fecd2f195aac557d7a4b4a5d1e0c5f1e60c0005d73df261401196c93384fcc271b0ebf0a9da2adee213319ed1fb5bec02202b4a0778bdd48420e701105a135b8bf2d4f50b6939ca0890174d7ce9852473456059b9b7a85db8e4664fa67dbc2d729080df312f3d8970e28e7304510b4828624279cf789d9deebf08cce0c1cf84ffb2abc52784230a5b488fe167632882be37fd1564c22b0dc89cf86ea5ffe6d7880b7199eecb9957b28ba3035316b763a4c0a0691bfe0001a1470107000027100000000045bcc8800000000027a318401078a5654313845429e9ff89f5848fb34caef8f89701d89c1f989a2169a7d1ea029ced6a1eb5263d34d0d6877178f5b9027cbd9014780197c22fb6fe6849d8c96600") val decoded = channelDataCodec.decode(bin.bits).require.value.asInstanceOf[DATA_NORMAL] - assert(decoded.channelParams.channelFeatures.commitmentFormat == DefaultCommitmentFormat) - assert(decoded.channelParams.channelFeatures.features == Set(Features.DualFunding, Features.StaticRemoteKey)) + assert(decoded.channelParams.channelFeatures.features == Set(Features.DualFunding)) + assert(decoded.commitments.latest.commitmentFormat == DefaultCommitmentFormat) assert(decoded.spliceStatus.isInstanceOf[SpliceStatus.SpliceWaitingForSigs]) val spliceStatus = decoded.spliceStatus.asInstanceOf[SpliceStatus.SpliceWaitingForSigs] // Local params. assert(!decoded.channelParams.localParams.isChannelOpener) + assert(decoded.channelParams.localParams.walletStaticPaymentBasepoint.nonEmpty) assert(spliceStatus.signingSession.localCommitParams.toSelfDelay == CltvExpiryDelta(144)) assert(spliceStatus.signingSession.localCommitParams.dustLimit == 1000.sat) assert(spliceStatus.signingSession.localCommitParams.htlcMinimum == 1000.msat) @@ -216,10 +219,11 @@ class ChannelCodecs4Spec extends AnyFunSuite { assert(decoded.closeStatus_opt.isEmpty) assert(decoded.commitments.active.size == 3) assert(decoded.commitments.all.size == 3) - assert(decoded.commitments.latest.commitmentFormat == ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) - assert(decoded.commitments.channelParams.channelFeatures.features == Set(Features.DualFunding, Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx)) + decoded.commitments.active.foreach(c => assert(c.commitmentFormat == ZeroFeeHtlcTxAnchorOutputsCommitmentFormat)) + assert(decoded.commitments.channelParams.channelFeatures.features == Set(Features.DualFunding)) // Local params. assert(!decoded.commitments.localChannelParams.isChannelOpener) + assert(decoded.commitments.localChannelParams.walletStaticPaymentBasepoint.isEmpty) assert(decoded.commitments.latest.localCommitParams.toSelfDelay == CltvExpiryDelta(144)) assert(decoded.commitments.latest.localCommitParams.dustLimit == 1000.sat) assert(decoded.commitments.latest.localCommitParams.htlcMinimum == 1000.msat) From 010b09383ae02b55ca20353404449c5c80039e7b Mon Sep 17 00:00:00 2001 From: t-bast Date: Tue, 19 Aug 2025 12:14:11 +0200 Subject: [PATCH 5/6] fixup! Make channel type features non-permanent --- .../scala/fr/acinq/eclair/channel/ChannelFeatures.scala | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala index 9b628d06d2..dc38e86015 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala @@ -44,12 +44,8 @@ object ChannelFeatures { val permanentFeatures = Features.knownFeatures.collect { // If we both support 0-conf or scid_alias, we use it even if it wasn't in the channel-type. // Note that we cannot use scid_alias if the channel is announced. - case Features.ScidAlias => - if (Features.canUseFeature(localFeatures, remoteFeatures, Features.ScidAlias) && !announceChannel) { - Some(Features.ScidAlias) - } else { - None - } + case Features.ScidAlias if Features.canUseFeature(localFeatures, remoteFeatures, Features.ScidAlias) && !announceChannel => Some(Features.ScidAlias) + case Features.ScidAlias => None case Features.ZeroConf if Features.canUseFeature(localFeatures, remoteFeatures, Features.ZeroConf) => Some(Features.ZeroConf) // We add all other permanent channel features that we both support. case f: PermanentChannelFeature if Features.canUseFeature(localFeatures, remoteFeatures, f) => Some(f) From a8bed6f288bc74e22ffce416d237d4d00f28118d Mon Sep 17 00:00:00 2001 From: t-bast Date: Tue, 19 Aug 2025 12:34:03 +0200 Subject: [PATCH 6/6] Temporarily disallow node start-up We add a mechanism to prevent the node from starting-up, unless an override is provided. This provides an opportunity to iterate on codecs while knowing that node operators won't be affected if we make changes that are backwards-incompatible. --- eclair-node/src/main/scala/fr/acinq/eclair/Boot.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/eclair-node/src/main/scala/fr/acinq/eclair/Boot.scala b/eclair-node/src/main/scala/fr/acinq/eclair/Boot.scala index d966500b6b..1d6c4ec134 100644 --- a/eclair-node/src/main/scala/fr/acinq/eclair/Boot.scala +++ b/eclair-node/src/main/scala/fr/acinq/eclair/Boot.scala @@ -33,6 +33,10 @@ import scala.util.{Failure, Success} */ object Boot extends App with Logging { try { + if (!System.getProperty("eclair.allow-unsafe-startup", "false").toBooleanOption.contains(true)) { + throw new RuntimeException("This version of eclair is unsafe to use: please wait for the next official release to update your node.") + } + val datadir = new File(System.getProperty("eclair.datadir", System.getProperty("user.home") + "/.eclair")) val config = NodeParams.loadConfiguration(datadir) @@ -63,7 +67,7 @@ object Boot extends App with Logging { /** * Starts the http APIs service if enabled in the configuration */ - def startApiServiceIfEnabled(kit: Kit, providers: Seq[RouteProvider] = Nil)(implicit system: ActorSystem, ec: ExecutionContext) = { + private def startApiServiceIfEnabled(kit: Kit, providers: Seq[RouteProvider] = Nil)(implicit system: ActorSystem, ec: ExecutionContext) = { val config = system.settings.config.getConfig("eclair") if (config.getBoolean("api.enabled")) { logger.info(s"json API enabled on port=${config.getInt("api.port")}") @@ -84,7 +88,7 @@ object Boot extends App with Logging { } } - def onError(t: Throwable): Unit = { + private def onError(t: Throwable): Unit = { val errorMsg = if (t.getMessage != null) t.getMessage else t.getClass.getSimpleName System.err.println(s"fatal error: $errorMsg") logger.error(s"fatal error: $errorMsg", t)