From e88e6c5c022bad8484a86409ac638d6fbd0020bd Mon Sep 17 00:00:00 2001 From: Bastien Teinturier <31281497+t-bast@users.noreply.github.com> Date: Tue, 6 Jul 2021 16:23:48 +0200 Subject: [PATCH] Separate internal channel config from features (#1852) * Separate internal channel config from features Our current ChannelVersion field mixes two unrelated concepts: channel features (as defined in Bolt 9) and channel internals (such as custom key derivation). It is more future-proof to separate these two unrelated concepts and will make it easier to implement channel types (see https://github.com/lightningnetwork/lightning-rfc/pull/880). * Add shutdown script to channel remote params This will be necessary when we implement `upfront_shutdown_script`. This field can be set to `None` for all current channels as we don't support the feature yet. --- .../eclair/blockchain/fee/FeeEstimator.scala | 15 +- .../fr/acinq/eclair/channel/Channel.scala | 72 +++++---- .../acinq/eclair/channel/ChannelConfig.scala | 58 +++++++ .../eclair/channel/ChannelFeatures.scala | 66 ++++++++ .../acinq/eclair/channel/ChannelTypes.scala | 79 +++------ .../fr/acinq/eclair/channel/Commitments.scala | 57 ++++--- .../fr/acinq/eclair/channel/Helpers.scala | 69 ++++---- .../publish/ReplaceableTxPublisher.scala | 4 +- .../crypto/keymanager/ChannelKeyManager.scala | 18 ++- .../main/scala/fr/acinq/eclair/io/Peer.scala | 29 ++-- .../channel/version0/ChannelCodecs0.scala | 49 +++--- .../channel/version0/ChannelTypes0.scala | 97 ++++++++++- .../channel/version1/ChannelCodecs1.scala | 42 ++--- .../channel/version2/ChannelCodecs2.scala | 42 ++--- .../channel/version3/ChannelCodecs3.scala | 64 +++++--- .../scala/fr/acinq/eclair/FeaturesSpec.scala | 23 ++- .../blockchain/fee/FeeEstimatorSpec.scala | 30 ++-- .../eclair/channel/ChannelConfigSpec.scala | 30 ++++ .../eclair/channel/ChannelTypesSpec.scala | 60 +++---- .../eclair/channel/CommitmentsSpec.scala | 10 +- .../fr/acinq/eclair/channel/FuzzySpec.scala | 4 +- .../states/StateTestsHelperMethods.scala | 21 +-- .../a/WaitForAcceptChannelStateSpec.scala | 9 +- .../a/WaitForOpenChannelStateSpec.scala | 9 +- ...itForFundingCreatedInternalStateSpec.scala | 9 +- .../b/WaitForFundingCreatedStateSpec.scala | 9 +- .../b/WaitForFundingSignedStateSpec.scala | 9 +- .../c/WaitForFundingConfirmedStateSpec.scala | 9 +- .../c/WaitForFundingLockedStateSpec.scala | 9 +- .../channel/states/e/NormalStateSpec.scala | 2 +- .../channel/states/e/OfflineStateSpec.scala | 4 +- .../channel/states/h/ClosingStateSpec.scala | 153 +++++++++--------- .../LocalChannelKeyManagerSpec.scala | 11 +- .../integration/ChannelIntegrationSpec.scala | 2 +- .../interop/rustytests/RustyTestsSpec.scala | 9 +- .../scala/fr/acinq/eclair/io/PeerSpec.scala | 11 +- .../eclair/payment/PaymentPacketSpec.scala | 6 +- .../eclair/transactions/TestVectorsSpec.scala | 16 +- .../internal/channel/ChannelCodecsSpec.scala | 15 +- .../channel/version0/ChannelCodecs0Spec.scala | 2 +- .../channel/version1/ChannelCodecs1Spec.scala | 8 +- .../channel/version2/ChannelCodecs2Spec.scala | 53 +++++- .../channel/version3/ChannelCodecs3Spec.scala | 43 ++++- .../eclair/api/serde/JsonSerializers.scala | 6 +- 44 files changed, 848 insertions(+), 495 deletions(-) create mode 100644 eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelConfig.scala create mode 100644 eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala create mode 100644 eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelConfigSpec.scala diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeEstimator.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeEstimator.scala index 8abaaae4a7..20c00afe88 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeEstimator.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeEstimator.scala @@ -18,8 +18,9 @@ package fr.acinq.eclair.blockchain.fee import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.Satoshi +import fr.acinq.eclair.Features import fr.acinq.eclair.blockchain.CurrentFeerates -import fr.acinq.eclair.channel.ChannelVersion +import fr.acinq.eclair.channel.ChannelFeatures trait FeeEstimator { // @formatter:off @@ -32,13 +33,13 @@ case class FeeTargets(fundingBlockTarget: Int, commitmentBlockTarget: Int, mutua case class FeerateTolerance(ratioLow: Double, ratioHigh: Double, anchorOutputMaxCommitFeerate: FeeratePerKw) { /** - * @param channelVersion channel version + * @param channelFeatures permanent channel features * @param networkFeerate reference fee rate (value we estimate from our view of the network) * @param proposedFeerate fee rate proposed (new proposal through update_fee or previous proposal used in our current commit tx) * @return true if the difference between proposed and reference fee rates is too high. */ - def isFeeDiffTooHigh(channelVersion: ChannelVersion, networkFeerate: FeeratePerKw, proposedFeerate: FeeratePerKw): Boolean = { - if (channelVersion.hasAnchorOutputs) { + def isFeeDiffTooHigh(channelFeatures: ChannelFeatures, networkFeerate: FeeratePerKw, proposedFeerate: FeeratePerKw): Boolean = { + if (channelFeatures.hasFeature(Features.AnchorOutputs)) { proposedFeerate < networkFeerate * ratioLow || anchorOutputMaxCommitFeerate * ratioHigh < proposedFeerate } else { proposedFeerate < networkFeerate * ratioLow || networkFeerate * ratioHigh < proposedFeerate @@ -60,15 +61,15 @@ case class OnChainFeeConf(feeTargets: FeeTargets, feeEstimator: FeeEstimator, cl * - otherwise we use a feerate that should get the commit tx confirmed within the configured block target * * @param remoteNodeId nodeId of our channel peer - * @param channelVersion channel version + * @param channelFeatures permanent channel features * @param currentFeerates_opt if provided, will be used to compute the most up-to-date network fee, otherwise we rely on the fee estimator */ - def getCommitmentFeerate(remoteNodeId: PublicKey, channelVersion: ChannelVersion, channelCapacity: Satoshi, currentFeerates_opt: Option[CurrentFeerates]): FeeratePerKw = { + def getCommitmentFeerate(remoteNodeId: PublicKey, channelFeatures: ChannelFeatures, channelCapacity: Satoshi, currentFeerates_opt: Option[CurrentFeerates]): FeeratePerKw = { val networkFeerate = currentFeerates_opt match { case Some(currentFeerates) => currentFeerates.feeratesPerKw.feePerBlock(feeTargets.commitmentBlockTarget) case None => feeEstimator.getFeeratePerKw(feeTargets.commitmentBlockTarget) } - if (channelVersion.hasAnchorOutputs) { + if (channelFeatures.hasFeature(Features.AnchorOutputs)) { networkFeerate.min(feerateToleranceFor(remoteNodeId).anchorOutputMaxCommitFeerate) } else { networkFeerate diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index 17619306d6..68d7febbaa 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -195,12 +195,12 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId startWith(WAIT_FOR_INIT_INTERNAL, Nothing) when(WAIT_FOR_INIT_INTERNAL)(handleExceptions { - case Event(initFunder@INPUT_INIT_FUNDER(temporaryChannelId, fundingSatoshis, pushMsat, initialFeeratePerKw, fundingTxFeeratePerKw, _, localParams, remote, _, channelFlags, channelVersion), Nothing) => + case Event(initFunder@INPUT_INIT_FUNDER(temporaryChannelId, fundingSatoshis, pushMsat, initialFeeratePerKw, fundingTxFeeratePerKw, _, localParams, remote, _, channelFlags, channelConfig, _), Nothing) => context.system.eventStream.publish(ChannelCreated(self, peer, remoteNodeId, isFunder = true, temporaryChannelId, initialFeeratePerKw, Some(fundingTxFeeratePerKw))) activeConnection = remote txPublisher ! SetChannelId(remoteNodeId, temporaryChannelId) val fundingPubKey = keyManager.fundingPublicKey(localParams.fundingKeyPath).publicKey - val channelKeyPath = keyManager.keyPath(localParams, channelVersion) + val channelKeyPath = keyManager.keyPath(localParams, channelConfig) val open = OpenChannel(nodeParams.chainHash, temporaryChannelId = temporaryChannelId, fundingSatoshis = fundingSatoshis, @@ -224,7 +224,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScript(ByteVector.empty))) goto(WAIT_FOR_ACCEPT_CHANNEL) using DATA_WAIT_FOR_ACCEPT_CHANNEL(initFunder, open) sending open - case Event(inputFundee@INPUT_INIT_FUNDEE(_, localParams, remote, _, _), Nothing) if !localParams.isFunder => + case Event(inputFundee@INPUT_INIT_FUNDEE(_, localParams, remote, _, _, _), Nothing) if !localParams.isFunder => activeConnection = remote txPublisher ! SetChannelId(remoteNodeId, inputFundee.temporaryChannelId) goto(WAIT_FOR_OPEN_CHANNEL) using DATA_WAIT_FOR_OPEN_CHANNEL(inputFundee) @@ -337,14 +337,14 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId }) when(WAIT_FOR_OPEN_CHANNEL)(handleExceptions { - case Event(open: OpenChannel, d@DATA_WAIT_FOR_OPEN_CHANNEL(INPUT_INIT_FUNDEE(_, localParams, _, remoteInit, channelVersion))) => + case Event(open: OpenChannel, d@DATA_WAIT_FOR_OPEN_CHANNEL(INPUT_INIT_FUNDEE(_, localParams, _, remoteInit, channelConfig, channelFeatures))) => log.info("received OpenChannel={}", open) - Helpers.validateParamsFundee(nodeParams, localParams.features, channelVersion, open, remoteNodeId) match { + Helpers.validateParamsFundee(nodeParams, localParams.features, channelFeatures, open, remoteNodeId) match { case Left(t) => handleLocalError(t, d, Some(open)) case _ => context.system.eventStream.publish(ChannelCreated(self, peer, remoteNodeId, isFunder = false, open.temporaryChannelId, open.feeratePerKw, None)) val fundingPubkey = keyManager.fundingPublicKey(localParams.fundingKeyPath).publicKey - val channelKeyPath = keyManager.keyPath(localParams, channelVersion) + val channelKeyPath = keyManager.keyPath(localParams, channelConfig) val minimumDepth = Helpers.minDepthForFunding(nodeParams, open.fundingSatoshis) val accept = AcceptChannel(temporaryChannelId = open.temporaryChannelId, dustLimitSatoshis = localParams.dustLimit, @@ -376,9 +376,10 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId paymentBasepoint = open.paymentBasepoint, delayedPaymentBasepoint = open.delayedPaymentBasepoint, htlcBasepoint = open.htlcBasepoint, - features = remoteInit.features) + features = remoteInit.features, + shutdownScript = None) log.debug("remote params: {}", remoteParams) - goto(WAIT_FOR_FUNDING_CREATED) using DATA_WAIT_FOR_FUNDING_CREATED(open.temporaryChannelId, localParams, remoteParams, open.fundingSatoshis, open.pushMsat, open.feeratePerKw, None, open.firstPerCommitmentPoint, open.channelFlags, channelVersion, accept) sending accept + goto(WAIT_FOR_FUNDING_CREATED) using DATA_WAIT_FOR_FUNDING_CREATED(open.temporaryChannelId, localParams, remoteParams, open.fundingSatoshis, open.pushMsat, open.feeratePerKw, None, open.firstPerCommitmentPoint, open.channelFlags, channelConfig, channelFeatures, accept) sending accept } case Event(c: CloseCommand, d) => handleFastClose(c, d.channelId) @@ -389,7 +390,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId }) when(WAIT_FOR_ACCEPT_CHANNEL)(handleExceptions { - case Event(accept: AcceptChannel, d@DATA_WAIT_FOR_ACCEPT_CHANNEL(INPUT_INIT_FUNDER(temporaryChannelId, fundingSatoshis, pushMsat, initialFeeratePerKw, fundingTxFeeratePerKw, initialRelayFees_opt, localParams, _, remoteInit, _, channelVersion), open)) => + case Event(accept: AcceptChannel, d@DATA_WAIT_FOR_ACCEPT_CHANNEL(INPUT_INIT_FUNDER(temporaryChannelId, fundingSatoshis, pushMsat, initialFeeratePerKw, fundingTxFeeratePerKw, initialRelayFees_opt, localParams, _, remoteInit, _, channelConfig, channelFeatures), open)) => log.info(s"received AcceptChannel=$accept") Helpers.validateParamsFunder(nodeParams, open, accept) match { case Left(t) => handleLocalError(t, d, Some(accept)) @@ -407,12 +408,13 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId paymentBasepoint = accept.paymentBasepoint, delayedPaymentBasepoint = accept.delayedPaymentBasepoint, htlcBasepoint = accept.htlcBasepoint, - features = remoteInit.features) + features = remoteInit.features, + shutdownScript = None) log.debug("remote params: {}", remoteParams) val localFundingPubkey = keyManager.fundingPublicKey(localParams.fundingKeyPath) val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(localFundingPubkey.publicKey, remoteParams.fundingPubKey))) wallet.makeFundingTx(fundingPubkeyScript, fundingSatoshis, fundingTxFeeratePerKw).pipeTo(self) - goto(WAIT_FOR_FUNDING_INTERNAL) using DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId, localParams, remoteParams, fundingSatoshis, pushMsat, initialFeeratePerKw, initialRelayFees_opt, accept.firstPerCommitmentPoint, channelVersion, open) + goto(WAIT_FOR_FUNDING_INTERNAL) using DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId, localParams, remoteParams, fundingSatoshis, pushMsat, initialFeeratePerKw, initialRelayFees_opt, accept.firstPerCommitmentPoint, channelConfig, channelFeatures, open) } case Event(c: CloseCommand, d: DATA_WAIT_FOR_ACCEPT_CHANNEL) => @@ -433,13 +435,13 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId }) when(WAIT_FOR_FUNDING_INTERNAL)(handleExceptions { - case Event(MakeFundingTxResponse(fundingTx, fundingTxOutputIndex, fundingTxFee), d@DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId, localParams, remoteParams, fundingAmount, pushMsat, initialFeeratePerKw, initialRelayFees_opt, remoteFirstPerCommitmentPoint, channelVersion, open)) => + case Event(MakeFundingTxResponse(fundingTx, fundingTxOutputIndex, fundingTxFee), d@DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId, localParams, remoteParams, fundingAmount, pushMsat, initialFeeratePerKw, initialRelayFees_opt, remoteFirstPerCommitmentPoint, channelConfig, channelFeatures, open)) => // let's create the first commitment tx that spends the yet uncommitted funding tx - Funding.makeFirstCommitTxs(keyManager, channelVersion, temporaryChannelId, localParams, remoteParams, fundingAmount, pushMsat, initialFeeratePerKw, fundingTx.hash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint) match { + Funding.makeFirstCommitTxs(keyManager, channelConfig, channelFeatures, temporaryChannelId, localParams, remoteParams, fundingAmount, pushMsat, initialFeeratePerKw, fundingTx.hash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint) 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 = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.fundingKeyPath), TxOwner.Remote, channelVersion.commitmentFormat) + val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.fundingKeyPath), TxOwner.Remote, channelFeatures.commitmentFormat) // signature of their initial commitment tx that pays remote pushMsat val fundingCreated = FundingCreated( temporaryChannelId = temporaryChannelId, @@ -452,7 +454,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId 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(channelId, localParams, remoteParams, fundingTx, fundingTxFee, initialRelayFees_opt, localSpec, localCommitTx, RemoteCommit(0, remoteSpec, remoteCommitTx.tx.txid, remoteFirstPerCommitmentPoint), open.channelFlags, channelVersion, fundingCreated) sending fundingCreated + goto(WAIT_FOR_FUNDING_SIGNED) using DATA_WAIT_FOR_FUNDING_SIGNED(channelId, localParams, remoteParams, fundingTx, fundingTxFee, initialRelayFees_opt, localSpec, localCommitTx, RemoteCommit(0, remoteSpec, remoteCommitTx.tx.txid, remoteFirstPerCommitmentPoint), open.channelFlags, channelConfig, channelFeatures, fundingCreated) sending fundingCreated } case Event(Status.Failure(t), d: DATA_WAIT_FOR_FUNDING_INTERNAL) => @@ -478,19 +480,19 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId }) when(WAIT_FOR_FUNDING_CREATED)(handleExceptions { - case Event(FundingCreated(_, fundingTxHash, fundingTxOutputIndex, remoteSig), d@DATA_WAIT_FOR_FUNDING_CREATED(temporaryChannelId, localParams, remoteParams, fundingAmount, pushMsat, initialFeeratePerKw, initialRelayFees_opt, remoteFirstPerCommitmentPoint, channelFlags, channelVersion, _)) => + case Event(FundingCreated(_, fundingTxHash, fundingTxOutputIndex, remoteSig), d@DATA_WAIT_FOR_FUNDING_CREATED(temporaryChannelId, localParams, remoteParams, fundingAmount, pushMsat, initialFeeratePerKw, initialRelayFees_opt, remoteFirstPerCommitmentPoint, channelFlags, channelConfig, channelFeatures, _)) => // they fund the channel with their funding tx, so the money is theirs (but we are paid pushMsat) - Funding.makeFirstCommitTxs(keyManager, channelVersion, temporaryChannelId, localParams, remoteParams, fundingAmount, pushMsat, initialFeeratePerKw, fundingTxHash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint) match { + Funding.makeFirstCommitTxs(keyManager, channelConfig, channelFeatures, temporaryChannelId, localParams, remoteParams, fundingAmount, pushMsat, initialFeeratePerKw, fundingTxHash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint) match { case Left(ex) => handleLocalError(ex, d, None) case Right((localSpec, localCommitTx, remoteSpec, remoteCommitTx)) => // check remote signature validity val fundingPubKey = keyManager.fundingPublicKey(localParams.fundingKeyPath) - val localSigOfLocalTx = keyManager.sign(localCommitTx, fundingPubKey, TxOwner.Local, channelVersion.commitmentFormat) + val localSigOfLocalTx = keyManager.sign(localCommitTx, fundingPubKey, TxOwner.Local, channelFeatures.commitmentFormat) val signedLocalCommitTx = Transactions.addSigs(localCommitTx, fundingPubKey.publicKey, remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig) Transactions.checkSpendable(signedLocalCommitTx) match { case Failure(_) => handleLocalError(InvalidCommitmentSignature(temporaryChannelId, signedLocalCommitTx.tx), d, None) case Success(_) => - val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, fundingPubKey, TxOwner.Remote, channelVersion.commitmentFormat) + val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, fundingPubKey, TxOwner.Remote, channelFeatures.commitmentFormat) val channelId = toLongId(fundingTxHash, fundingTxOutputIndex) // watch the funding tx transaction val commitInput = localCommitTx.input @@ -498,7 +500,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId channelId = channelId, signature = localSigOfRemoteTx ) - val commitments = Commitments(channelVersion, localParams, remoteParams, channelFlags, + val commitments = Commitments(channelConfig, channelFeatures, localParams, remoteParams, channelFlags, LocalCommit(0, localSpec, CommitTxAndRemoteSig(localCommitTx, remoteSig), htlcTxsAndRemoteSigs = Nil), RemoteCommit(0, remoteSpec, remoteCommitTx.tx.txid, remoteFirstPerCommitmentPoint), LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), localNextHtlcId = 0L, remoteNextHtlcId = 0L, @@ -528,10 +530,10 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId }) when(WAIT_FOR_FUNDING_SIGNED)(handleExceptions { - case Event(msg@FundingSigned(_, remoteSig), d@DATA_WAIT_FOR_FUNDING_SIGNED(channelId, localParams, remoteParams, fundingTx, fundingTxFee, initialRelayFees_opt, localSpec, localCommitTx, remoteCommit, channelFlags, channelVersion, fundingCreated)) => + case Event(msg@FundingSigned(_, remoteSig), d@DATA_WAIT_FOR_FUNDING_SIGNED(channelId, localParams, remoteParams, fundingTx, fundingTxFee, initialRelayFees_opt, localSpec, localCommitTx, remoteCommit, channelFlags, channelConfig, channelFeatures, fundingCreated)) => // we make sure that their sig checks out and that our first commit tx is spendable val fundingPubKey = keyManager.fundingPublicKey(localParams.fundingKeyPath) - val localSigOfLocalTx = keyManager.sign(localCommitTx, fundingPubKey, TxOwner.Local, channelVersion.commitmentFormat) + val localSigOfLocalTx = keyManager.sign(localCommitTx, fundingPubKey, TxOwner.Local, channelFeatures.commitmentFormat) val signedLocalCommitTx = Transactions.addSigs(localCommitTx, fundingPubKey.publicKey, remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig) Transactions.checkSpendable(signedLocalCommitTx) match { case Failure(cause) => @@ -541,7 +543,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId handleLocalError(InvalidCommitmentSignature(channelId, signedLocalCommitTx.tx), d, Some(msg)) case Success(_) => val commitInput = localCommitTx.input - val commitments = Commitments(channelVersion, localParams, remoteParams, channelFlags, + val commitments = Commitments(channelConfig, channelFeatures, localParams, remoteParams, channelFlags, LocalCommit(0, localSpec, CommitTxAndRemoteSig(localCommitTx, remoteSig), htlcTxsAndRemoteSigs = Nil), remoteCommit, LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), localNextHtlcId = 0L, remoteNextHtlcId = 0L, @@ -610,7 +612,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId case Success(_) => log.info(s"channelId=${commitments.channelId} was confirmed at blockHeight=$blockHeight txIndex=$txIndex") blockchain ! WatchFundingLost(self, commitments.commitInput.outPoint.txid, nodeParams.minDepthBlocks) - val channelKeyPath = keyManager.keyPath(d.commitments.localParams, commitments.channelVersion) + val channelKeyPath = keyManager.keyPath(d.commitments.localParams, commitments.channelConfig) val nextPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, 1) val fundingLocked = FundingLocked(commitments.channelId, nextPerCommitmentPoint) deferred.foreach(self ! _) @@ -1504,7 +1506,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId activeConnection = r val yourLastPerCommitmentSecret = d.commitments.remotePerCommitmentSecrets.lastIndex.flatMap(d.commitments.remotePerCommitmentSecrets.getHash).getOrElse(ByteVector32.Zeroes) - val channelKeyPath = keyManager.keyPath(d.commitments.localParams, d.commitments.channelVersion) + val channelKeyPath = keyManager.keyPath(d.commitments.localParams, d.commitments.channelConfig) val myCurrentPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, d.commitments.localCommit.index) val channelReestablish = ChannelReestablish( @@ -1576,14 +1578,14 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId case Event(_: ChannelReestablish, d: DATA_WAIT_FOR_FUNDING_LOCKED) => log.debug("re-sending fundingLocked") - val channelKeyPath = keyManager.keyPath(d.commitments.localParams, d.commitments.channelVersion) + val channelKeyPath = keyManager.keyPath(d.commitments.localParams, d.commitments.channelConfig) val nextPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, 1) val fundingLocked = FundingLocked(d.commitments.channelId, nextPerCommitmentPoint) goto(WAIT_FOR_FUNDING_LOCKED) sending fundingLocked case Event(channelReestablish: ChannelReestablish, d: DATA_NORMAL) => var sendQueue = Queue.empty[LightningMessage] - val channelKeyPath = keyManager.keyPath(d.commitments.localParams, d.commitments.channelVersion) + val channelKeyPath = keyManager.keyPath(d.commitments.localParams, d.commitments.channelConfig) channelReestablish match { case ChannelReestablish(_, _, nextRemoteRevocationNumber, yourLastPerCommitmentSecret, _) if !Helpers.checkLocalCommit(d, nextRemoteRevocationNumber) => // if next_remote_revocation_number is greater than our local commitment index, it means that either we are using an outdated commitment, or they are lying @@ -1664,7 +1666,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId val shutdownInProgress = d.localShutdown.nonEmpty || d.remoteShutdown.nonEmpty if (d.commitments.localParams.isFunder && !shutdownInProgress) { val currentFeeratePerKw = d.commitments.localCommit.spec.feeratePerKw - val networkFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(remoteNodeId, d.commitments.channelVersion, d.commitments.capacity, None) + val networkFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(remoteNodeId, d.commitments.channelFeatures, d.commitments.capacity, None) if (nodeParams.onChainFeeConf.shouldUpdateFee(currentFeeratePerKw, networkFeeratePerKw)) { self ! CMD_UPDATE_FEE(networkFeeratePerKw, commit = true) } @@ -1935,11 +1937,11 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId } private def handleCurrentFeerate(c: CurrentFeerates, d: HasCommitments) = { - val networkFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(remoteNodeId, d.commitments.channelVersion, d.commitments.capacity, Some(c)) + val networkFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(remoteNodeId, d.commitments.channelFeatures, d.commitments.capacity, Some(c)) val currentFeeratePerKw = d.commitments.localCommit.spec.feeratePerKw val shouldUpdateFee = d.commitments.localParams.isFunder && nodeParams.onChainFeeConf.shouldUpdateFee(currentFeeratePerKw, networkFeeratePerKw) val shouldClose = !d.commitments.localParams.isFunder && - nodeParams.onChainFeeConf.feerateToleranceFor(d.commitments.remoteNodeId).isFeeDiffTooHigh(d.commitments.channelVersion, networkFeeratePerKw, currentFeeratePerKw) && + nodeParams.onChainFeeConf.feerateToleranceFor(d.commitments.remoteNodeId).isFeeDiffTooHigh(d.commitments.channelFeatures, networkFeeratePerKw, currentFeeratePerKw) && d.commitments.hasPendingOrProposedHtlcs // we close only if we have HTLCs potentially at risk if (shouldUpdateFee) { self ! CMD_UPDATE_FEE(networkFeeratePerKw, commit = true) @@ -1959,11 +1961,11 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId * @return */ private def handleOfflineFeerate(c: CurrentFeerates, d: HasCommitments) = { - val networkFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(remoteNodeId, d.commitments.channelVersion, d.commitments.capacity, Some(c)) + val networkFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(remoteNodeId, d.commitments.channelFeatures, d.commitments.capacity, Some(c)) val currentFeeratePerKw = d.commitments.localCommit.spec.feeratePerKw // 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).isFeeDiffTooHigh(d.commitments.channelVersion, networkFeeratePerKw, currentFeeratePerKw) && + nodeParams.onChainFeeConf.feerateToleranceFor(d.commitments.remoteNodeId).isFeeDiffTooHigh(d.commitments.channelFeatures, networkFeeratePerKw, currentFeeratePerKw) && d.commitments.hasPendingOrProposedHtlcs // we close only if we have HTLCs potentially at risk if (shouldClose) { if (nodeParams.onChainFeeConf.closeOnOfflineMismatch) { @@ -2301,8 +2303,8 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId private def handleRemoteSpentFuture(commitTx: Transaction, d: DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT) = { log.warning(s"they published their future commit (because we asked them to) in txid=${commitTx.txid}") - d.commitments.channelVersion match { - case v if v.paysDirectlyToWallet => + d.commitments.channelFeatures match { + case ct if ct.paysDirectlyToWallet => val remoteCommitPublished = RemoteCommitPublished(commitTx, None, Map.empty, List.empty, Map.empty) val nextData = DATA_CLOSING(d.commitments, fundingTx = None, waitingSinceBlock = nodeParams.currentBlockHeight, Nil, futureRemoteCommitPublished = Some(remoteCommitPublished)) goto(CLOSING) using nextData storing() // we don't need to claim our main output in the remote commit because it already spends to our wallet address @@ -2419,7 +2421,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId } else if (commitments1.localCommit.index == channelReestablish.nextRemoteRevocationNumber + 1) { // our last revocation got lost, let's resend it log.debug("re-sending last revocation") - val channelKeyPath = keyManager.keyPath(d.commitments.localParams, d.commitments.channelVersion) + val channelKeyPath = keyManager.keyPath(d.commitments.localParams, d.commitments.channelConfig) val localPerCommitmentSecret = keyManager.commitmentSecret(channelKeyPath, d.commitments.localCommit.index - 1) val localNextPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, d.commitments.localCommit.index + 1) val revocation = RevokeAndAck( diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelConfig.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelConfig.scala new file mode 100644 index 0000000000..04e425a57b --- /dev/null +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelConfig.scala @@ -0,0 +1,58 @@ +/* + * Copyright 2021 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.channel + +/** + * Created by t-bast on 24/06/2021. + */ + +/** + * Internal configuration option impacting the channel's structure or behavior. + * This must be set when creating the channel and cannot be changed afterwards. + */ +trait ChannelConfigOption { + // @formatter:off + def supportBit: Int + def name: String + // @formatter:on +} + +case class ChannelConfig(options: Set[ChannelConfigOption]) { + + def hasOption(option: ChannelConfigOption): Boolean = options.contains(option) + +} + +object ChannelConfig { + + val standard: ChannelConfig = ChannelConfig(options = Set(FundingPubKeyBasedChannelKeyPath)) + + def apply(opts: ChannelConfigOption*): ChannelConfig = ChannelConfig(Set.from(opts)) + + /** + * If set, the channel's BIP32 key path will be deterministically derived from the funding public key. + * It makes it very easy to retrieve funds when channel data has been lost: + * - connect to your peer and use option_data_loss_protect to get them to publish their remote commit tx + * - retrieve the commit tx from the bitcoin network, extract your funding pubkey from its witness data + * - recompute your channel keys and spend your output + */ + case object FundingPubKeyBasedChannelKeyPath extends ChannelConfigOption { + override val supportBit: Int = 0 + override val name: String = "funding_pubkey_based_channel_keypath" + } + +} 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 new file mode 100644 index 0000000000..bebbf377e2 --- /dev/null +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala @@ -0,0 +1,66 @@ +/* + * Copyright 2021 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.channel + +import fr.acinq.eclair.Features.{AnchorOutputs, StaticRemoteKey, Wumbo} +import fr.acinq.eclair.transactions.Transactions.{AnchorOutputsCommitmentFormat, CommitmentFormat, DefaultCommitmentFormat} +import fr.acinq.eclair.{Feature, FeatureSupport, Features} + +/** + * Created by t-bast on 24/06/2021. + */ + +/** + * 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. + */ +case class ChannelFeatures(features: Features) { + + /** True if our main output in the remote commitment is directly sent (without any delay) to one of our wallet addresses. */ + val paysDirectlyToWallet: Boolean = { + features.hasFeature(Features.StaticRemoteKey) && !features.hasFeature(Features.AnchorOutputs) + } + + /** Format of the channel transactions. */ + val commitmentFormat: CommitmentFormat = { + if (features.hasFeature(AnchorOutputs)) { + AnchorOutputsCommitmentFormat + } else { + DefaultCommitmentFormat + } + } + + def hasFeature(feature: Feature): Boolean = features.hasFeature(feature) + +} + +object ChannelFeatures { + + /** Pick the channel features that should be used based on local and remote feature bits. */ + def pickChannelFeatures(localFeatures: Features, remoteFeatures: Features): ChannelFeatures = { + // NB: we don't include features that can be safely activated/deactivated without impacting the channel's operation, + // such as option_dataloss_protect or option_shutdown_anysegwit. + val availableFeatures: Seq[Feature] = Seq( + StaticRemoteKey, + Wumbo, + AnchorOutputs, + ).filter(f => Features.canUseFeature(localFeatures, remoteFeatures, f)) + ChannelFeatures(Features(availableFeatures.map(f => f -> FeatureSupport.Mandatory).toMap)) + } + +} diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala index 254b8ff8f1..d761893345 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala @@ -26,7 +26,7 @@ import fr.acinq.eclair.transactions.CommitmentSpec import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.wire.protocol.{AcceptChannel, ChannelAnnouncement, ChannelReestablish, ChannelUpdate, ClosingSigned, FailureMessage, FundingCreated, FundingLocked, FundingSigned, Init, OnionRoutingPacket, OpenChannel, Shutdown, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFulfillHtlc} import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, Features, MilliSatoshi, ShortChannelId, UInt64} -import scodec.bits.{BitVector, ByteVector} +import scodec.bits.ByteVector import java.util.UUID @@ -87,8 +87,14 @@ case class INPUT_INIT_FUNDER(temporaryChannelId: ByteVector32, remote: ActorRef, remoteInit: Init, channelFlags: Byte, - channelVersion: ChannelVersion) -case class INPUT_INIT_FUNDEE(temporaryChannelId: ByteVector32, localParams: LocalParams, remote: ActorRef, remoteInit: Init, channelVersion: ChannelVersion) + channelConfig: ChannelConfig, + channelFeatures: ChannelFeatures) +case class INPUT_INIT_FUNDEE(temporaryChannelId: ByteVector32, + localParams: LocalParams, + remote: ActorRef, + remoteInit: Init, + channelConfig: ChannelConfig, + channelFeatures: ChannelFeatures) case object INPUT_CLOSE_COMPLETE_TIMEOUT // when requesting a mutual close, we wait for as much as this timeout, then unilateral close case object INPUT_DISCONNECTED case class INPUT_RECONNECTED(remote: ActorRef, localInit: Init, remoteInit: Init) @@ -375,7 +381,8 @@ final case class DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId: ByteVector32 initialFeeratePerKw: FeeratePerKw, initialRelayFees_opt: Option[(MilliSatoshi, Int)], remoteFirstPerCommitmentPoint: PublicKey, - channelVersion: ChannelVersion, + channelConfig: ChannelConfig, + channelFeatures: ChannelFeatures, lastSent: OpenChannel) extends Data { val channelId: ByteVector32 = temporaryChannelId } @@ -388,7 +395,8 @@ final case class DATA_WAIT_FOR_FUNDING_CREATED(temporaryChannelId: ByteVector32, initialRelayFees_opt: Option[(MilliSatoshi, Int)], remoteFirstPerCommitmentPoint: PublicKey, channelFlags: Byte, - channelVersion: ChannelVersion, + channelConfig: ChannelConfig, + channelFeatures: ChannelFeatures, lastSent: AcceptChannel) extends Data { val channelId: ByteVector32 = temporaryChannelId } @@ -402,7 +410,8 @@ final case class DATA_WAIT_FOR_FUNDING_SIGNED(channelId: ByteVector32, localCommitTx: CommitTx, remoteCommit: RemoteCommit, channelFlags: Byte, - channelVersion: ChannelVersion, + channelConfig: ChannelConfig, + channelFeatures: ChannelFeatures, lastSent: FundingCreated) extends Data final case class DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments: Commitments, fundingTx: Option[Transaction], @@ -445,8 +454,8 @@ final case class DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT(commitments: Com /** * @param features current connection features, or last features used if the channel is disconnected. Note that these - * features are updated at each reconnection and may be different from the ones that were used when the - * channel was created. See [[ChannelVersion]] for permanent features associated to a channel. + * features are updated at each reconnection and may be different from the channel permanent features + * (see [[ChannelFeatures]]). */ final case class LocalParams(nodeId: PublicKey, fundingKeyPath: DeterministicWallet.KeyPath, @@ -476,61 +485,11 @@ final case class RemoteParams(nodeId: PublicKey, paymentBasepoint: PublicKey, delayedPaymentBasepoint: PublicKey, htlcBasepoint: PublicKey, - features: Features) + features: Features, + shutdownScript: Option[ByteVector]) object ChannelFlags { val AnnounceChannel = 0x01.toByte val Empty = 0x00.toByte } - -case class ChannelVersion(bits: BitVector) { - import ChannelVersion._ - - require(bits.size == ChannelVersion.LENGTH_BITS, "channel version takes 4 bytes") - - val commitmentFormat: CommitmentFormat = if (hasAnchorOutputs) { - AnchorOutputsCommitmentFormat - } else { - DefaultCommitmentFormat - } - - def |(other: ChannelVersion) = ChannelVersion(bits | other.bits) - def &(other: ChannelVersion) = ChannelVersion(bits & other.bits) - def ^(other: ChannelVersion) = ChannelVersion(bits ^ other.bits) - - def isSet(bit: Int): Boolean = bits.reverse.get(bit) - - def hasPubkeyKeyPath: Boolean = isSet(USE_PUBKEY_KEYPATH_BIT) - def hasStaticRemotekey: Boolean = isSet(USE_STATIC_REMOTEKEY_BIT) - def hasAnchorOutputs: Boolean = isSet(USE_ANCHOR_OUTPUTS_BIT) - /** True if our main output in the remote commitment is directly sent (without any delay) to one of our wallet addresses. */ - def paysDirectlyToWallet: Boolean = hasStaticRemotekey && !hasAnchorOutputs -} - -object ChannelVersion { - import scodec.bits._ - - val LENGTH_BITS: Int = 4 * 8 - - private val USE_PUBKEY_KEYPATH_BIT = 0 // bit numbers start at 0 - private val USE_STATIC_REMOTEKEY_BIT = 1 - private val USE_ANCHOR_OUTPUTS_BIT = 2 - - def fromBit(bit: Int): ChannelVersion = ChannelVersion(BitVector.low(LENGTH_BITS).set(bit).reverse) - - def pickChannelVersion(localFeatures: Features, remoteFeatures: Features): ChannelVersion = { - if (Features.canUseFeature(localFeatures, remoteFeatures, Features.AnchorOutputs)) { - ANCHOR_OUTPUTS - } else if (Features.canUseFeature(localFeatures, remoteFeatures, Features.StaticRemoteKey)) { - STATIC_REMOTEKEY - } else { - STANDARD - } - } - - val ZEROES = ChannelVersion(bin"00000000000000000000000000000000") - val STANDARD = ZEROES | fromBit(USE_PUBKEY_KEYPATH_BIT) - val STATIC_REMOTEKEY = STANDARD | fromBit(USE_STATIC_REMOTEKEY_BIT) // PUBKEY_KEYPATH + STATIC_REMOTEKEY - val ANCHOR_OUTPUTS = STATIC_REMOTEKEY | fromBit(USE_ANCHOR_OUTPUTS_BIT) // PUBKEY_KEYPATH + STATIC_REMOTEKEY + ANCHOR_OUTPUTS -} // @formatter:on 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 20d59ac41a..afe6a76d57 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 @@ -69,7 +69,8 @@ trait AbstractCommitments { * So, when we've signed and sent a commit message and are waiting for their revocation message, * theirNextCommitInfo is their next commit tx. The rest of the time, it is their next per-commitment point */ -case class Commitments(channelVersion: ChannelVersion, +case class Commitments(channelConfig: ChannelConfig, + channelFeatures: ChannelFeatures, localParams: LocalParams, remoteParams: RemoteParams, channelFlags: Byte, localCommit: LocalCommit, remoteCommit: RemoteCommit, @@ -80,7 +81,7 @@ case class Commitments(channelVersion: ChannelVersion, commitInput: InputInfo, remotePerCommitmentSecrets: ShaChain, channelId: ByteVector32) extends AbstractCommitments { - require(channelVersion.paysDirectlyToWallet == localParams.walletStaticPaymentBasepoint.isDefined, s"localParams.walletStaticPaymentBasepoint must be defined only for commitments that pay directly to our wallet (version=$channelVersion)") + require(channelFeatures.paysDirectlyToWallet == localParams.walletStaticPaymentBasepoint.isDefined, s"localParams.walletStaticPaymentBasepoint must be defined only for commitments that pay directly to our wallet (channel features: ${channelFeatures.features})") def hasNoPendingHtlcs: Boolean = localCommit.spec.htlcs.isEmpty && remoteCommit.spec.htlcs.isEmpty && remoteNextCommitInfo.isRight @@ -157,7 +158,7 @@ case class Commitments(channelVersion: ChannelVersion, commitTx } - val commitmentFormat: CommitmentFormat = channelVersion.commitmentFormat + val commitmentFormat: CommitmentFormat = channelFeatures.commitmentFormat val localNodeId: PublicKey = localParams.nodeId @@ -297,9 +298,9 @@ object Commitments { // 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 localFeeratePerKw = feeConf.getCommitmentFeerate(commitments.remoteNodeId, commitments.channelVersion, commitments.capacity, None) + val localFeeratePerKw = feeConf.getCommitmentFeerate(commitments.remoteNodeId, commitments.channelFeatures, commitments.capacity, None) val remoteFeeratePerKw = commitments.localCommit.spec.feeratePerKw +: commitments.remoteChanges.all.collect { case f: UpdateFee => f.feeratePerKw } - remoteFeeratePerKw.find(feerate => feeConf.feerateToleranceFor(commitments.remoteNodeId).isFeeDiffTooHigh(commitments.channelVersion, localFeeratePerKw, feerate)) match { + remoteFeeratePerKw.find(feerate => feeConf.feerateToleranceFor(commitments.remoteNodeId).isFeeDiffTooHigh(commitments.channelFeatures, localFeeratePerKw, feerate)) match { case Some(feerate) => return Left(FeerateTooDifferent(commitments.channelId, localFeeratePerKw = localFeeratePerKw, remoteFeeratePerKw = feerate)) case None => } @@ -361,9 +362,9 @@ object Commitments { // 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 localFeeratePerKw = feeConf.getCommitmentFeerate(commitments.remoteNodeId, commitments.channelVersion, commitments.capacity, None) + val localFeeratePerKw = feeConf.getCommitmentFeerate(commitments.remoteNodeId, commitments.channelFeatures, commitments.capacity, None) val remoteFeeratePerKw = commitments.localCommit.spec.feeratePerKw +: commitments.remoteChanges.all.collect { case f: UpdateFee => f.feeratePerKw } - remoteFeeratePerKw.find(feerate => feeConf.feerateToleranceFor(commitments.remoteNodeId).isFeeDiffTooHigh(commitments.channelVersion, localFeeratePerKw, feerate)) match { + remoteFeeratePerKw.find(feerate => feeConf.feerateToleranceFor(commitments.remoteNodeId).isFeeDiffTooHigh(commitments.channelFeatures, localFeeratePerKw, feerate)) match { case Some(feerate) => return Left(FeerateTooDifferent(commitments.channelId, localFeeratePerKw = localFeeratePerKw, remoteFeeratePerKw = feerate)) case None => } @@ -510,9 +511,9 @@ object Commitments { Left(FeerateTooSmall(commitments.channelId, remoteFeeratePerKw = fee.feeratePerKw)) } else { Metrics.RemoteFeeratePerKw.withoutTags().record(fee.feeratePerKw.toLong) - val localFeeratePerKw = feeConf.getCommitmentFeerate(commitments.remoteNodeId, commitments.channelVersion, commitments.capacity, None) + val localFeeratePerKw = feeConf.getCommitmentFeerate(commitments.remoteNodeId, commitments.channelFeatures, commitments.capacity, None) log.info("remote feeratePerKw={}, local feeratePerKw={}, ratio={}", fee.feeratePerKw, localFeeratePerKw, fee.feeratePerKw.toLong.toDouble / localFeeratePerKw.toLong) - if (feeConf.feerateToleranceFor(commitments.remoteNodeId).isFeeDiffTooHigh(commitments.channelVersion, localFeeratePerKw, fee.feeratePerKw) && commitments.hasPendingOrProposedHtlcs) { + if (feeConf.feerateToleranceFor(commitments.remoteNodeId).isFeeDiffTooHigh(commitments.channelFeatures, localFeeratePerKw, fee.feeratePerKw) && commitments.hasPendingOrProposedHtlcs) { Left(FeerateTooDifferent(commitments.channelId, localFeeratePerKw = localFeeratePerKw, remoteFeeratePerKw = fee.feeratePerKw)) } else { // NB: we check that the funder can afford this new fee even if spec allows to do it at next signature @@ -561,11 +562,11 @@ object Commitments { case Right(remoteNextPerCommitmentPoint) => // remote commitment will includes all local changes + remote acked changes val spec = CommitmentSpec.reduce(remoteCommit.spec, remoteChanges.acked, localChanges.proposed) - val (remoteCommitTx, htlcTxs) = makeRemoteTxs(keyManager, channelVersion, remoteCommit.index + 1, localParams, remoteParams, commitInput, remoteNextPerCommitmentPoint, spec) + val (remoteCommitTx, htlcTxs) = makeRemoteTxs(keyManager, channelConfig, channelFeatures, remoteCommit.index + 1, localParams, remoteParams, commitInput, remoteNextPerCommitmentPoint, spec) val sig = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(commitments.localParams.fundingKeyPath), TxOwner.Remote, commitmentFormat) val sortedHtlcTxs: Seq[TransactionWithInputInfo] = htlcTxs.sortBy(_.input.outPoint.index) - val channelKeyPath = keyManager.keyPath(commitments.localParams, commitments.channelVersion) + val channelKeyPath = keyManager.keyPath(commitments.localParams, channelConfig) val htlcSigs = sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(channelKeyPath), remoteNextPerCommitmentPoint, TxOwner.Remote, commitmentFormat)) // NB: IN/OUT htlcs are inverted because this is the remote commit @@ -604,9 +605,9 @@ object Commitments { } val spec = CommitmentSpec.reduce(localCommit.spec, localChanges.acked, remoteChanges.proposed) - val channelKeyPath = keyManager.keyPath(commitments.localParams, commitments.channelVersion) + val channelKeyPath = keyManager.keyPath(localParams, channelConfig) val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, commitments.localCommit.index + 1) - val (localCommitTx, htlcTxs) = makeLocalTxs(keyManager, channelVersion, localCommit.index + 1, localParams, remoteParams, commitInput, localPerCommitmentPoint, spec) + val (localCommitTx, htlcTxs) = makeLocalTxs(keyManager, channelConfig, channelFeatures, localCommit.index + 1, localParams, remoteParams, commitInput, localPerCommitmentPoint, spec) log.info(s"built local commit number=${localCommit.index + 1} toLocalMsat=${spec.toLocal.toLong} toRemoteMsat=${spec.toRemote.toLong} htlc_in={} htlc_out={} feeratePerKw=${spec.feeratePerKw} txid=${localCommitTx.tx.txid} tx={}", spec.htlcs.collect(incoming).map(_.id).mkString(","), spec.htlcs.collect(outgoing).map(_.id).mkString(","), localCommitTx.tx) @@ -692,46 +693,56 @@ object Commitments { } def makeLocalTxs(keyManager: ChannelKeyManager, - channelVersion: ChannelVersion, + channelConfig: ChannelConfig, + channelFeatures: ChannelFeatures, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, localPerCommitmentPoint: PublicKey, spec: CommitmentSpec): (CommitTx, Seq[HtlcTx]) = { - val channelKeyPath = keyManager.keyPath(localParams, channelVersion) + val channelKeyPath = keyManager.keyPath(localParams, channelConfig) val localFundingPubkey = keyManager.fundingPublicKey(localParams.fundingKeyPath).publicKey val localDelayedPaymentPubkey = Generators.derivePubKey(keyManager.delayedPaymentPoint(channelKeyPath).publicKey, localPerCommitmentPoint) val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(channelKeyPath).publicKey, localPerCommitmentPoint) - val remotePaymentPubkey = if (channelVersion.hasStaticRemotekey) remoteParams.paymentBasepoint else Generators.derivePubKey(remoteParams.paymentBasepoint, localPerCommitmentPoint) + val remotePaymentPubkey = if (channelFeatures.hasFeature(Features.StaticRemoteKey)) { + remoteParams.paymentBasepoint + } else { + Generators.derivePubKey(remoteParams.paymentBasepoint, localPerCommitmentPoint) + } val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, localPerCommitmentPoint) val localRevocationPubkey = Generators.revocationPubKey(remoteParams.revocationBasepoint, localPerCommitmentPoint) val localPaymentBasepoint = localParams.walletStaticPaymentBasepoint.getOrElse(keyManager.paymentPoint(channelKeyPath).publicKey) - val outputs = makeCommitTxOutputs(localParams.isFunder, localParams.dustLimit, localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, remotePaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, localFundingPubkey, remoteParams.fundingPubKey, spec, channelVersion.commitmentFormat) + val outputs = makeCommitTxOutputs(localParams.isFunder, localParams.dustLimit, localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, remotePaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, localFundingPubkey, remoteParams.fundingPubKey, spec, channelFeatures.commitmentFormat) val commitTx = Transactions.makeCommitTx(commitmentInput, commitTxNumber, localPaymentBasepoint, remoteParams.paymentBasepoint, localParams.isFunder, outputs) - val htlcTxs = Transactions.makeHtlcTxs(commitTx.tx, localParams.dustLimit, localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, spec.feeratePerKw, outputs, channelVersion.commitmentFormat) + val htlcTxs = Transactions.makeHtlcTxs(commitTx.tx, localParams.dustLimit, localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, spec.feeratePerKw, outputs, channelFeatures.commitmentFormat) (commitTx, htlcTxs) } def makeRemoteTxs(keyManager: ChannelKeyManager, - channelVersion: ChannelVersion, + channelConfig: ChannelConfig, + channelFeatures: ChannelFeatures, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, remotePerCommitmentPoint: PublicKey, spec: CommitmentSpec): (CommitTx, Seq[HtlcTx]) = { - val channelKeyPath = keyManager.keyPath(localParams, channelVersion) + val channelKeyPath = keyManager.keyPath(localParams, channelConfig) val localFundingPubkey = keyManager.fundingPublicKey(localParams.fundingKeyPath).publicKey val localPaymentBasepoint = localParams.walletStaticPaymentBasepoint.getOrElse(keyManager.paymentPoint(channelKeyPath).publicKey) - val localPaymentPubkey = if (channelVersion.hasStaticRemotekey) localPaymentBasepoint else Generators.derivePubKey(localPaymentBasepoint, remotePerCommitmentPoint) + val localPaymentPubkey = if (channelFeatures.hasFeature(Features.StaticRemoteKey)) { + localPaymentBasepoint + } else { + Generators.derivePubKey(localPaymentBasepoint, remotePerCommitmentPoint) + } val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(channelKeyPath).publicKey, remotePerCommitmentPoint) val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint) val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remotePerCommitmentPoint) val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(channelKeyPath).publicKey, remotePerCommitmentPoint) - val outputs = makeCommitTxOutputs(!localParams.isFunder, remoteParams.dustLimit, remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, localPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, remoteParams.fundingPubKey, localFundingPubkey, spec, channelVersion.commitmentFormat) + val outputs = makeCommitTxOutputs(!localParams.isFunder, remoteParams.dustLimit, remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, localPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, remoteParams.fundingPubKey, localFundingPubkey, spec, channelFeatures.commitmentFormat) val commitTx = Transactions.makeCommitTx(commitmentInput, commitTxNumber, remoteParams.paymentBasepoint, localPaymentBasepoint, !localParams.isFunder, outputs) - val htlcTxs = Transactions.makeHtlcTxs(commitTx.tx, remoteParams.dustLimit, remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, spec.feeratePerKw, outputs, channelVersion.commitmentFormat) + val htlcTxs = Transactions.makeHtlcTxs(commitTx.tx, remoteParams.dustLimit, remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, spec.feeratePerKw, outputs, channelFeatures.commitmentFormat) (commitTx, htlcTxs) } 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 4b3f0425c4..c249285286 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 @@ -81,7 +81,7 @@ object Helpers { /** * Called by the fundee */ - def validateParamsFundee(nodeParams: NodeParams, features: Features, channelVersion: ChannelVersion, open: OpenChannel, remoteNodeId: PublicKey): Either[ChannelException, Unit] = { + def validateParamsFundee(nodeParams: NodeParams, features: Features, channelFeatures: ChannelFeatures, open: OpenChannel, remoteNodeId: PublicKey): Either[ChannelException, Unit] = { // BOLT #2: if the chain_hash value, within the open_channel, message is set to a hash of a chain that is unknown to the receiver: // MUST reject the channel. if (nodeParams.chainHash != open.chainHash) return Left(InvalidChainHash(open.temporaryChannelId, local = nodeParams.chainHash, remote = open.chainHash)) @@ -116,8 +116,8 @@ object Helpers { } // 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(remoteNodeId, channelVersion, open.fundingSatoshis, None) - if (nodeParams.onChainFeeConf.feerateToleranceFor(remoteNodeId).isFeeDiffTooHigh(channelVersion, localFeeratePerKw, open.feeratePerKw)) return Left(FeerateTooDifferent(open.temporaryChannelId, localFeeratePerKw, open.feeratePerKw)) + val localFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(remoteNodeId, channelFeatures, open.fundingSatoshis, None) + if (nodeParams.onChainFeeConf.feerateToleranceFor(remoteNodeId).isFeeDiffTooHigh(channelFeatures, localFeeratePerKw, open.feeratePerKw)) return Left(FeerateTooDifferent(open.temporaryChannelId, localFeeratePerKw, open.feeratePerKw)) // only enforce dust limit check on mainnet if (nodeParams.chainHash == Block.LivenetGenesisBlock.hash) { if (open.dustLimitSatoshis < Channel.MIN_DUSTLIMIT) return Left(DustLimitTooSmall(open.temporaryChannelId, open.dustLimitSatoshis, Channel.MIN_DUSTLIMIT)) @@ -249,7 +249,7 @@ object Helpers { * * @return (localSpec, localTx, remoteSpec, remoteTx, fundingTxOutput) */ - def makeFirstCommitTxs(keyManager: ChannelKeyManager, channelVersion: ChannelVersion, temporaryChannelId: ByteVector32, localParams: LocalParams, remoteParams: RemoteParams, fundingAmount: Satoshi, pushMsat: MilliSatoshi, initialFeeratePerKw: FeeratePerKw, fundingTxHash: ByteVector32, fundingTxOutputIndex: Int, remoteFirstPerCommitmentPoint: PublicKey): Either[ChannelException, (CommitmentSpec, CommitTx, CommitmentSpec, CommitTx)] = { + def makeFirstCommitTxs(keyManager: ChannelKeyManager, channelConfig: ChannelConfig, channelFeatures: ChannelFeatures, temporaryChannelId: ByteVector32, localParams: LocalParams, remoteParams: RemoteParams, fundingAmount: Satoshi, pushMsat: MilliSatoshi, initialFeeratePerKw: FeeratePerKw, fundingTxHash: ByteVector32, fundingTxOutputIndex: Int, remoteFirstPerCommitmentPoint: PublicKey): Either[ChannelException, (CommitmentSpec, CommitTx, CommitmentSpec, CommitTx)] = { val toLocalMsat = if (localParams.isFunder) fundingAmount.toMilliSatoshi - pushMsat else pushMsat val toRemoteMsat = if (localParams.isFunder) pushMsat else fundingAmount.toMilliSatoshi - pushMsat @@ -259,7 +259,7 @@ object Helpers { if (!localParams.isFunder) { // they are funder, therefore they pay the fee: we need to make sure they can afford it! val toRemoteMsat = remoteSpec.toLocal - val fees = commitTxTotalCost(remoteParams.dustLimit, remoteSpec, channelVersion.commitmentFormat) + val fees = commitTxTotalCost(remoteParams.dustLimit, remoteSpec, channelFeatures.commitmentFormat) val missing = toRemoteMsat.truncateToSatoshi - localParams.channelReserve - fees if (missing < Satoshi(0)) { return Left(CannotAffordFees(temporaryChannelId, missing = -missing, reserve = localParams.channelReserve, fees = fees)) @@ -267,11 +267,11 @@ object Helpers { } val fundingPubKey = keyManager.fundingPublicKey(localParams.fundingKeyPath) - val channelKeyPath = keyManager.keyPath(localParams, channelVersion) + val channelKeyPath = keyManager.keyPath(localParams, channelConfig) val commitmentInput = makeFundingInputInfo(fundingTxHash, fundingTxOutputIndex, fundingAmount, fundingPubKey.publicKey, remoteParams.fundingPubKey) val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, 0) - val (localCommitTx, _) = Commitments.makeLocalTxs(keyManager, channelVersion, 0, localParams, remoteParams, commitmentInput, localPerCommitmentPoint, localSpec) - val (remoteCommitTx, _) = Commitments.makeRemoteTxs(keyManager, channelVersion, 0, localParams, remoteParams, commitmentInput, remoteFirstPerCommitmentPoint, remoteSpec) + val (localCommitTx, _) = Commitments.makeLocalTxs(keyManager, channelConfig, channelFeatures, 0, localParams, remoteParams, commitmentInput, localPerCommitmentPoint, localSpec) + val (remoteCommitTx, _) = Commitments.makeRemoteTxs(keyManager, channelConfig, channelFeatures, 0, localParams, remoteParams, commitmentInput, remoteFirstPerCommitmentPoint, remoteSpec) Right(localSpec, localCommitTx, remoteSpec, remoteCommitTx) } @@ -432,7 +432,7 @@ object Helpers { def firstClosingFee(commitments: Commitments, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feeEstimator: FeeEstimator, feeTargets: FeeTargets)(implicit log: LoggingAdapter): Satoshi = { val requestedFeerate = feeEstimator.getFeeratePerKw(feeTargets.mutualCloseBlockTarget) - val feeratePerKw = if (commitments.channelVersion.hasAnchorOutputs) { + val feeratePerKw = if (commitments.channelFeatures.hasFeature(Features.AnchorOutputs)) { requestedFeerate } else { // we "MUST set fee_satoshis less than or equal to the base fee of the final commitment transaction" @@ -466,7 +466,7 @@ object Helpers { def checkClosingSignature(keyManager: ChannelKeyManager, commitments: Commitments, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, remoteClosingFee: Satoshi, remoteClosingSig: ByteVector64)(implicit log: LoggingAdapter): Either[ChannelException, ClosingTx] = { import commitments._ val lastCommitFeeSatoshi = commitments.commitInput.txOut.amount - commitments.localCommit.commitTxAndRemoteSig.commitTx.tx.txOut.map(_.amount).sum - if (remoteClosingFee > lastCommitFeeSatoshi && !commitments.channelVersion.hasAnchorOutputs) { + if (remoteClosingFee > lastCommitFeeSatoshi && !commitments.channelFeatures.hasFeature(Features.AnchorOutputs)) { log.error(s"remote proposed a commit fee higher than the last commitment fee: remoteClosingFeeSatoshi=${remoteClosingFee.toLong} lastCommitFeeSatoshi=$lastCommitFeeSatoshi") Left(InvalidCloseFee(commitments.channelId, remoteClosingFee)) } else { @@ -505,7 +505,7 @@ object Helpers { def claimCurrentLocalCommitTxOutputs(keyManager: ChannelKeyManager, commitments: Commitments, tx: Transaction, feeEstimator: FeeEstimator, feeTargets: FeeTargets)(implicit log: LoggingAdapter): LocalCommitPublished = { import commitments._ require(localCommit.commitTxAndRemoteSig.commitTx.tx.txid == tx.txid, "txid mismatch, provided tx is not the current local commit tx") - val channelKeyPath = keyManager.keyPath(localParams, channelVersion) + val channelKeyPath = keyManager.keyPath(localParams, channelConfig) val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, commitments.localCommit.index.toInt) val localRevocationPubkey = Generators.revocationPubKey(remoteParams.revocationBasepoint, localPerCommitmentPoint) val localDelayedPubkey = Generators.derivePubKey(keyManager.delayedPaymentPoint(channelKeyPath).publicKey, localPerCommitmentPoint) @@ -572,7 +572,7 @@ object Helpers { import commitments._ if (isHtlcSuccess(tx, localCommitPublished) || isHtlcTimeout(tx, localCommitPublished)) { val feeratePerKwDelayed = feeEstimator.getFeeratePerKw(feeTargets.claimMainBlockTarget) - val channelKeyPath = keyManager.keyPath(localParams, channelVersion) + val channelKeyPath = keyManager.keyPath(localParams, channelConfig) val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, commitments.localCommit.index.toInt) val localRevocationPubkey = Generators.revocationPubKey(remoteParams.revocationBasepoint, localPerCommitmentPoint) val localDelayedPubkey = Generators.derivePubKey(keyManager.delayedPaymentPoint(channelKeyPath).publicKey, localPerCommitmentPoint) @@ -599,11 +599,11 @@ object Helpers { * @return a list of transactions (one per output of the commit tx that we can claim) */ def claimRemoteCommitTxOutputs(keyManager: ChannelKeyManager, commitments: Commitments, remoteCommit: RemoteCommit, tx: Transaction, feeEstimator: FeeEstimator, feeTargets: FeeTargets)(implicit log: LoggingAdapter): RemoteCommitPublished = { - import commitments.{channelVersion, commitInput, localParams, remoteParams} + import commitments.{channelConfig, channelFeatures, commitInput, localParams, remoteParams} require(remoteCommit.txid == tx.txid, "txid mismatch, provided tx is not the current remote commit tx") - val (remoteCommitTx, _) = Commitments.makeRemoteTxs(keyManager, channelVersion, remoteCommit.index, localParams, remoteParams, commitInput, remoteCommit.remotePerCommitmentPoint, remoteCommit.spec) + val (remoteCommitTx, _) = Commitments.makeRemoteTxs(keyManager, channelConfig, channelFeatures, remoteCommit.index, localParams, remoteParams, commitInput, remoteCommit.remotePerCommitmentPoint, remoteCommit.spec) require(remoteCommitTx.tx.txid == tx.txid, "txid mismatch, cannot recompute the current remote commit tx") - val channelKeyPath = keyManager.keyPath(localParams, channelVersion) + val channelKeyPath = keyManager.keyPath(localParams, channelConfig) val localFundingPubkey = keyManager.fundingPublicKey(localParams.fundingKeyPath).publicKey val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(channelKeyPath).publicKey, remoteCommit.remotePerCommitmentPoint) val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remoteCommit.remotePerCommitmentPoint) @@ -657,20 +657,19 @@ object Helpers { } ).flatten - channelVersion match { - case v if v.paysDirectlyToWallet => - RemoteCommitPublished( - commitTx = tx, - claimMainOutputTx = None, - claimHtlcTxs = htlcTxs, - claimAnchorTxs = claimAnchorTxs, - irrevocablySpent = Map.empty - ) - case _ => - claimRemoteCommitMainOutput(keyManager, commitments, remoteCommit.remotePerCommitmentPoint, tx, feeEstimator, feeTargets).copy( - claimHtlcTxs = htlcTxs, - claimAnchorTxs = claimAnchorTxs, - ) + if (channelFeatures.paysDirectlyToWallet) { + RemoteCommitPublished( + commitTx = tx, + claimMainOutputTx = None, + claimHtlcTxs = htlcTxs, + claimAnchorTxs = claimAnchorTxs, + irrevocablySpent = Map.empty + ) + } else { + claimRemoteCommitMainOutput(keyManager, commitments, remoteCommit.remotePerCommitmentPoint, tx, feeEstimator, feeTargets).copy( + claimHtlcTxs = htlcTxs, + claimAnchorTxs = claimAnchorTxs, + ) } } @@ -685,7 +684,7 @@ object Helpers { * @return a transaction claiming our main output */ def claimRemoteCommitMainOutput(keyManager: ChannelKeyManager, commitments: Commitments, remotePerCommitmentPoint: PublicKey, tx: Transaction, feeEstimator: FeeEstimator, feeTargets: FeeTargets)(implicit log: LoggingAdapter): RemoteCommitPublished = { - val channelKeyPath = keyManager.keyPath(commitments.localParams, commitments.channelVersion) + val channelKeyPath = keyManager.keyPath(commitments.localParams, commitments.channelConfig) val localPubkey = Generators.derivePubKey(keyManager.paymentPoint(channelKeyPath).publicKey, remotePerCommitmentPoint) val localPaymentPoint = keyManager.paymentPoint(channelKeyPath).publicKey val feeratePerKwMain = feeEstimator.getFeeratePerKw(feeTargets.claimMainBlockTarget) @@ -726,7 +725,7 @@ object Helpers { def claimRevokedRemoteCommitTxOutputs(keyManager: ChannelKeyManager, commitments: Commitments, commitTx: Transaction, db: ChannelsDb, feeEstimator: FeeEstimator, feeTargets: FeeTargets)(implicit log: LoggingAdapter): Option[RevokedCommitPublished] = { import commitments._ require(commitTx.txIn.size == 1, "commitment tx should have 1 input") - val channelKeyPath = keyManager.keyPath(localParams, channelVersion) + val channelKeyPath = keyManager.keyPath(localParams, channelConfig) val obscuredTxNumber = Transactions.decodeTxNumber(commitTx.txIn.head.sequence, commitTx.lockTime) val localPaymentPoint = localParams.walletStaticPaymentBasepoint.getOrElse(keyManager.paymentPoint(channelKeyPath).publicKey) // this tx has been published by remote, so we need to invert local/remote params @@ -749,11 +748,11 @@ object Helpers { val feeratePerKwPenalty = feeEstimator.getFeeratePerKw(target = 2) // first we will claim our main output right away - val mainTx = channelVersion match { - case v if v.paysDirectlyToWallet => + val mainTx = channelFeatures match { + case ct if ct.paysDirectlyToWallet => log.info(s"channel uses option_static_remotekey to pay directly to our wallet, there is nothing to do") None - case v if v.hasAnchorOutputs => generateTx("remote-main-delayed") { + case ct if ct.features.hasFeature(Features.AnchorOutputs) => generateTx("remote-main-delayed") { Transactions.makeClaimRemoteDelayedOutputTx(commitTx, localParams.dustLimit, localPaymentPoint, localParams.defaultFinalScriptPubKey, feeratePerKwMain).map(claimMain => { val sig = keyManager.sign(claimMain, keyManager.paymentPoint(channelKeyPath), TxOwner.Local, commitmentFormat) Transactions.addSigs(claimMain, sig) @@ -829,7 +828,7 @@ object Helpers { import commitments._ val commitTx = revokedCommitPublished.commitTx val obscuredTxNumber = Transactions.decodeTxNumber(commitTx.txIn.head.sequence, commitTx.lockTime) - val channelKeyPath = keyManager.keyPath(localParams, channelVersion) + val channelKeyPath = keyManager.keyPath(localParams, channelConfig) val localPaymentPoint = localParams.walletStaticPaymentBasepoint.getOrElse(keyManager.paymentPoint(channelKeyPath).publicKey) // this tx has been published by remote, so we need to invert local/remote params val txNumber = Transactions.obscuredCommitTxNumber(obscuredTxNumber, !localParams.isFunder, remoteParams.paymentBasepoint, localPaymentPoint) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisher.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisher.scala index 7ab258901c..d61a36ba7b 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisher.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisher.scala @@ -243,7 +243,7 @@ private class ReplaceableTxPublisher(nodeParams: NodeParams, // been confirmed (we don't need to check again here). HtlcTxAndWitnessData(htlcTx, cmd.commitments) match { case Some(txWithWitnessData) if targetFeerate <= commitFeerate => - val channelKeyPath = keyManager.keyPath(cmd.commitments.localParams, cmd.commitments.channelVersion) + val channelKeyPath = keyManager.keyPath(cmd.commitments.localParams, cmd.commitments.channelConfig) val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, cmd.commitments.localCommit.index) val localHtlcBasepoint = keyManager.htlcPoint(channelKeyPath) val localSig = keyManager.sign(htlcTx, localHtlcBasepoint, localPerCommitmentPoint, TxOwner.Local, cmd.commitments.commitmentFormat) @@ -296,7 +296,7 @@ private class ReplaceableTxPublisher(nodeParams: NodeParams, // NB: we've already checked witness data in the precondition phase. Witness data extraction should be done // earlier by the channel to remove this duplication. val txWithWitnessData = HtlcTxAndWitnessData(htlcTx, cmd.commitments).get - val channelKeyPath = keyManager.keyPath(cmd.commitments.localParams, cmd.commitments.channelVersion) + val channelKeyPath = keyManager.keyPath(cmd.commitments.localParams, cmd.commitments.channelConfig) val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, cmd.commitments.localCommit.index) val localHtlcBasepoint = keyManager.htlcPoint(channelKeyPath) val localSig = keyManager.sign(htlcTx, localHtlcBasepoint, localPerCommitmentPoint, TxOwner.Local, cmd.commitments.commitmentFormat) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/ChannelKeyManager.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/ChannelKeyManager.scala index 91071bd8e1..b1979d3a19 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/ChannelKeyManager.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/ChannelKeyManager.scala @@ -19,7 +19,7 @@ package fr.acinq.eclair.crypto.keymanager import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.DeterministicWallet.ExtendedPublicKey import fr.acinq.bitcoin.{ByteVector64, Crypto, DeterministicWallet, Protocol} -import fr.acinq.eclair.channel.{ChannelVersion, LocalParams} +import fr.acinq.eclair.channel.{ChannelConfig, LocalParams} import fr.acinq.eclair.transactions.Transactions.{CommitmentFormat, TransactionWithInputInfo, TxOwner} import scodec.bits.ByteVector @@ -41,12 +41,14 @@ trait ChannelKeyManager { def commitmentPoint(channelKeyPath: DeterministicWallet.KeyPath, index: Long): Crypto.PublicKey - def keyPath(localParams: LocalParams, channelVersion: ChannelVersion): DeterministicWallet.KeyPath = if (channelVersion.hasPubkeyKeyPath) { - // deterministic mode: use the funding pubkey to compute the channel key path - ChannelKeyManager.keyPath(fundingPublicKey(localParams.fundingKeyPath)) - } else { - // legacy mode: we reuse the funding key path as our channel key path - localParams.fundingKeyPath + def keyPath(localParams: LocalParams, channelConfig: ChannelConfig): DeterministicWallet.KeyPath = { + if (channelConfig.hasOption(ChannelConfig.FundingPubKeyBasedChannelKeyPath)) { + // deterministic mode: use the funding pubkey to compute the channel key path + ChannelKeyManager.keyPath(fundingPublicKey(localParams.fundingKeyPath)) + } else { + // legacy mode: we reuse the funding key path as our channel key path + localParams.fundingKeyPath + } } /** @@ -114,7 +116,7 @@ object ChannelKeyManager { val buffer = Crypto.sha256(fundingPubKey.value) val bis = new ByteArrayInputStream(buffer.toArray) - def next() = Protocol.uint32(bis, ByteOrder.BIG_ENDIAN) + def next(): Long = Protocol.uint32(bis, ByteOrder.BIG_ENDIAN) DeterministicWallet.KeyPath(Seq(next(), next(), next(), next(), next(), next(), next(), next())) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala index 509980a442..44818a4fbb 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala @@ -130,25 +130,27 @@ class Peer(val nodeParams: NodeParams, remoteNodeId: PublicKey, wallet: EclairWa sender ! Status.Failure(new RuntimeException(s"fundingSatoshis=${c.fundingSatoshis} is too big for the current settings, increase 'eclair.max-funding-satoshis' (see eclair.conf)")) stay } else { - val channelVersion = ChannelVersion.pickChannelVersion(d.localFeatures, d.remoteFeatures) - val (channel, localParams) = createNewChannel(nodeParams, d.localFeatures, funder = true, c.fundingSatoshis, origin_opt = Some(sender), channelVersion) + val channelConfig = ChannelConfig.standard + val channelFeatures = ChannelFeatures.pickChannelFeatures(d.localFeatures, d.remoteFeatures) + val (channel, localParams) = createNewChannel(nodeParams, d.localFeatures, funder = true, c.fundingSatoshis, origin_opt = Some(sender), channelFeatures) c.timeout_opt.map(openTimeout => context.system.scheduler.scheduleOnce(openTimeout.duration, channel, Channel.TickChannelOpenTimeout)(context.dispatcher)) val temporaryChannelId = randomBytes32() - val channelFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(remoteNodeId, channelVersion, c.fundingSatoshis, None) + val channelFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(remoteNodeId, channelFeatures, c.fundingSatoshis, None) val fundingTxFeeratePerKw = c.fundingTxFeeratePerKw_opt.getOrElse(nodeParams.onChainFeeConf.feeEstimator.getFeeratePerKw(target = nodeParams.onChainFeeConf.feeTargets.fundingBlockTarget)) log.info(s"requesting a new channel with fundingSatoshis=${c.fundingSatoshis}, pushMsat=${c.pushMsat} and fundingFeeratePerByte=${c.fundingTxFeeratePerKw_opt} temporaryChannelId=$temporaryChannelId localParams=$localParams") - channel ! INPUT_INIT_FUNDER(temporaryChannelId, c.fundingSatoshis, c.pushMsat, channelFeeratePerKw, fundingTxFeeratePerKw, c.initialRelayFees_opt, localParams, d.peerConnection, d.remoteInit, c.channelFlags.getOrElse(nodeParams.channelFlags), channelVersion) + channel ! INPUT_INIT_FUNDER(temporaryChannelId, c.fundingSatoshis, c.pushMsat, channelFeeratePerKw, fundingTxFeeratePerKw, c.initialRelayFees_opt, localParams, d.peerConnection, d.remoteInit, c.channelFlags.getOrElse(nodeParams.channelFlags), channelConfig, channelFeatures) stay using d.copy(channels = d.channels + (TemporaryChannelId(temporaryChannelId) -> channel)) } case Event(msg: protocol.OpenChannel, d: ConnectedData) => d.channels.get(TemporaryChannelId(msg.temporaryChannelId)) match { case None => - val channelVersion = ChannelVersion.pickChannelVersion(d.localFeatures, d.remoteFeatures) - val (channel, localParams) = createNewChannel(nodeParams, d.localFeatures, funder = false, fundingAmount = msg.fundingSatoshis, origin_opt = None, channelVersion) + val channelConfig = ChannelConfig.standard + val channelFeatures = ChannelFeatures.pickChannelFeatures(d.localFeatures, d.remoteFeatures) + val (channel, localParams) = createNewChannel(nodeParams, d.localFeatures, funder = false, fundingAmount = msg.fundingSatoshis, origin_opt = None, channelFeatures) val temporaryChannelId = msg.temporaryChannelId log.info(s"accepting a new channel with temporaryChannelId=$temporaryChannelId localParams=$localParams") - channel ! INPUT_INIT_FUNDEE(temporaryChannelId, localParams, d.peerConnection, d.remoteInit, channelVersion) + channel ! INPUT_INIT_FUNDEE(temporaryChannelId, localParams, d.peerConnection, d.remoteInit, channelConfig, channelFeatures) channel ! msg stay using d.copy(channels = d.channels + (TemporaryChannelId(temporaryChannelId) -> channel)) case Some(_) => @@ -292,13 +294,12 @@ class Peer(val nodeParams: NodeParams, remoteNodeId: PublicKey, wallet: EclairWa s(e) } - def createNewChannel(nodeParams: NodeParams, features: Features, funder: Boolean, fundingAmount: Satoshi, origin_opt: Option[ActorRef], channelVersion: ChannelVersion): (ActorRef, LocalParams) = { - val (finalScript, walletStaticPaymentBasepoint) = channelVersion match { - case v if v.paysDirectlyToWallet => - val walletKey = Helpers.getWalletPaymentBasepoint(wallet) - (Script.write(Script.pay2wpkh(walletKey)), Some(walletKey)) - case _ => - (Helpers.getFinalScriptPubKey(wallet, nodeParams.chainHash), None) + def createNewChannel(nodeParams: NodeParams, features: Features, funder: Boolean, fundingAmount: Satoshi, origin_opt: Option[ActorRef], channelFeatures: ChannelFeatures): (ActorRef, LocalParams) = { + val (finalScript, walletStaticPaymentBasepoint) = if (channelFeatures.paysDirectlyToWallet) { + val walletKey = Helpers.getWalletPaymentBasepoint(wallet) + (Script.write(Script.pay2wpkh(walletKey)), Some(walletKey)) + } else { + (Helpers.getFinalScriptPubKey(wallet, nodeParams.chainHash), None) } val localParams = makeChannelParams(nodeParams, features, finalScript, walletStaticPaymentBasepoint, funder, fundingAmount) val channel = spawnChannel(origin_opt) 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 488f259d3a..b4c22df3c6 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 @@ -16,7 +16,6 @@ package fr.acinq.eclair.wire.internal.channel.version0 -import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.DeterministicWallet.{ExtendedPrivateKey, KeyPath} import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Crypto, OutPoint, Transaction, TxOut} import fr.acinq.eclair.MilliSatoshi @@ -28,7 +27,7 @@ import fr.acinq.eclair.wire.internal.channel.version0.ChannelTypes0.{HtlcTxAndSi import fr.acinq.eclair.wire.protocol.CommonCodecs._ import fr.acinq.eclair.wire.protocol.LightningMessageCodecs._ import fr.acinq.eclair.wire.protocol.UpdateMessage -import scodec.bits.BitVector +import scodec.bits.{BitVector, ByteVector} import scodec.codecs._ import scodec.{Attempt, Codec} @@ -53,16 +52,16 @@ private[channel] object ChannelCodecs0 { ("path" | keyPathCodec) :: ("parent" | int64)).as[ExtendedPrivateKey].decodeOnly - val channelVersionCodec: Codec[ChannelVersion] = discriminatorWithDefault[ChannelVersion]( - discriminator = discriminated[ChannelVersion].by(byte) - .typecase(0x01, bits(ChannelVersion.LENGTH_BITS).as[ChannelVersion]) + val channelVersionCodec: Codec[ChannelTypes0.ChannelVersion] = discriminatorWithDefault[ChannelTypes0.ChannelVersion]( + discriminator = discriminated[ChannelTypes0.ChannelVersion].by(byte) + .typecase(0x01, bits(ChannelTypes0.ChannelVersion.LENGTH_BITS).as[ChannelTypes0.ChannelVersion]) // NB: 0x02 and 0x03 are *reserved* for backward compatibility reasons , - fallback = provide(ChannelVersion.ZEROES) // README: DO NOT CHANGE THIS !! old channels don't have a channel version + fallback = provide(ChannelTypes0.ChannelVersion.ZEROES) // README: DO NOT CHANGE THIS !! old channels don't have a channel version // field and don't support additional features which is why all bits are set to 0. ) - def localParamsCodec(channelVersion: ChannelVersion): Codec[LocalParams] = ( + def localParamsCodec(channelVersion: ChannelTypes0.ChannelVersion): Codec[LocalParams] = ( ("nodeId" | publicKey) :: ("channelPath" | keyPathCodec) :: ("dustLimit" | satoshi) :: @@ -89,7 +88,8 @@ private[channel] object ChannelCodecs0 { ("paymentBasepoint" | publicKey) :: ("delayedPaymentBasepoint" | publicKey) :: ("htlcBasepoint" | publicKey) :: - ("features" | combinedFeaturesCodec)).as[RemoteParams].decodeOnly + ("features" | combinedFeaturesCodec) :: + ("shutdownScript" | provide[Option[ByteVector]](None))).as[RemoteParams].decodeOnly val htlcCodec: Codec[DirectedHtlc] = discriminated[DirectedHtlc].by(bool) .typecase(true, updateAddHtlcCodec.as[IncomingHtlc]) @@ -154,10 +154,10 @@ private[channel] object ChannelCodecs0 { ("commitTx" | (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[CommitTx]) :: ("htlcTxsAndSigs" | listOfN(uint16, htlcTxAndSigsCodec))).as[PublishableTxs].decodeOnly - def localCommitCodec(remoteFundingPubKey: PublicKey): Codec[LocalCommit] = ( + val localCommitCodec: Codec[ChannelTypes0.LocalCommit] = ( ("index" | uint64overflow) :: ("spec" | commitmentSpecCodec) :: - ("publishableTxs" | publishableTxsCodec)).as[ChannelTypes0.LocalCommit].map(_.migrate(remoteFundingPubKey)).decodeOnly + ("publishableTxs" | publishableTxsCodec)).as[ChannelTypes0.LocalCommit].decodeOnly val remoteCommitCodec: Codec[RemoteCommit] = ( ("index" | uint64overflow) :: @@ -225,21 +225,20 @@ private[channel] object ChannelCodecs0 { val commitmentsCodec: Codec[Commitments] = ( ("channelVersion" | channelVersionCodec) >>:~ { channelVersion => ("localParams" | localParamsCodec(channelVersion)) :: - (("remoteParams" | remoteParamsCodec) >>:~ { remoteParams => - ("channelFlags" | byte) :: - ("localCommit" | localCommitCodec(remoteParams.fundingPubKey)) :: - ("remoteCommit" | remoteCommitCodec) :: - ("localChanges" | localChangesCodec) :: - ("remoteChanges" | remoteChangesCodec) :: - ("localNextHtlcId" | uint64overflow) :: - ("remoteNextHtlcId" | uint64overflow) :: - ("originChannels" | originsMapCodec) :: - ("remoteNextCommitInfo" | either(bool, waitingForRevocationCodec, publicKey)) :: - ("commitInput" | inputInfoCodec) :: - ("remotePerCommitmentSecrets" | ShaChain.shaChainCodec) :: - ("channelId" | bytes32) - }) - }).as[Commitments].decodeOnly + ("remoteParams" | remoteParamsCodec) :: + ("channelFlags" | byte) :: + ("localCommit" | localCommitCodec) :: + ("remoteCommit" | remoteCommitCodec) :: + ("localChanges" | localChangesCodec) :: + ("remoteChanges" | remoteChangesCodec) :: + ("localNextHtlcId" | uint64overflow) :: + ("remoteNextHtlcId" | uint64overflow) :: + ("originChannels" | originsMapCodec) :: + ("remoteNextCommitInfo" | either(bool, waitingForRevocationCodec, publicKey)) :: + ("commitInput" | inputInfoCodec) :: + ("remotePerCommitmentSecrets" | ShaChain.shaChainCodec) :: + ("channelId" | bytes32) + }).as[ChannelTypes0.Commitments].decodeOnly.map[Commitments](_.migrate()).decodeOnly val closingTxProposedCodec: Codec[ClosingTxProposed] = ( ("unsignedTx" | closingTxCodec) :: 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 1515277242..62da73df4a 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 @@ -19,10 +19,12 @@ package fr.acinq.eclair.wire.internal.channel.version0 import com.softwaremill.quicklens._ import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Crypto, OP_CHECKMULTISIG, OP_PUSHDATA, OutPoint, Satoshi, Script, ScriptWitness, Transaction, TxOut} -import fr.acinq.eclair.channel -import fr.acinq.eclair.channel.{CommitTxAndRemoteSig, HtlcTxAndRemoteSig} +import fr.acinq.eclair.channel._ +import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.transactions.CommitmentSpec import fr.acinq.eclair.transactions.Transactions._ +import fr.acinq.eclair.{Feature, FeatureSupport, Features, channel} +import scodec.bits.BitVector private[channel] object ChannelTypes0 { @@ -99,17 +101,20 @@ private[channel] object ChannelTypes0 { } } - /** - * Starting with version2, we store a complete ClosingTx object for mutual close scenarios instead of simply storing - * 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), Nil), tx, None) + /** + * Starting with version2, we store a complete ClosingTx object for mutual close scenarios instead of simply storing + * 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), Nil), tx, None) case class HtlcTxAndSigs(txinfo: HtlcTx, localSig: ByteVector64, remoteSig: ByteVector64) case class PublishableTxs(commitTx: CommitTx, htlcTxsAndSigs: List[HtlcTxAndSigs]) + // 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 unsigned txs and remote + // signatures. case class LocalCommit(index: Long, spec: CommitmentSpec, publishableTxs: PublishableTxs) { def migrate(remoteFundingPubKey: PublicKey): channel.LocalCommit = { val remoteSig = extractRemoteSig(publishableTxs.commitTx, remoteFundingPubKey) @@ -138,4 +143,80 @@ private[channel] object ChannelTypes0 { } } } + + // Before version3, we had a ChannelVersion field describing what channel features were activated. It was mixing + // official features (static_remotekey, anchor_outputs) and internal features (channel key derivation scheme). + // We separated this into two separate fields in version3: + // - a channel type field containing the channel Bolt 9 features + // - an internal channel configuration field + case class ChannelVersion(bits: BitVector) { + // @formatter:off + def isSet(bit: Int): Boolean = bits.reverse.get(bit) + def |(other: ChannelVersion): ChannelVersion = ChannelVersion(bits | other.bits) + + def hasPubkeyKeyPath: Boolean = isSet(ChannelVersion.USE_PUBKEY_KEYPATH_BIT) + def hasStaticRemotekey: Boolean = isSet(ChannelVersion.USE_STATIC_REMOTEKEY_BIT) + def hasAnchorOutputs: Boolean = isSet(ChannelVersion.USE_ANCHOR_OUTPUTS_BIT) + def paysDirectlyToWallet: Boolean = hasStaticRemotekey && !hasAnchorOutputs + // @formatter:on + } + + object ChannelVersion { + + import scodec.bits._ + + val LENGTH_BITS: Int = 4 * 8 + + private val USE_PUBKEY_KEYPATH_BIT = 0 // bit numbers start at 0 + private val USE_STATIC_REMOTEKEY_BIT = 1 + private val USE_ANCHOR_OUTPUTS_BIT = 2 + + def fromBit(bit: Int): ChannelVersion = ChannelVersion(BitVector.low(LENGTH_BITS).set(bit).reverse) + + val ZEROES = ChannelVersion(bin"00000000000000000000000000000000") + val STANDARD = ZEROES | fromBit(USE_PUBKEY_KEYPATH_BIT) + val STATIC_REMOTEKEY = STANDARD | fromBit(USE_STATIC_REMOTEKEY_BIT) // PUBKEY_KEYPATH + STATIC_REMOTEKEY + val ANCHOR_OUTPUTS = STATIC_REMOTEKEY | fromBit(USE_ANCHOR_OUTPUTS_BIT) // PUBKEY_KEYPATH + STATIC_REMOTEKEY + ANCHOR_OUTPUTS + } + + case class Commitments(channelVersion: ChannelVersion, + localParams: LocalParams, remoteParams: RemoteParams, + channelFlags: Byte, + localCommit: LocalCommit, remoteCommit: RemoteCommit, + localChanges: LocalChanges, remoteChanges: RemoteChanges, + localNextHtlcId: Long, remoteNextHtlcId: Long, + originChannels: Map[Long, Origin], + remoteNextCommitInfo: Either[WaitingForRevocation, PublicKey], + commitInput: InputInfo, + remotePerCommitmentSecrets: ShaChain, channelId: ByteVector32) { + def migrate(): channel.Commitments = { + val channelConfig = if (channelVersion.hasPubkeyKeyPath) { + ChannelConfig(ChannelConfig.FundingPubKeyBasedChannelKeyPath) + } else { + ChannelConfig() + } + val isWumboChannel = commitInput.txOut.amount > Satoshi(16777215) + val baseFeatures: Seq[Feature] = if (isWumboChannel) Seq(Features.Wumbo) else Nil + val commitmentFeatures: Seq[Feature] = if (channelVersion.hasAnchorOutputs) { + Seq(Features.StaticRemoteKey, Features.AnchorOutputs) + } else if (channelVersion.hasStaticRemotekey) { + Seq(Features.StaticRemoteKey) + } else { + Nil + } + val channelFeatures = ChannelFeatures(Features((baseFeatures ++ commitmentFeatures).map(f => f -> FeatureSupport.Mandatory).toMap)) + channel.Commitments( + channelConfig, channelFeatures, + localParams, remoteParams, + channelFlags, + localCommit.migrate(remoteParams.fundingPubKey), remoteCommit, + localChanges, remoteChanges, + localNextHtlcId, remoteNextHtlcId, + originChannels, + remoteNextCommitInfo, + commitInput, + remotePerCommitmentSecrets, channelId) + } + } + } 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 80236bc975..fd145291e9 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 @@ -16,7 +16,6 @@ package fr.acinq.eclair.wire.internal.channel.version1 -import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.DeterministicWallet.{ExtendedPrivateKey, KeyPath} import fr.acinq.bitcoin.{ByteVector32, OutPoint, Transaction, TxOut} import fr.acinq.eclair.MilliSatoshi @@ -29,6 +28,7 @@ import fr.acinq.eclair.wire.internal.channel.version0.ChannelTypes0.{HtlcTxAndSi import fr.acinq.eclair.wire.protocol.CommonCodecs._ import fr.acinq.eclair.wire.protocol.LightningMessageCodecs._ import fr.acinq.eclair.wire.protocol.UpdateMessage +import scodec.bits.ByteVector import scodec.codecs._ import scodec.{Attempt, Codec} @@ -45,9 +45,9 @@ private[channel] object ChannelCodecs1 { ("path" | keyPathCodec) :: ("parent" | int64)).as[ExtendedPrivateKey] - val channelVersionCodec: Codec[ChannelVersion] = bits(ChannelVersion.LENGTH_BITS).as[ChannelVersion] + val channelVersionCodec: Codec[ChannelTypes0.ChannelVersion] = bits(ChannelTypes0.ChannelVersion.LENGTH_BITS).as[ChannelTypes0.ChannelVersion] - def localParamsCodec(channelVersion: ChannelVersion): Codec[LocalParams] = ( + def localParamsCodec(channelVersion: ChannelTypes0.ChannelVersion): Codec[LocalParams] = ( ("nodeId" | publicKey) :: ("channelPath" | keyPathCodec) :: ("dustLimit" | satoshi) :: @@ -74,7 +74,8 @@ private[channel] object ChannelCodecs1 { ("paymentBasepoint" | publicKey) :: ("delayedPaymentBasepoint" | publicKey) :: ("htlcBasepoint" | publicKey) :: - ("features" | combinedFeaturesCodec)).as[RemoteParams] + ("features" | combinedFeaturesCodec) :: + ("shutdownScript" | provide[Option[ByteVector]](None))).as[RemoteParams] def setCodec[T](codec: Codec[T]): Codec[Set[T]] = listOfN(uint16, codec).xmap(_.toSet, _.toList) @@ -127,10 +128,10 @@ private[channel] object ChannelCodecs1 { ("commitTx" | (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[CommitTx]) :: ("htlcTxsAndSigs" | listOfN(uint16, htlcTxAndSigsCodec))).as[PublishableTxs] - def localCommitCodec(remoteFundingPubKey: PublicKey): Codec[LocalCommit] = ( + val localCommitCodec: Codec[ChannelTypes0.LocalCommit] = ( ("index" | uint64overflow) :: ("spec" | commitmentSpecCodec) :: - ("publishableTxs" | publishableTxsCodec)).as[ChannelTypes0.LocalCommit].map(_.migrate(remoteFundingPubKey)).decodeOnly + ("publishableTxs" | publishableTxsCodec)).as[ChannelTypes0.LocalCommit].decodeOnly val remoteCommitCodec: Codec[RemoteCommit] = ( ("index" | uint64overflow) :: @@ -186,21 +187,20 @@ private[channel] object ChannelCodecs1 { val commitmentsCodec: Codec[Commitments] = ( ("channelVersion" | channelVersionCodec) >>:~ { channelVersion => ("localParams" | localParamsCodec(channelVersion)) :: - (("remoteParams" | remoteParamsCodec) >>:~ { remoteParams => - ("channelFlags" | byte) :: - ("localCommit" | localCommitCodec(remoteParams.fundingPubKey)) :: - ("remoteCommit" | remoteCommitCodec) :: - ("localChanges" | localChangesCodec) :: - ("remoteChanges" | remoteChangesCodec) :: - ("localNextHtlcId" | uint64overflow) :: - ("remoteNextHtlcId" | uint64overflow) :: - ("originChannels" | originsMapCodec) :: - ("remoteNextCommitInfo" | either(bool8, waitingForRevocationCodec, publicKey)) :: - ("commitInput" | inputInfoCodec) :: - ("remotePerCommitmentSecrets" | byteAligned(ShaChain.shaChainCodec)) :: - ("channelId" | bytes32) - }) - }).as[Commitments] + ("remoteParams" | remoteParamsCodec) :: + ("channelFlags" | byte) :: + ("localCommit" | localCommitCodec) :: + ("remoteCommit" | remoteCommitCodec) :: + ("localChanges" | localChangesCodec) :: + ("remoteChanges" | remoteChangesCodec) :: + ("localNextHtlcId" | uint64overflow) :: + ("remoteNextHtlcId" | uint64overflow) :: + ("originChannels" | originsMapCodec) :: + ("remoteNextCommitInfo" | either(bool8, waitingForRevocationCodec, publicKey)) :: + ("commitInput" | inputInfoCodec) :: + ("remotePerCommitmentSecrets" | byteAligned(ShaChain.shaChainCodec)) :: + ("channelId" | bytes32) + }).as[ChannelTypes0.Commitments].decodeOnly.map[Commitments](_.migrate()).decodeOnly val closingTxProposedCodec: Codec[ClosingTxProposed] = ( ("unsignedTx" | closingTxCodec) :: 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 bb152da04d..a73cf05661 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 @@ -16,7 +16,6 @@ package fr.acinq.eclair.wire.internal.channel.version2 -import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.DeterministicWallet.{ExtendedPrivateKey, KeyPath} import fr.acinq.bitcoin.{OutPoint, Transaction, TxOut} import fr.acinq.eclair.MilliSatoshi @@ -29,6 +28,7 @@ import fr.acinq.eclair.wire.internal.channel.version0.ChannelTypes0.{HtlcTxAndSi import fr.acinq.eclair.wire.protocol.CommonCodecs._ import fr.acinq.eclair.wire.protocol.LightningMessageCodecs._ import fr.acinq.eclair.wire.protocol.UpdateMessage +import scodec.bits.ByteVector import scodec.codecs._ import scodec.{Attempt, Codec} @@ -45,9 +45,9 @@ private[channel] object ChannelCodecs2 { ("path" | keyPathCodec) :: ("parent" | int64)).as[ExtendedPrivateKey] - val channelVersionCodec: Codec[ChannelVersion] = bits(ChannelVersion.LENGTH_BITS).as[ChannelVersion] + val channelVersionCodec: Codec[ChannelTypes0.ChannelVersion] = bits(ChannelTypes0.ChannelVersion.LENGTH_BITS).as[ChannelTypes0.ChannelVersion] - def localParamsCodec(channelVersion: ChannelVersion): Codec[LocalParams] = ( + def localParamsCodec(channelVersion: ChannelTypes0.ChannelVersion): Codec[LocalParams] = ( ("nodeId" | publicKey) :: ("channelPath" | keyPathCodec) :: ("dustLimit" | satoshi) :: @@ -74,7 +74,8 @@ private[channel] object ChannelCodecs2 { ("paymentBasepoint" | publicKey) :: ("delayedPaymentBasepoint" | publicKey) :: ("htlcBasepoint" | publicKey) :: - ("features" | combinedFeaturesCodec)).as[RemoteParams] + ("features" | combinedFeaturesCodec) :: + ("shutdownScript" | provide[Option[ByteVector]](None))).as[RemoteParams] def setCodec[T](codec: Codec[T]): Codec[Set[T]] = listOfN(uint16, codec).xmap(_.toSet, _.toList) @@ -162,10 +163,10 @@ private[channel] object ChannelCodecs2 { ("commitTx" | (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[CommitTx]) :: ("htlcTxsAndSigs" | listOfN(uint16, htlcTxAndSigsCodec))).as[PublishableTxs] - def localCommitCodec(remoteFundingPubKey: PublicKey): Codec[LocalCommit] = ( + val localCommitCodec: Codec[ChannelTypes0.LocalCommit] = ( ("index" | uint64overflow) :: ("spec" | commitmentSpecCodec) :: - ("publishableTxs" | publishableTxsCodec)).as[ChannelTypes0.LocalCommit].map(_.migrate(remoteFundingPubKey)).decodeOnly + ("publishableTxs" | publishableTxsCodec)).as[ChannelTypes0.LocalCommit].decodeOnly val remoteCommitCodec: Codec[RemoteCommit] = ( ("index" | uint64overflow) :: @@ -221,21 +222,20 @@ private[channel] object ChannelCodecs2 { val commitmentsCodec: Codec[Commitments] = ( ("channelVersion" | channelVersionCodec) >>:~ { channelVersion => ("localParams" | localParamsCodec(channelVersion)) :: - (("remoteParams" | remoteParamsCodec) >>:~ { remoteParams => - ("channelFlags" | byte) :: - ("localCommit" | localCommitCodec(remoteParams.fundingPubKey)) :: - ("remoteCommit" | remoteCommitCodec) :: - ("localChanges" | localChangesCodec) :: - ("remoteChanges" | remoteChangesCodec) :: - ("localNextHtlcId" | uint64overflow) :: - ("remoteNextHtlcId" | uint64overflow) :: - ("originChannels" | originsMapCodec) :: - ("remoteNextCommitInfo" | either(bool8, waitingForRevocationCodec, publicKey)) :: - ("commitInput" | inputInfoCodec) :: - ("remotePerCommitmentSecrets" | byteAligned(ShaChain.shaChainCodec)) :: - ("channelId" | bytes32) - }) - }).as[Commitments] + ("remoteParams" | remoteParamsCodec) :: + ("channelFlags" | byte) :: + ("localCommit" | localCommitCodec) :: + ("remoteCommit" | remoteCommitCodec) :: + ("localChanges" | localChangesCodec) :: + ("remoteChanges" | remoteChangesCodec) :: + ("localNextHtlcId" | uint64overflow) :: + ("remoteNextHtlcId" | uint64overflow) :: + ("originChannels" | originsMapCodec) :: + ("remoteNextCommitInfo" | either(bool8, waitingForRevocationCodec, publicKey)) :: + ("commitInput" | inputInfoCodec) :: + ("remotePerCommitmentSecrets" | byteAligned(ShaChain.shaChainCodec)) :: + ("channelId" | bytes32) + }).as[ChannelTypes0.Commitments].decodeOnly.map[Commitments](_.migrate()).decodeOnly val closingTxProposedCodec: Codec[ClosingTxProposed] = ( ("unsignedTx" | closingTxCodec) :: 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 155568ba10..0f92686c6d 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 @@ -18,7 +18,6 @@ package fr.acinq.eclair.wire.internal.channel.version3 import fr.acinq.bitcoin.DeterministicWallet.{ExtendedPrivateKey, KeyPath} import fr.acinq.bitcoin.{OutPoint, Transaction, TxOut} -import fr.acinq.eclair.MilliSatoshi import fr.acinq.eclair.channel._ import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.transactions.Transactions._ @@ -26,6 +25,8 @@ import fr.acinq.eclair.transactions.{CommitmentSpec, DirectedHtlc, IncomingHtlc, 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.{Features, MilliSatoshi} +import scodec.bits.{BitVector, ByteVector} import scodec.codecs._ import scodec.{Attempt, Codec} @@ -42,9 +43,26 @@ private[channel] object ChannelCodecs3 { ("path" | keyPathCodec) :: ("parent" | int64)).as[ExtendedPrivateKey] - val channelVersionCodec: Codec[ChannelVersion] = bits(ChannelVersion.LENGTH_BITS).as[ChannelVersion] - - def localParamsCodec(channelVersion: ChannelVersion): Codec[LocalParams] = ( + 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 + } + }) + + val channelFeaturesCodec: Codec[ChannelFeatures] = lengthDelimited(bytes).xmap(b => ChannelFeatures(Features(b)), cf => cf.features.toByteVector) + + def localParamsCodec(channelFeatures: ChannelFeatures): Codec[LocalParams] = ( ("nodeId" | publicKey) :: ("channelPath" | keyPathCodec) :: ("dustLimit" | satoshi) :: @@ -55,7 +73,7 @@ private[channel] object ChannelCodecs3 { ("maxAcceptedHtlcs" | uint16) :: ("isFunder" | bool8) :: ("defaultFinalScriptPubKey" | lengthDelimited(bytes)) :: - ("walletStaticPaymentBasepoint" | optional(provide(channelVersion.paysDirectlyToWallet), publicKey)) :: + ("walletStaticPaymentBasepoint" | optional(provide(channelFeatures.paysDirectlyToWallet), publicKey)) :: ("features" | combinedFeaturesCodec)).as[LocalParams] val remoteParamsCodec: Codec[RemoteParams] = ( @@ -71,7 +89,8 @@ private[channel] object ChannelCodecs3 { ("paymentBasepoint" | publicKey) :: ("delayedPaymentBasepoint" | publicKey) :: ("htlcBasepoint" | publicKey) :: - ("features" | combinedFeaturesCodec)).as[RemoteParams] + ("features" | combinedFeaturesCodec) :: + ("shutdownScript" | optional(bool8, bytes))).as[RemoteParams] def setCodec[T](codec: Codec[T]): Codec[Set[T]] = listOfN(uint16, codec).xmap(_.toSet, _.toList) @@ -216,22 +235,23 @@ private[channel] object ChannelCodecs3 { val spentMapCodec: Codec[Map[OutPoint, Transaction]] = mapCodec(outPointCodec, txCodec) val commitmentsCodec: Codec[Commitments] = ( - ("channelVersion" | channelVersionCodec) >>:~ { channelVersion => - ("localParams" | localParamsCodec(channelVersion)) :: - ("remoteParams" | remoteParamsCodec) :: - ("channelFlags" | byte) :: - ("localCommit" | localCommitCodec) :: - ("remoteCommit" | remoteCommitCodec) :: - ("localChanges" | localChangesCodec) :: - ("remoteChanges" | remoteChangesCodec) :: - ("localNextHtlcId" | uint64overflow) :: - ("remoteNextHtlcId" | uint64overflow) :: - ("originChannels" | originsMapCodec) :: - ("remoteNextCommitInfo" | either(bool8, waitingForRevocationCodec, publicKey)) :: - ("commitInput" | inputInfoCodec) :: - ("remotePerCommitmentSecrets" | byteAligned(ShaChain.shaChainCodec)) :: - ("channelId" | bytes32) - }).as[Commitments] + ("channelConfig" | channelConfigCodec) :: + (("channelFeatures" | channelFeaturesCodec) >>:~ { channelFeatures => + ("localParams" | localParamsCodec(channelFeatures)) :: + ("remoteParams" | remoteParamsCodec) :: + ("channelFlags" | byte) :: + ("localCommit" | localCommitCodec) :: + ("remoteCommit" | remoteCommitCodec) :: + ("localChanges" | localChangesCodec) :: + ("remoteChanges" | remoteChangesCodec) :: + ("localNextHtlcId" | uint64overflow) :: + ("remoteNextHtlcId" | uint64overflow) :: + ("originChannels" | originsMapCodec) :: + ("remoteNextCommitInfo" | either(bool8, waitingForRevocationCodec, publicKey)) :: + ("commitInput" | inputInfoCodec) :: + ("remotePerCommitmentSecrets" | byteAligned(ShaChain.shaChainCodec)) :: + ("channelId" | bytes32) + })).as[Commitments] val closingTxProposedCodec: Codec[ClosingTxProposed] = ( ("unsignedTx" | closingTxCodec) :: diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/FeaturesSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/FeaturesSpec.scala index 95d56f59a6..26c496db12 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/FeaturesSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/FeaturesSpec.scala @@ -329,16 +329,27 @@ class FeaturesSpec extends AnyFunSuite { } test("'knownFeatures' contains all our known features (reflection test)") { + import scala.reflect.ClassTag import scala.reflect.runtime.universe._ import scala.reflect.runtime.{universe => runtime} val mirror = runtime.runtimeMirror(ClassLoader.getSystemClassLoader) - val subclasses = typeOf[Feature].typeSymbol.asClass.knownDirectSubclasses - val knownFeatures = subclasses.map({ desc => - val mod = mirror.staticModule(desc.asClass.fullName) - mirror.reflectModule(mod).instance.asInstanceOf[Feature] - }) - assert((knownFeatures -- Features.knownFeatures).isEmpty) + def extract[T: TypeTag](container: T)(implicit c: ClassTag[T]): Set[Feature] = { + typeOf[T].decls.filter(_.isPublic).flatMap(symbol => { + if (symbol.isTerm && symbol.isModule) { + mirror.reflectModule(symbol.asModule).instance match { + case f: Feature => Some(f) + case _ => None + } + } else { + None + } + }).toSet + } + + val declaredFeatures = extract(Features) + assert(declaredFeatures.nonEmpty) + assert(declaredFeatures.removedAll(knownFeatures).isEmpty) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/FeeEstimatorSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/FeeEstimatorSpec.scala index 8f118fbf1c..82bcdcd197 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/FeeEstimatorSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/FeeEstimatorSpec.scala @@ -19,8 +19,8 @@ package fr.acinq.eclair.blockchain.fee import fr.acinq.bitcoin.SatoshiLong import fr.acinq.eclair.TestConstants.TestFeeEstimator import fr.acinq.eclair.blockchain.CurrentFeerates -import fr.acinq.eclair.channel.ChannelVersion -import fr.acinq.eclair.randomKey +import fr.acinq.eclair.channel.ChannelFeatures +import fr.acinq.eclair.{FeatureSupport, Features, randomKey} import org.scalatest.funsuite.AnyFunSuite class FeeEstimatorSpec extends AnyFunSuite { @@ -36,19 +36,19 @@ class FeeEstimatorSpec extends AnyFunSuite { test("get commitment feerate") { val feeEstimator = new TestFeeEstimator() - val channelVersion = ChannelVersion.STANDARD + val channelFeatures = ChannelFeatures(Features.empty) val feeConf = OnChainFeeConf(FeeTargets(1, 2, 1, 1), feeEstimator, closeOnOfflineMismatch = true, updateFeeMinDiffRatio = 0.1, FeerateTolerance(0.5, 2.0, FeeratePerKw(2500 sat)), Map.empty) feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(10000 sat)).copy(blocks_2 = FeeratePerKw(5000 sat))) - assert(feeConf.getCommitmentFeerate(randomKey().publicKey, channelVersion, 100000 sat, None) === FeeratePerKw(5000 sat)) + assert(feeConf.getCommitmentFeerate(randomKey().publicKey, channelFeatures, 100000 sat, None) === FeeratePerKw(5000 sat)) val currentFeerates = CurrentFeerates(FeeratesPerKw.single(FeeratePerKw(10000 sat)).copy(blocks_2 = FeeratePerKw(4000 sat))) - assert(feeConf.getCommitmentFeerate(randomKey().publicKey, channelVersion, 100000 sat, Some(currentFeerates)) === FeeratePerKw(4000 sat)) + assert(feeConf.getCommitmentFeerate(randomKey().publicKey, channelFeatures, 100000 sat, Some(currentFeerates)) === FeeratePerKw(4000 sat)) } test("get commitment feerate (anchor outputs)") { val feeEstimator = new TestFeeEstimator() - val channelVersion = ChannelVersion.ANCHOR_OUTPUTS + val channelFeatures = ChannelFeatures(Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory, Features.AnchorOutputs -> FeatureSupport.Mandatory)) val defaultNodeId = randomKey().publicKey val defaultMaxCommitFeerate = FeeratePerKw(2500 sat) val overrideNodeId = randomKey().publicKey @@ -56,23 +56,23 @@ class FeeEstimatorSpec extends AnyFunSuite { val feeConf = OnChainFeeConf(FeeTargets(1, 2, 1, 1), feeEstimator, closeOnOfflineMismatch = true, updateFeeMinDiffRatio = 0.1, FeerateTolerance(0.5, 2.0, defaultMaxCommitFeerate), Map(overrideNodeId -> FeerateTolerance(0.5, 2.0, overrideMaxCommitFeerate))) feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(10000 sat)).copy(blocks_2 = defaultMaxCommitFeerate / 2)) - assert(feeConf.getCommitmentFeerate(defaultNodeId, channelVersion, 100000 sat, None) === defaultMaxCommitFeerate / 2) + assert(feeConf.getCommitmentFeerate(defaultNodeId, channelFeatures, 100000 sat, None) === defaultMaxCommitFeerate / 2) feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(10000 sat)).copy(blocks_2 = defaultMaxCommitFeerate * 2)) - assert(feeConf.getCommitmentFeerate(defaultNodeId, channelVersion, 100000 sat, None) === defaultMaxCommitFeerate) - assert(feeConf.getCommitmentFeerate(overrideNodeId, channelVersion, 100000 sat, None) === overrideMaxCommitFeerate) + assert(feeConf.getCommitmentFeerate(defaultNodeId, channelFeatures, 100000 sat, None) === defaultMaxCommitFeerate) + assert(feeConf.getCommitmentFeerate(overrideNodeId, channelFeatures, 100000 sat, None) === overrideMaxCommitFeerate) val currentFeerates1 = CurrentFeerates(FeeratesPerKw.single(FeeratePerKw(10000 sat)).copy(blocks_2 = defaultMaxCommitFeerate / 2)) - assert(feeConf.getCommitmentFeerate(defaultNodeId, channelVersion, 100000 sat, Some(currentFeerates1)) === defaultMaxCommitFeerate / 2) + assert(feeConf.getCommitmentFeerate(defaultNodeId, channelFeatures, 100000 sat, Some(currentFeerates1)) === defaultMaxCommitFeerate / 2) val currentFeerates2 = CurrentFeerates(FeeratesPerKw.single(FeeratePerKw(10000 sat)).copy(blocks_2 = defaultMaxCommitFeerate * 1.5)) feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(10000 sat)).copy(blocks_2 = defaultMaxCommitFeerate / 2)) - assert(feeConf.getCommitmentFeerate(defaultNodeId, channelVersion, 100000 sat, Some(currentFeerates2)) === defaultMaxCommitFeerate) + assert(feeConf.getCommitmentFeerate(defaultNodeId, channelFeatures, 100000 sat, Some(currentFeerates2)) === defaultMaxCommitFeerate) } test("fee difference too high") { val tolerance = FeerateTolerance(ratioLow = 0.5, ratioHigh = 4.0, anchorOutputMaxCommitFeerate = FeeratePerKw(2500 sat)) - val channelVersion = ChannelVersion.STANDARD + val channelFeatures = ChannelFeatures(Features.empty) val testCases = Seq( (FeeratePerKw(500 sat), FeeratePerKw(500 sat), false), (FeeratePerKw(500 sat), FeeratePerKw(250 sat), false), @@ -85,13 +85,13 @@ class FeeEstimatorSpec extends AnyFunSuite { (FeeratePerKw(250 sat), FeeratePerKw(1500 sat), true), ) testCases.foreach { case (networkFeerate, proposedFeerate, expected) => - assert(tolerance.isFeeDiffTooHigh(channelVersion, networkFeerate, proposedFeerate) === expected) + assert(tolerance.isFeeDiffTooHigh(channelFeatures, networkFeerate, proposedFeerate) === expected) } } test("fee difference too high (anchor outputs)") { val tolerance = FeerateTolerance(ratioLow = 0.5, ratioHigh = 4.0, anchorOutputMaxCommitFeerate = FeeratePerKw(2500 sat)) - val channelVersion = ChannelVersion.ANCHOR_OUTPUTS + val channelFeatures = ChannelFeatures(Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory, Features.AnchorOutputs -> FeatureSupport.Mandatory)) val testCases = Seq( (FeeratePerKw(500 sat), FeeratePerKw(500 sat), false), (FeeratePerKw(500 sat), FeeratePerKw(2500 sat), false), @@ -106,7 +106,7 @@ class FeeEstimatorSpec extends AnyFunSuite { (FeeratePerKw(1000 sat), FeeratePerKw(499 sat), true), ) testCases.foreach { case (networkFeerate, proposedFeerate, expected) => - assert(tolerance.isFeeDiffTooHigh(channelVersion, networkFeerate, proposedFeerate) === expected) + assert(tolerance.isFeeDiffTooHigh(channelFeatures, networkFeerate, proposedFeerate) === expected) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelConfigSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelConfigSpec.scala new file mode 100644 index 0000000000..e712164962 --- /dev/null +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelConfigSpec.scala @@ -0,0 +1,30 @@ +/* + * Copyright 2021 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.channel + +import fr.acinq.eclair.channel.ChannelConfig._ +import org.scalatest.funsuite.AnyFunSuiteLike + +class ChannelConfigSpec extends AnyFunSuiteLike { + + test("channel key path based on funding public key") { + assert(!ChannelConfig(Set.empty[ChannelConfigOption]).hasOption(FundingPubKeyBasedChannelKeyPath)) + assert(ChannelConfig.standard.hasOption(FundingPubKeyBasedChannelKeyPath)) + assert(ChannelConfig(FundingPubKeyBasedChannelKeyPath).hasOption(FundingPubKeyBasedChannelKeyPath)) + } + +} diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelTypesSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelTypesSpec.scala index 8558ae5b9c..f70086783c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelTypesSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelTypesSpec.scala @@ -8,7 +8,7 @@ import fr.acinq.eclair.channel.states.StateTestsHelperMethods import fr.acinq.eclair.transactions.Transactions import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.wire.protocol.{CommitSig, RevokeAndAck, UpdateAddHtlc} -import fr.acinq.eclair.{MilliSatoshiLong, TestKitBaseClass} +import fr.acinq.eclair.{FeatureSupport, Features, MilliSatoshiLong, TestKitBaseClass} import org.scalatest.funsuite.AnyFunSuiteLike import scodec.bits.ByteVector @@ -16,43 +16,47 @@ class ChannelTypesSpec extends TestKitBaseClass with AnyFunSuiteLike with StateT implicit val log: akka.event.LoggingAdapter = akka.event.NoLogging - test("standard channel features include deterministic channel key path") { - assert(!ChannelVersion.ZEROES.hasPubkeyKeyPath) - assert(ChannelVersion.STANDARD.hasPubkeyKeyPath) - assert(ChannelVersion.STATIC_REMOTEKEY.hasStaticRemotekey) - assert(ChannelVersion.STATIC_REMOTEKEY.hasPubkeyKeyPath) + test("channel features determines commitment format") { + val standardChannel = ChannelFeatures(Features.empty) + val staticRemoteKeyChannel = ChannelFeatures(Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory)) + val anchorOutputsChannel = ChannelFeatures(Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory, Features.AnchorOutputs -> FeatureSupport.Mandatory)) + 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.commitmentFormat === Transactions.AnchorOutputsCommitmentFormat) + assert(!anchorOutputsChannel.paysDirectlyToWallet) } - test("anchor outputs includes static remote key") { - assert(ChannelVersion.ANCHOR_OUTPUTS.hasPubkeyKeyPath) - assert(ChannelVersion.ANCHOR_OUTPUTS.hasStaticRemotekey) - } - - test("channel version determines commitment format") { - assert(ChannelVersion.ZEROES.commitmentFormat === Transactions.DefaultCommitmentFormat) - assert(ChannelVersion.STANDARD.commitmentFormat === Transactions.DefaultCommitmentFormat) - assert(ChannelVersion.STATIC_REMOTEKEY.commitmentFormat === Transactions.DefaultCommitmentFormat) - assert(ChannelVersion.ANCHOR_OUTPUTS.commitmentFormat === Transactions.AnchorOutputsCommitmentFormat) - } - - test("pick channel version based on local and remote features") { + test("pick channel features based on local and remote features") { import fr.acinq.eclair.FeatureSupport._ import fr.acinq.eclair.Features import fr.acinq.eclair.Features._ - case class TestCase(localFeatures: Features, remoteFeatures: Features, expectedChannelVersion: ChannelVersion) + case class TestCase(localFeatures: Features, remoteFeatures: Features, expectedChannelFeatures: ChannelFeatures) val testCases = Seq( - TestCase(Features.empty, Features.empty, ChannelVersion.STANDARD), - TestCase(Features(StaticRemoteKey -> Optional), Features.empty, ChannelVersion.STANDARD), - TestCase(Features.empty, Features(StaticRemoteKey -> Optional), ChannelVersion.STANDARD), - TestCase(Features(StaticRemoteKey -> Optional), Features(StaticRemoteKey -> Optional), ChannelVersion.STATIC_REMOTEKEY), - TestCase(Features(StaticRemoteKey -> Optional), Features(StaticRemoteKey -> Mandatory), ChannelVersion.STATIC_REMOTEKEY), - TestCase(Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional), Features(StaticRemoteKey -> Optional), ChannelVersion.STATIC_REMOTEKEY), - TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional), ChannelVersion.ANCHOR_OUTPUTS) + TestCase(Features.empty, Features.empty, ChannelFeatures(Features.empty)), + TestCase(Features(StaticRemoteKey -> Optional), Features.empty, ChannelFeatures(Features.empty)), + TestCase(Features.empty, Features(StaticRemoteKey -> Optional), ChannelFeatures(Features.empty)), + TestCase(Features.empty, Features(StaticRemoteKey -> Mandatory), ChannelFeatures(Features.empty)), + TestCase(Features(StaticRemoteKey -> Optional, Wumbo -> Mandatory), Features(Wumbo -> Mandatory), ChannelFeatures(Features(Wumbo -> Mandatory))), + TestCase(Features(StaticRemoteKey -> Optional), Features(StaticRemoteKey -> Optional), ChannelFeatures(Features(StaticRemoteKey -> Mandatory))), + TestCase(Features(StaticRemoteKey -> Optional), Features(StaticRemoteKey -> Mandatory), ChannelFeatures(Features(StaticRemoteKey -> Mandatory))), + TestCase(Features(StaticRemoteKey -> Optional, Wumbo -> Optional), Features(StaticRemoteKey -> Mandatory, Wumbo -> Mandatory), ChannelFeatures(Features(StaticRemoteKey -> Mandatory, Wumbo -> Mandatory))), + TestCase(Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional), Features(StaticRemoteKey -> Optional), ChannelFeatures(Features(StaticRemoteKey -> Mandatory))), + TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional), ChannelFeatures(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Mandatory))) ) for (testCase <- testCases) { - assert(ChannelVersion.pickChannelVersion(testCase.localFeatures, testCase.remoteFeatures) === testCase.expectedChannelVersion) + assert(ChannelFeatures.pickChannelFeatures(testCase.localFeatures, testCase.remoteFeatures) === testCase.expectedChannelFeatures) } } 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 8c5e294216..2a62becd59 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 @@ -466,10 +466,11 @@ object CommitmentsSpec { def makeCommitments(toLocal: MilliSatoshi, toRemote: MilliSatoshi, feeRatePerKw: FeeratePerKw = FeeratePerKw(0 sat), dustLimit: Satoshi = 0 sat, isFunder: Boolean = true, announceChannel: Boolean = true): Commitments = { val localParams = LocalParams(randomKey().publicKey, DeterministicWallet.KeyPath(Seq(42L)), dustLimit, UInt64.MaxValue, 0 sat, 1 msat, CltvExpiryDelta(144), 50, isFunder, ByteVector.empty, None, Features.empty) - val remoteParams = RemoteParams(randomKey().publicKey, dustLimit, UInt64.MaxValue, 0 sat, 1 msat, CltvExpiryDelta(144), 50, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, Features.empty) + val remoteParams = RemoteParams(randomKey().publicKey, dustLimit, UInt64.MaxValue, 0 sat, 1 msat, CltvExpiryDelta(144), 50, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, Features.empty, None) val commitmentInput = Funding.makeFundingInputInfo(randomBytes32(), 0, (toLocal + toRemote).truncateToSatoshi, randomKey().publicKey, remoteParams.fundingPubKey) Commitments( - ChannelVersion.STANDARD, + ChannelConfig.standard, + ChannelFeatures(Features.empty), localParams, remoteParams, channelFlags = if (announceChannel) ChannelFlags.AnnounceChannel else ChannelFlags.Empty, @@ -488,10 +489,11 @@ object CommitmentsSpec { def makeCommitments(toLocal: MilliSatoshi, toRemote: MilliSatoshi, localNodeId: PublicKey, remoteNodeId: PublicKey, announceChannel: Boolean): Commitments = { val localParams = LocalParams(localNodeId, DeterministicWallet.KeyPath(Seq(42L)), 0 sat, UInt64.MaxValue, 0 sat, 1 msat, CltvExpiryDelta(144), 50, isFunder = true, ByteVector.empty, None, Features.empty) - val remoteParams = RemoteParams(remoteNodeId, 0 sat, UInt64.MaxValue, 0 sat, 1 msat, CltvExpiryDelta(144), 50, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, Features.empty) + val remoteParams = RemoteParams(remoteNodeId, 0 sat, UInt64.MaxValue, 0 sat, 1 msat, CltvExpiryDelta(144), 50, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, Features.empty, None) val commitmentInput = Funding.makeFundingInputInfo(randomBytes32(), 0, (toLocal + toRemote).truncateToSatoshi, randomKey().publicKey, remoteParams.fundingPubKey) Commitments( - ChannelVersion.STANDARD, + ChannelConfig.standard, + ChannelFeatures(Features.empty), localParams, remoteParams, channelFlags = if (announceChannel) ChannelFlags.AnnounceChannel else ChannelFlags.Empty, 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 bd09a38827..89c0e72cb5 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 @@ -79,9 +79,9 @@ class FuzzySpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with StateT registerA ! alice registerB ! bob // no announcements - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, None, Alice.channelParams, pipe, bobInit, channelFlags = 0x00.toByte, ChannelVersion.STANDARD) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, None, Alice.channelParams, pipe, bobInit, channelFlags = 0x00.toByte, ChannelConfig.standard, ChannelFeatures(Features.empty)) alice2blockchain.expectMsgType[TxPublisher.SetChannelId] - bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams, pipe, aliceInit, ChannelVersion.STANDARD) + bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams, pipe, aliceInit, ChannelConfig.standard, ChannelFeatures(Features.empty)) bob2blockchain.expectMsgType[TxPublisher.SetChannelId] pipe ! (alice, bob) alice2blockchain.expectMsgType[TxPublisher.SetChannelId] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala index 2ab1775687..3780fbdde0 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala @@ -123,19 +123,22 @@ trait StateTestsHelperMethods extends TestKitBase { import com.softwaremill.quicklens._ import setup._ - val channelVersion = List( - ChannelVersion.STANDARD, - if (tags.contains(StateTestsTags.AnchorOutputs)) ChannelVersion.ANCHOR_OUTPUTS else ChannelVersion.ZEROES, - if (tags.contains(StateTestsTags.StaticRemoteKey)) ChannelVersion.STATIC_REMOTEKEY else ChannelVersion.ZEROES, - ).reduce(_ | _) + val channelConfig = ChannelConfig.standard + val channelFeatures = if (tags.contains(StateTestsTags.AnchorOutputs)) { + ChannelFeatures(Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory, Features.AnchorOutputs -> FeatureSupport.Mandatory)) + } else if (tags.contains(StateTestsTags.StaticRemoteKey)) { + ChannelFeatures(Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory)) + } else { + ChannelFeatures(Features.empty) + } val channelFlags = if (tags.contains(StateTestsTags.ChannelsPublic)) ChannelFlags.AnnounceChannel else ChannelFlags.Empty val aliceParams = setChannelFeatures(Alice.channelParams, tags) - .modify(_.walletStaticPaymentBasepoint).setToIf(channelVersion.paysDirectlyToWallet)(Some(Helpers.getWalletPaymentBasepoint(wallet))) + .modify(_.walletStaticPaymentBasepoint).setToIf(channelFeatures.paysDirectlyToWallet)(Some(Helpers.getWalletPaymentBasepoint(wallet))) .modify(_.maxHtlcValueInFlightMsat).setToIf(tags.contains(StateTestsTags.NoMaxHtlcValueInFlight))(UInt64.MaxValue) .modify(_.maxHtlcValueInFlightMsat).setToIf(tags.contains(StateTestsTags.AliceLowMaxHtlcValueInFlight))(UInt64(150000000)) val bobParams = setChannelFeatures(Bob.channelParams, tags) - .modify(_.walletStaticPaymentBasepoint).setToIf(channelVersion.paysDirectlyToWallet)(Some(Helpers.getWalletPaymentBasepoint(wallet))) + .modify(_.walletStaticPaymentBasepoint).setToIf(channelFeatures.paysDirectlyToWallet)(Some(Helpers.getWalletPaymentBasepoint(wallet))) .modify(_.maxHtlcValueInFlightMsat).setToIf(tags.contains(StateTestsTags.NoMaxHtlcValueInFlight))(UInt64.MaxValue) val initialFeeratePerKw = if (tags.contains(StateTestsTags.AnchorOutputs)) TestConstants.anchorOutputsFeeratePerKw else TestConstants.feeratePerKw val (fundingSatoshis, pushMsat) = if (tags.contains(StateTestsTags.NoPushMsat)) { @@ -146,9 +149,9 @@ trait StateTestsHelperMethods extends TestKitBase { val aliceInit = Init(aliceParams.features) val bobInit = Init(bobParams.features) - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, fundingSatoshis, pushMsat, initialFeeratePerKw, TestConstants.feeratePerKw, None, aliceParams, alice2bob.ref, bobInit, channelFlags, channelVersion) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, fundingSatoshis, pushMsat, initialFeeratePerKw, TestConstants.feeratePerKw, None, aliceParams, alice2bob.ref, bobInit, channelFlags, channelConfig, channelFeatures) assert(alice2blockchain.expectMsgType[TxPublisher.SetChannelId].channelId === ByteVector32.Zeroes) - bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, bobParams, bob2alice.ref, aliceInit, channelVersion) + bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, bobParams, bob2alice.ref, aliceInit, channelConfig, channelFeatures) assert(bob2blockchain.expectMsgType[TxPublisher.SetChannelId].channelId === ByteVector32.Zeroes) alice2bob.expectMsgType[OpenChannel] alice2bob.forward(bob) 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 03adaece48..f89b476f67 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 @@ -25,7 +25,7 @@ import fr.acinq.eclair.channel.Channel.TickChannelOpenTimeout import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.states.{StateTestsBase, StateTestsTags} import fr.acinq.eclair.wire.protocol.{AcceptChannel, ChannelTlv, Error, Init, OpenChannel, TlvStream} -import fr.acinq.eclair.{CltvExpiryDelta, TestConstants, TestKitBaseClass} +import fr.acinq.eclair.{CltvExpiryDelta, Features, TestConstants, TestKitBaseClass} import org.scalatest.funsuite.FixtureAnyFunSuiteLike import org.scalatest.{Outcome, Tag} import scodec.bits.ByteVector @@ -61,13 +61,14 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS val setup = init(aliceNodeParams, bobNodeParams, wallet = noopWallet) import setup._ - val channelVersion = ChannelVersion.STANDARD + val channelConfig = ChannelConfig.standard + val channelFeatures = ChannelFeatures(Features.empty) val aliceInit = Init(aliceParams.features) val bobInit = Init(bobParams.features) within(30 seconds) { val fundingAmount = if (test.tags.contains(StateTestsTags.Wumbo)) Btc(5).toSatoshi else TestConstants.fundingSatoshis - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, fundingAmount, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, None, aliceParams, alice2bob.ref, bobInit, ChannelFlags.Empty, channelVersion) - bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, bobParams, bob2alice.ref, aliceInit, channelVersion) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, fundingAmount, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, None, aliceParams, alice2bob.ref, bobInit, ChannelFlags.Empty, channelConfig, channelFeatures) + bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, bobParams, bob2alice.ref, aliceInit, channelConfig, channelFeatures) alice2bob.expectMsgType[OpenChannel] alice2bob.forward(bob) awaitCond(alice.stateName == WAIT_FOR_ACCEPT_CHANNEL) 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 6f865d27b2..7f1e597838 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 @@ -23,7 +23,7 @@ import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.states.{StateTestsBase, StateTestsTags} import fr.acinq.eclair.wire.protocol.{AcceptChannel, ChannelTlv, Error, Init, OpenChannel, TlvStream} -import fr.acinq.eclair.{CltvExpiryDelta, MilliSatoshiLong, TestConstants, TestKitBaseClass, ToMilliSatoshiConversion} +import fr.acinq.eclair.{CltvExpiryDelta, Features, MilliSatoshiLong, TestConstants, TestKitBaseClass, ToMilliSatoshiConversion} import org.scalatest.funsuite.FixtureAnyFunSuiteLike import org.scalatest.{Outcome, Tag} import scodec.bits.ByteVector @@ -48,12 +48,13 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui val setup = init(nodeParamsB = bobNodeParams) import setup._ - val channelVersion = ChannelVersion.STANDARD + val channelConfig = ChannelConfig.standard + val channelFeatures = ChannelFeatures(Features.empty) val aliceInit = Init(aliceParams.features) val bobInit = Init(bobParams.features) within(30 seconds) { - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, None, aliceParams, alice2bob.ref, bobInit, ChannelFlags.Empty, channelVersion) - bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, bobParams, bob2alice.ref, aliceInit, channelVersion) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, None, aliceParams, alice2bob.ref, bobInit, ChannelFlags.Empty, channelConfig, channelFeatures) + bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, bobParams, bob2alice.ref, aliceInit, channelConfig, channelFeatures) awaitCond(bob.stateName == WAIT_FOR_OPEN_CHANNEL) withFixture(test.toNoArgTest(FixtureParam(bob, alice2bob, bob2alice, bob2blockchain))) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala index 75887ff558..f2d66a3c37 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala @@ -24,7 +24,7 @@ import fr.acinq.eclair.blockchain.{MakeFundingTxResponse, TestWallet} import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.states.StateTestsBase import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{TestConstants, TestKitBaseClass} +import fr.acinq.eclair.{Features, TestConstants, TestKitBaseClass} import org.scalatest.Outcome import org.scalatest.funsuite.FixtureAnyFunSuiteLike import scodec.bits.ByteVector @@ -46,12 +46,13 @@ class WaitForFundingCreatedInternalStateSpec extends TestKitBaseClass with Fixtu } val setup = init(wallet = noopWallet) import setup._ - val channelVersion = ChannelVersion.STANDARD + val channelConfig = ChannelConfig.standard + val channelFeatures = ChannelFeatures(Features.empty) val aliceInit = Init(Alice.channelParams.features) val bobInit = Init(Bob.channelParams.features) within(30 seconds) { - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, None, Alice.channelParams, alice2bob.ref, bobInit, ChannelFlags.Empty, channelVersion) - bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams, bob2alice.ref, aliceInit, channelVersion) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, None, Alice.channelParams, alice2bob.ref, bobInit, ChannelFlags.Empty, channelConfig, channelFeatures) + bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams, bob2alice.ref, aliceInit, channelConfig, channelFeatures) alice2bob.expectMsgType[OpenChannel] alice2bob.forward(bob) bob2alice.expectMsgType[AcceptChannel] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala index 84b2882892..9e958b0e3b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala @@ -26,7 +26,7 @@ import fr.acinq.eclair.channel.publish.TxPublisher import fr.acinq.eclair.channel.states.{StateTestsBase, StateTestsTags} import fr.acinq.eclair.transactions.Transactions import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{TestConstants, TestKitBaseClass, ToMilliSatoshiConversion} +import fr.acinq.eclair.{Features, TestConstants, TestKitBaseClass, ToMilliSatoshiConversion} import org.scalatest.funsuite.FixtureAnyFunSuiteLike import org.scalatest.{Outcome, Tag} @@ -58,13 +58,14 @@ class WaitForFundingCreatedStateSpec extends TestKitBaseClass with FixtureAnyFun val setup = init(aliceNodeParams, bobNodeParams) import setup._ - val channelVersion = ChannelVersion.STANDARD + val channelConfig = ChannelConfig.standard + val channelFeatures = ChannelFeatures(Features.empty) val aliceInit = Init(aliceParams.features) val bobInit = Init(bobParams.features) within(30 seconds) { - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, fundingSatoshis, pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, None, aliceParams, alice2bob.ref, bobInit, ChannelFlags.Empty, channelVersion) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, fundingSatoshis, pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, None, aliceParams, alice2bob.ref, bobInit, ChannelFlags.Empty, channelConfig, channelFeatures) alice2blockchain.expectMsgType[TxPublisher.SetChannelId] - bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, bobParams, bob2alice.ref, aliceInit, channelVersion) + bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, bobParams, bob2alice.ref, aliceInit, channelConfig, channelFeatures) bob2blockchain.expectMsgType[TxPublisher.SetChannelId] alice2bob.expectMsgType[OpenChannel] alice2bob.forward(bob) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala index 6b7a0e96ef..028e17200c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala @@ -26,7 +26,7 @@ import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.publish.TxPublisher import fr.acinq.eclair.channel.states.{StateTestsBase, StateTestsTags} import fr.acinq.eclair.wire.protocol.{AcceptChannel, Error, FundingCreated, FundingSigned, Init, OpenChannel} -import fr.acinq.eclair.{TestConstants, TestKitBaseClass} +import fr.acinq.eclair.{Features, TestConstants, TestKitBaseClass} import org.scalatest.funsuite.FixtureAnyFunSuiteLike import org.scalatest.{Outcome, Tag} @@ -56,13 +56,14 @@ class WaitForFundingSignedStateSpec extends TestKitBaseClass with FixtureAnyFunS val setup = init(aliceNodeParams, bobNodeParams) import setup._ - val channelVersion = ChannelVersion.STANDARD + val channelConfig = ChannelConfig.standard + val channelFeatures = ChannelFeatures(Features.empty) val aliceInit = Init(aliceParams.features) val bobInit = Init(bobParams.features) within(30 seconds) { - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, fundingSatoshis, pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, None, aliceParams, alice2bob.ref, bobInit, ChannelFlags.Empty, channelVersion) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, fundingSatoshis, pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, None, aliceParams, alice2bob.ref, bobInit, ChannelFlags.Empty, channelConfig, channelFeatures) alice2blockchain.expectMsgType[TxPublisher.SetChannelId] - bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, bobParams, bob2alice.ref, aliceInit, channelVersion) + bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, bobParams, bob2alice.ref, aliceInit, channelConfig, channelFeatures) bob2blockchain.expectMsgType[TxPublisher.SetChannelId] alice2bob.expectMsgType[OpenChannel] alice2bob.forward(bob) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala index f96300bd3f..4569b05373 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala @@ -27,7 +27,7 @@ import fr.acinq.eclair.channel.publish.TxPublisher import fr.acinq.eclair.channel.states.StateTestsBase import fr.acinq.eclair.transactions.Scripts.multiSig2of2 import fr.acinq.eclair.wire.protocol.{AcceptChannel, Error, FundingCreated, FundingLocked, FundingSigned, Init, OpenChannel} -import fr.acinq.eclair.{TestConstants, TestKitBaseClass, randomKey} +import fr.acinq.eclair.{Features, TestConstants, TestKitBaseClass, randomKey} import org.scalatest.Outcome import org.scalatest.funsuite.FixtureAnyFunSuiteLike @@ -44,13 +44,14 @@ class WaitForFundingConfirmedStateSpec extends TestKitBaseClass with FixtureAnyF override def withFixture(test: OneArgTest): Outcome = { val setup = init() import setup._ - val channelVersion = ChannelVersion.STANDARD + val channelConfig = ChannelConfig.standard + val channelFeatures = ChannelFeatures(Features.empty) val aliceInit = Init(Alice.channelParams.features) val bobInit = Init(Bob.channelParams.features) within(30 seconds) { - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, None, Alice.channelParams, alice2bob.ref, bobInit, ChannelFlags.Empty, channelVersion) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, None, Alice.channelParams, alice2bob.ref, bobInit, ChannelFlags.Empty, channelConfig, channelFeatures) alice2blockchain.expectMsgType[TxPublisher.SetChannelId] - bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams, bob2alice.ref, aliceInit, channelVersion) + bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams, bob2alice.ref, aliceInit, channelConfig, channelFeatures) bob2blockchain.expectMsgType[TxPublisher.SetChannelId] alice2bob.expectMsgType[OpenChannel] alice2bob.forward(bob) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingLockedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingLockedStateSpec.scala index c6832a9bf1..1886b672b6 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingLockedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingLockedStateSpec.scala @@ -24,7 +24,7 @@ import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.publish.TxPublisher import fr.acinq.eclair.channel.states.StateTestsBase import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{MilliSatoshiLong, TestConstants, TestKitBaseClass} +import fr.acinq.eclair.{Features, MilliSatoshiLong, TestConstants, TestKitBaseClass} import org.scalatest.Outcome import org.scalatest.funsuite.FixtureAnyFunSuiteLike @@ -43,13 +43,14 @@ class WaitForFundingLockedStateSpec extends TestKitBaseClass with FixtureAnyFunS override def withFixture(test: OneArgTest): Outcome = { val setup = init() import setup._ - val channelVersion = ChannelVersion.STANDARD + val channelConfig = ChannelConfig.standard + val channelFeatures = ChannelFeatures(Features.empty) val aliceInit = Init(Alice.channelParams.features) val bobInit = Init(Bob.channelParams.features) within(30 seconds) { - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, Some(initialRelayFees), Alice.channelParams, alice2bob.ref, bobInit, ChannelFlags.Empty, channelVersion) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, Some(initialRelayFees), Alice.channelParams, alice2bob.ref, bobInit, ChannelFlags.Empty, channelConfig, channelFeatures) alice2blockchain.expectMsgType[TxPublisher.SetChannelId] - bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams, bob2alice.ref, aliceInit, channelVersion) + bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams, bob2alice.ref, aliceInit, channelConfig, channelFeatures) bob2blockchain.expectMsgType[TxPublisher.SetChannelId] alice2bob.expectMsgType[OpenChannel] alice2bob.forward(bob) 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 edc0fdf18d..dcea237c4a 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 @@ -216,7 +216,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.copy(channelVersion = ChannelVersion.STANDARD).availableBalanceForSend) + assert(initialState.commitments.availableBalanceForSend < initialState.commitments.copy(channelFeatures = ChannelFeatures(Features.empty)).availableBalanceForSend) val add = CMD_ADD_HTLC(sender.ref, initialState.commitments.availableBalanceForSend + 1.msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, localOrigin(sender.ref)) alice ! add diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala index 91bf298f48..fb42df2ab8 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala @@ -729,12 +729,12 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val aliceCommitments = alice.stateData.asInstanceOf[HasCommitments].commitments val aliceCurrentPerCommitmentPoint = TestConstants.Alice.channelKeyManager.commitmentPoint( - TestConstants.Alice.channelKeyManager.keyPath(aliceCommitments.localParams, aliceCommitments.channelVersion), + TestConstants.Alice.channelKeyManager.keyPath(aliceCommitments.localParams, aliceCommitments.channelConfig), aliceCommitments.localCommit.index) val bobCommitments = bob.stateData.asInstanceOf[HasCommitments].commitments val bobCurrentPerCommitmentPoint = TestConstants.Bob.channelKeyManager.commitmentPoint( - TestConstants.Bob.channelKeyManager.keyPath(bobCommitments.localParams, bobCommitments.channelVersion), + TestConstants.Bob.channelKeyManager.keyPath(bobCommitments.localParams, bobCommitments.channelConfig), bobCommitments.localCommit.index) (aliceCurrentPerCommitmentPoint, bobCurrentPerCommitmentPoint) 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 26688cf268..98f16c1811 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 @@ -29,10 +29,10 @@ import fr.acinq.eclair.channel.publish.TxPublisher.{PublishRawTx, PublishTx, Set import fr.acinq.eclair.channel.states.{StateTestsBase, StateTestsTags} import fr.acinq.eclair.payment._ import fr.acinq.eclair.payment.relay.Relayer._ -import fr.acinq.eclair.transactions.Transactions.{AnchorOutputsCommitmentFormat, HtlcSuccessTx, HtlcTimeoutTx, TxOwner} +import fr.acinq.eclair.transactions.Transactions.{AnchorOutputsCommitmentFormat, HtlcSuccessTx, HtlcTimeoutTx} import fr.acinq.eclair.transactions.{Scripts, Transactions} import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{CltvExpiry, MilliSatoshiLong, TestConstants, TestKitBaseClass, randomBytes32, randomKey} +import fr.acinq.eclair.{CltvExpiry, FeatureSupport, Features, MilliSatoshiLong, TestConstants, TestKitBaseClass, randomBytes32, randomKey} import org.scalatest.funsuite.FixtureAnyFunSuiteLike import org.scalatest.{Outcome, Tag} import scodec.bits.ByteVector @@ -64,12 +64,13 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with if (unconfirmedFundingTx) { within(30 seconds) { - val channelVersion = ChannelVersion.STANDARD + val channelConfig = ChannelConfig.standard + val channelFeatures = ChannelFeatures(Features.empty) val aliceInit = Init(Alice.channelParams.features) val bobInit = Init(Bob.channelParams.features) - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, None, Alice.channelParams, alice2bob.ref, bobInit, ChannelFlags.Empty, channelVersion) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, None, Alice.channelParams, alice2bob.ref, bobInit, ChannelFlags.Empty, channelConfig, channelFeatures) alice2blockchain.expectMsgType[SetChannelId] - bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams, bob2alice.ref, aliceInit, channelVersion) + bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams, bob2alice.ref, aliceInit, channelConfig, channelFeatures) bob2blockchain.expectMsgType[SetChannelId] alice2bob.expectMsgType[OpenChannel] alice2bob.forward(bob) @@ -284,10 +285,10 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // NB: nominal case is tested in IntegrationSpec } - def testMutualCloseBeforeConverge(f: FixtureParam, channelVersion: ChannelVersion): Unit = { + def testMutualCloseBeforeConverge(f: FixtureParam, channelFeatures: ChannelFeatures): Unit = { import f._ val sender = TestProbe() - assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.channelVersion === channelVersion) + assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.channelFeatures === channelFeatures) // alice initiates a closing alice ! CMD_CLOSE(sender.ref, None) alice2bob.expectMsgType[Shutdown] @@ -316,11 +317,11 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } test("recv WatchFundingSpentTriggered (mutual close before converging)") { f => - testMutualCloseBeforeConverge(f, ChannelVersion.STANDARD) + testMutualCloseBeforeConverge(f, ChannelFeatures(Features.empty)) } test("recv WatchFundingSpentTriggered (mutual close before converging, anchor outputs)", Tag(StateTestsTags.AnchorOutputs)) { f => - testMutualCloseBeforeConverge(f, ChannelVersion.ANCHOR_OUTPUTS) + testMutualCloseBeforeConverge(f, ChannelFeatures(Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory, Features.AnchorOutputs -> FeatureSupport.Mandatory))) } test("recv WatchTxConfirmedTriggered (mutual close)") { f => @@ -377,10 +378,10 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(alice.stateData == initialState) // this was a no-op } - def testLocalCommitTxConfirmed(f: FixtureParam, channelVersion: ChannelVersion): Unit = { + def testLocalCommitTxConfirmed(f: FixtureParam, channelFeatures: ChannelFeatures): Unit = { import f._ - assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.channelVersion === channelVersion) + assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.channelFeatures === channelFeatures) val listener = TestProbe() system.eventStream.subscribe(listener.ref, classOf[LocalCommitConfirmed]) @@ -423,11 +424,11 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } test("recv WatchTxConfirmedTriggered (local commit)") { f => - testLocalCommitTxConfirmed(f, ChannelVersion.STANDARD) + testLocalCommitTxConfirmed(f, ChannelFeatures(Features.empty)) } test("recv WatchTxConfirmedTriggered (local commit, anchor outputs)", Tag(StateTestsTags.AnchorOutputs)) { f => - testLocalCommitTxConfirmed(f, ChannelVersion.ANCHOR_OUTPUTS) + testLocalCommitTxConfirmed(f, ChannelFeatures(Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory, Features.AnchorOutputs -> FeatureSupport.Mandatory))) } test("recv WatchTxConfirmedTriggered (local commit with multiple htlcs for the same payment)") { f => @@ -656,7 +657,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.channelVersion === ChannelVersion.STANDARD) + assert(initialState.commitments.channelFeatures === ChannelFeatures(Features.empty)) // bob publishes his last current commit tx, the one it had when entering NEGOTIATING state val bobCommitTx = bobCommitTxs.last.commitTx.tx assert(bobCommitTx.txOut.size == 2) // two main outputs @@ -674,7 +675,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv WatchTxConfirmedTriggered (remote commit, option_static_remotekey)", Tag(StateTestsTags.StaticRemoteKey)) { f => import f._ mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain) - assert(alice.stateData.asInstanceOf[DATA_CLOSING].commitments.channelVersion === ChannelVersion.STATIC_REMOTEKEY) + assert(alice.stateData.asInstanceOf[DATA_CLOSING].commitments.channelFeatures === ChannelFeatures(Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory))) // bob publishes his last current commit tx, the one it had when entering NEGOTIATING state val bobCommitTx = bobCommitTxs.last.commitTx.tx assert(bobCommitTx.txOut.size == 2) // two main outputs @@ -692,7 +693,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.channelVersion === ChannelVersion.ANCHOR_OUTPUTS) + assert(initialState.commitments.channelFeatures === ChannelFeatures(Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory, Features.AnchorOutputs -> FeatureSupport.Mandatory))) // bob publishes his last current commit tx, the one it had when entering NEGOTIATING state val bobCommitTx = bobCommitTxs.last.commitTx.tx assert(bobCommitTx.txOut.size == 4) // two main outputs + two anchors @@ -707,10 +708,10 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(alice.stateName == CLOSED) } - def testRemoteCommitTxWithHtlcsConfirmed(f: FixtureParam, channelVersion: ChannelVersion): Unit = { + def testRemoteCommitTxWithHtlcsConfirmed(f: FixtureParam, channelFeatures: ChannelFeatures): Unit = { import f._ - assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.channelVersion === channelVersion) + assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.channelFeatures === channelFeatures) // alice sends a first htlc to bob val (ra1, htlca1) = addHtlc(15000000 msat, alice, bob, alice2bob, bob2alice) @@ -723,7 +724,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // Bob publishes the latest commit tx. val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.commitTxAndRemoteSig.commitTx.tx - if (channelVersion.hasAnchorOutputs) { + if (channelFeatures.hasFeature(Features.AnchorOutputs)) { assert(bobCommitTx.txOut.length === 7) // two main outputs + two anchors + 3 HTLCs } else { assert(bobCommitTx.txOut.length === 5) // two main outputs + 3 HTLCs @@ -750,11 +751,11 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } test("recv WatchTxConfirmedTriggered (remote commit with multiple htlcs for the same payment)") { f => - testRemoteCommitTxWithHtlcsConfirmed(f, ChannelVersion.STANDARD) + testRemoteCommitTxWithHtlcsConfirmed(f, ChannelFeatures(Features.empty)) } test("recv WatchTxConfirmedTriggered (remote commit with multiple htlcs for the same payment, anchor outputs)", Tag(StateTestsTags.AnchorOutputs)) { f => - testRemoteCommitTxWithHtlcsConfirmed(f, ChannelVersion.ANCHOR_OUTPUTS) + testRemoteCommitTxWithHtlcsConfirmed(f, ChannelFeatures(Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory, Features.AnchorOutputs -> FeatureSupport.Mandatory))) } test("recv WatchTxConfirmedTriggered (remote commit) followed by CMD_FULFILL_HTLC") { f => @@ -829,10 +830,10 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(alice2blockchain.expectMsgType[WatchOutputSpent].outputIndex === htlcTimeoutTx.input.outPoint.index) } - private def testNextRemoteCommitTxConfirmed(f: FixtureParam, channelVersion: ChannelVersion): (Transaction, RemoteCommitPublished, Set[UpdateAddHtlc]) = { + private def testNextRemoteCommitTxConfirmed(f: FixtureParam, channelFeatures: ChannelFeatures): (Transaction, RemoteCommitPublished, Set[UpdateAddHtlc]) = { import f._ - assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.channelVersion === channelVersion) + assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.channelFeatures === channelFeatures) // alice sends a first htlc to bob val (ra1, htlca1) = addHtlc(15000000 msat, alice, bob, alice2bob, bob2alice) @@ -851,7 +852,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // Bob publishes the next commit tx. val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.commitTxAndRemoteSig.commitTx.tx - if (channelVersion.hasAnchorOutputs) { + if (channelFeatures.hasFeature(Features.AnchorOutputs)) { assert(bobCommitTx.txOut.length === 7) // two main outputs + two anchors + 3 HTLCs } else { assert(bobCommitTx.txOut.length === 5) // two main outputs + 3 HTLCs @@ -863,7 +864,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv WatchTxConfirmedTriggered (next remote commit)") { f => import f._ - val (bobCommitTx, closingState, htlcs) = testNextRemoteCommitTxConfirmed(f, ChannelVersion.STANDARD) + val (bobCommitTx, closingState, htlcs) = testNextRemoteCommitTxConfirmed(f, ChannelFeatures(Features.empty)) val claimHtlcTimeoutTxs = getClaimHtlcTimeoutTxs(closingState).map(_.tx) alice ! WatchTxConfirmedTriggered(42, 0, bobCommitTx) alice ! WatchTxConfirmedTriggered(45, 0, closingState.claimMainOutputTx.get.tx) @@ -883,7 +884,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv WatchTxConfirmedTriggered (next remote commit, static_remotekey)", Tag(StateTestsTags.StaticRemoteKey)) { f => import f._ - val (bobCommitTx, closingState, htlcs) = testNextRemoteCommitTxConfirmed(f, ChannelVersion.STATIC_REMOTEKEY) + val (bobCommitTx, closingState, htlcs) = testNextRemoteCommitTxConfirmed(f, ChannelFeatures(Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory))) val claimHtlcTimeoutTxs = getClaimHtlcTimeoutTxs(closingState).map(_.tx) alice ! WatchTxConfirmedTriggered(42, 0, bobCommitTx) assert(closingState.claimMainOutputTx.isEmpty) // with static_remotekey we don't claim out main output @@ -903,7 +904,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv WatchTxConfirmedTriggered (next remote commit, anchor outputs)", Tag(StateTestsTags.AnchorOutputs)) { f => import f._ - val (bobCommitTx, closingState, htlcs) = testNextRemoteCommitTxConfirmed(f, ChannelVersion.ANCHOR_OUTPUTS) + val (bobCommitTx, closingState, htlcs) = testNextRemoteCommitTxConfirmed(f, ChannelFeatures(Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory, Features.AnchorOutputs -> FeatureSupport.Mandatory))) val claimHtlcTimeoutTxs = getClaimHtlcTimeoutTxs(closingState).map(_.tx) alice ! WatchTxConfirmedTriggered(42, 0, bobCommitTx) alice ! WatchTxConfirmedTriggered(45, 0, closingState.claimMainOutputTx.get.tx) @@ -974,7 +975,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv INPUT_RESTORED (next remote commit, anchor outputs)", Tag(StateTestsTags.AnchorOutputs)) { f => import f._ - val (bobCommitTx, closingState, _) = testNextRemoteCommitTxConfirmed(f, ChannelVersion.ANCHOR_OUTPUTS) + val (bobCommitTx, closingState, _) = testNextRemoteCommitTxConfirmed(f, ChannelFeatures(Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory, Features.AnchorOutputs -> FeatureSupport.Mandatory))) val claimHtlcTimeoutTxs = getClaimHtlcTimeoutTxs(closingState) // simulate a node restart @@ -994,10 +995,10 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with claimHtlcTimeoutTxs.foreach(claimHtlcTimeout => assert(alice2blockchain.expectMsgType[WatchOutputSpent].outputIndex === claimHtlcTimeout.input.outPoint.index)) } - private def testFutureRemoteCommitTxConfirmed(f: FixtureParam, channelVersion: ChannelVersion): Transaction = { + private def testFutureRemoteCommitTxConfirmed(f: FixtureParam, channelFeatures: ChannelFeatures): Transaction = { import f._ val oldStateData = alice.stateData - assert(oldStateData.asInstanceOf[DATA_NORMAL].commitments.channelVersion === channelVersion) + assert(oldStateData.asInstanceOf[DATA_NORMAL].commitments.channelFeatures === channelFeatures) // This HTLC will be fulfilled. val (ra1, htlca1) = addHtlc(25000000 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. @@ -1031,7 +1032,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.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.commitTxAndRemoteSig.commitTx.tx - if (channelVersion.hasAnchorOutputs) { + if (channelFeatures.hasFeature(Features.AnchorOutputs)) { assert(bobCommitTx.txOut.length === 6) // two main outputs + two anchors + 2 HTLCs } else { assert(bobCommitTx.txOut.length === 4) // two main outputs + 2 HTLCs @@ -1042,7 +1043,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv WatchTxConfirmedTriggered (future remote commit)") { f => import f._ - val bobCommitTx = testFutureRemoteCommitTxConfirmed(f, ChannelVersion.STANDARD) + val bobCommitTx = testFutureRemoteCommitTxConfirmed(f, ChannelFeatures(Features.empty)) // alice is able to claim its main output val claimMainTx = alice2blockchain.expectMsgType[PublishRawTx].tx Transaction.correctlySpends(claimMainTx, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) @@ -1059,7 +1060,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv WatchTxConfirmedTriggered (future remote commit, option_static_remotekey)", Tag(StateTestsTags.StaticRemoteKey)) { f => import f._ - val bobCommitTx = testFutureRemoteCommitTxConfirmed(f, ChannelVersion.STATIC_REMOTEKEY) + val bobCommitTx = testFutureRemoteCommitTxConfirmed(f, ChannelFeatures(Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory))) // using option_static_remotekey alice doesn't need to sweep her output awaitCond(alice.stateName == CLOSING, 10 seconds) alice ! WatchTxConfirmedTriggered(0, 0, bobCommitTx) @@ -1069,7 +1070,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv WatchTxConfirmedTriggered (future remote commit, anchor outputs)", Tag(StateTestsTags.AnchorOutputs)) { f => import f._ - val bobCommitTx = testFutureRemoteCommitTxConfirmed(f, ChannelVersion.ANCHOR_OUTPUTS) + val bobCommitTx = testFutureRemoteCommitTxConfirmed(f, ChannelFeatures(Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory, Features.AnchorOutputs -> FeatureSupport.Mandatory))) // alice is able to claim its main output val claimMainTx = alice2blockchain.expectMsgType[PublishRawTx].tx Transaction.correctlySpends(claimMainTx, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) @@ -1087,7 +1088,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv INPUT_RESTORED (future remote commit)") { f => import f._ - val bobCommitTx = testFutureRemoteCommitTxConfirmed(f, ChannelVersion.STANDARD) + val bobCommitTx = testFutureRemoteCommitTxConfirmed(f, ChannelFeatures(Features.empty)) // simulate a node restart val beforeRestart = alice.stateData.asInstanceOf[DATA_CLOSING] @@ -1104,12 +1105,12 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with case class RevokedCloseFixture(bobRevokedTxs: Seq[LocalCommit], htlcsAlice: Seq[(UpdateAddHtlc, ByteVector32)], htlcsBob: Seq[(UpdateAddHtlc, ByteVector32)]) - private def prepareRevokedClose(f: FixtureParam, channelVersion: ChannelVersion): RevokedCloseFixture = { + private def prepareRevokedClose(f: FixtureParam, channelFeatures: ChannelFeatures): RevokedCloseFixture = { import f._ // Bob's first commit tx doesn't contain any htlc val localCommit1 = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit - if (channelVersion.hasAnchorOutputs) { + if (channelFeatures.hasFeature(Features.AnchorOutputs)) { assert(localCommit1.commitTxAndRemoteSig.commitTx.tx.txOut.size === 4) // 2 main outputs + 2 anchors } else { assert(localCommit1.commitTxAndRemoteSig.commitTx.tx.txOut.size === 2) // 2 main outputs @@ -1126,7 +1127,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.commitTxAndRemoteSig.commitTx.tx.txOut.size == localCommit2.commitTxAndRemoteSig.commitTx.tx.txOut.size) - if (channelVersion.hasAnchorOutputs) { + if (channelFeatures.hasFeature(Features.AnchorOutputs)) { assert(localCommit2.commitTxAndRemoteSig.commitTx.tx.txOut.size === 6) } else { assert(localCommit2.commitTxAndRemoteSig.commitTx.tx.txOut.size === 4) @@ -1143,7 +1144,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.commitTxAndRemoteSig.commitTx.tx.txOut.size == localCommit3.commitTxAndRemoteSig.commitTx.tx.txOut.size) - if (channelVersion.hasAnchorOutputs) { + if (channelFeatures.hasFeature(Features.AnchorOutputs)) { assert(localCommit3.commitTxAndRemoteSig.commitTx.tx.txOut.size === 8) } else { assert(localCommit3.commitTxAndRemoteSig.commitTx.tx.txOut.size === 6) @@ -1158,7 +1159,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.commitTxAndRemoteSig.commitTx.tx.txOut.size == localCommit4.commitTxAndRemoteSig.commitTx.tx.txOut.size) - if (channelVersion.hasAnchorOutputs) { + if (channelFeatures.hasFeature(Features.AnchorOutputs)) { assert(localCommit4.commitTxAndRemoteSig.commitTx.tx.txOut.size === 4) } else { assert(localCommit4.commitTxAndRemoteSig.commitTx.tx.txOut.size === 2) @@ -1167,11 +1168,11 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with RevokedCloseFixture(Seq(localCommit1, localCommit2, localCommit3, localCommit4), Seq(htlcAlice1, htlcAlice2), Seq(htlcBob1, htlcBob2)) } - private def setupFundingSpentRevokedTx(f: FixtureParam, channelVersion: ChannelVersion): (Transaction, RevokedCommitPublished) = { + private def setupFundingSpentRevokedTx(f: FixtureParam, channelFeatures: ChannelFeatures): (Transaction, RevokedCommitPublished) = { import f._ - val revokedCloseFixture = prepareRevokedClose(f, channelVersion) - assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.channelVersion === channelVersion) + val revokedCloseFixture = prepareRevokedClose(f, channelFeatures) + assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.channelFeatures === channelFeatures) // bob publishes one of his revoked txs val bobRevokedTx = revokedCloseFixture.bobRevokedTxs(1).commitTxAndRemoteSig.commitTx.tx @@ -1181,7 +1182,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 (!channelVersion.paysDirectlyToWallet) { + if (!channelFeatures.paysDirectlyToWallet) { assert(rvk.claimMainOutputTx.nonEmpty) } assert(rvk.mainPenaltyTx.nonEmpty) @@ -1190,7 +1191,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val penaltyTxs = rvk.claimMainOutputTx.toList ++ rvk.mainPenaltyTx.toList ++ rvk.htlcPenaltyTxs // alice publishes the penalty txs - if (!channelVersion.paysDirectlyToWallet) { + if (!channelFeatures.paysDirectlyToWallet) { assert(alice2blockchain.expectMsgType[PublishRawTx].tx === rvk.claimMainOutputTx.get.tx) } assert(alice2blockchain.expectMsgType[PublishRawTx].tx === rvk.mainPenaltyTx.get.tx) @@ -1202,10 +1203,10 @@ 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 = penaltyTxs.flatMap(_.tx.txIn.map(_.outPoint)).toSet assert(spentOutpoints.forall(_.txid === bobRevokedTx.txid)) - if (channelVersion.hasAnchorOutputs) { + if (channelFeatures.hasFeature(Features.AnchorOutputs)) { assert(spentOutpoints.size === bobRevokedTx.txOut.size - 2) // we don't claim the anchors } - else if (channelVersion.paysDirectlyToWallet) { + else if (channelFeatures.paysDirectlyToWallet) { assert(spentOutpoints.size === bobRevokedTx.txOut.size - 1) // we don't claim our main output, it directly goes to our wallet } else { assert(spentOutpoints.size === bobRevokedTx.txOut.size) @@ -1213,7 +1214,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // alice watches confirmation for the outputs only her can claim assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId === bobRevokedTx.txid) - if (!channelVersion.paysDirectlyToWallet) { + if (!channelFeatures.paysDirectlyToWallet) { assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId === rvk.claimMainOutputTx.get.tx.txid) } @@ -1225,15 +1226,15 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with (bobRevokedTx, rvk) } - private def testFundingSpentRevokedTx(f: FixtureParam, channelVersion: ChannelVersion): Unit = { + private def testFundingSpentRevokedTx(f: FixtureParam, channelFeatures: ChannelFeatures): Unit = { import f._ - val (bobRevokedTx, rvk) = setupFundingSpentRevokedTx(f, channelVersion) + val (bobRevokedTx, rvk) = setupFundingSpentRevokedTx(f, channelFeatures) // once all txs are confirmed, alice can move to the closed state alice ! WatchTxConfirmedTriggered(100, 3, bobRevokedTx) alice ! WatchTxConfirmedTriggered(110, 1, rvk.mainPenaltyTx.get.tx) - if (!channelVersion.paysDirectlyToWallet) { + if (!channelFeatures.paysDirectlyToWallet) { alice ! WatchTxConfirmedTriggered(110, 2, rvk.claimMainOutputTx.get.tx) } alice ! WatchTxConfirmedTriggered(115, 0, rvk.htlcPenaltyTxs(0).tx) @@ -1243,20 +1244,20 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } test("recv WatchFundingSpentTriggered (one revoked tx)") { f => - testFundingSpentRevokedTx(f, ChannelVersion.STANDARD) + testFundingSpentRevokedTx(f, ChannelFeatures(Features.empty)) } test("recv WatchFundingSpentTriggered (one revoked tx, option_static_remotekey)", Tag(StateTestsTags.StaticRemoteKey)) { f => - testFundingSpentRevokedTx(f, ChannelVersion.STATIC_REMOTEKEY) + testFundingSpentRevokedTx(f, ChannelFeatures(Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory))) } test("recv WatchFundingSpentTriggered (one revoked tx, anchor outputs)", Tag(StateTestsTags.AnchorOutputs)) { f => - testFundingSpentRevokedTx(f, ChannelVersion.ANCHOR_OUTPUTS) + testFundingSpentRevokedTx(f, ChannelFeatures(Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory, Features.AnchorOutputs -> FeatureSupport.Mandatory))) } test("recv WatchFundingSpentTriggered (multiple revoked tx)") { f => import f._ - val revokedCloseFixture = prepareRevokedClose(f, ChannelVersion.STANDARD) + val revokedCloseFixture = prepareRevokedClose(f, ChannelFeatures(Features.empty)) assert(revokedCloseFixture.bobRevokedTxs.map(_.commitTxAndRemoteSig.commitTx.tx.txid).toSet.size === revokedCloseFixture.bobRevokedTxs.size) // all commit txs are distinct def broadcastBobRevokedTx(revokedTx: Transaction, htlcCount: Int, revokedCount: Int): RevokedCommitPublished = { @@ -1302,10 +1303,10 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(alice.stateName === CLOSED) } - def testInputRestoredRevokedTx(f: FixtureParam, channelVersion: ChannelVersion): Unit = { + def testInputRestoredRevokedTx(f: FixtureParam, channelFeatures: ChannelFeatures): Unit = { import f._ - val (bobRevokedTx, rvk) = setupFundingSpentRevokedTx(f, channelVersion) + val (bobRevokedTx, rvk) = setupFundingSpentRevokedTx(f, channelFeatures) // simulate a node restart val beforeRestart = alice.stateData.asInstanceOf[DATA_CLOSING] @@ -1327,17 +1328,17 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } test("recv INPUT_RESTORED (one revoked tx)") { f => - testInputRestoredRevokedTx(f, ChannelVersion.STANDARD) + testInputRestoredRevokedTx(f, ChannelFeatures(Features.empty)) } test("recv INPUT_RESTORED (one revoked tx, anchor outputs)", Tag(StateTestsTags.AnchorOutputs)) { f => - testInputRestoredRevokedTx(f, ChannelVersion.ANCHOR_OUTPUTS) + testInputRestoredRevokedTx(f, ChannelFeatures(Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory, Features.AnchorOutputs -> FeatureSupport.Mandatory))) } - def testOutputSpentRevokedTx(f: FixtureParam, channelVersion: ChannelVersion): Unit = { + def testOutputSpentRevokedTx(f: FixtureParam, channelFeatures: ChannelFeatures): Unit = { import f._ - val revokedCloseFixture = prepareRevokedClose(f, channelVersion) - assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.channelVersion === channelVersion) + val revokedCloseFixture = prepareRevokedClose(f, channelFeatures) + assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.channelFeatures === channelFeatures) val commitmentFormat = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.commitmentFormat // bob publishes one of his revoked txs @@ -1348,7 +1349,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.commitTxAndRemoteSig.commitTx.tx) - if (channelVersion.paysDirectlyToWallet) { + if (channelFeatures.paysDirectlyToWallet) { assert(rvk.claimMainOutputTx.isEmpty) } else { assert(rvk.claimMainOutputTx.nonEmpty) @@ -1358,10 +1359,10 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(rvk.claimHtlcDelayedPenaltyTxs.isEmpty) // alice publishes the penalty txs and watches outputs - val claimTxsCount = if (channelVersion.paysDirectlyToWallet) 5 else 6 // 2 main outputs and 4 htlcs + val claimTxsCount = if (channelFeatures.paysDirectlyToWallet) 5 else 6 // 2 main outputs and 4 htlcs (1 to claimTxsCount).foreach(_ => alice2blockchain.expectMsgType[PublishTx]) assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId === rvk.commitTx.txid) - if (!channelVersion.paysDirectlyToWallet) { + if (!channelFeatures.paysDirectlyToWallet) { assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId === rvk.claimMainOutputTx.get.tx.txid) } (1 to 5).foreach(_ => alice2blockchain.expectMsgType[WatchOutputSpent]) // main output penalty and 4 htlc penalties @@ -1425,7 +1426,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(remainingHtlcPenaltyTxs.size === 2) alice ! WatchTxConfirmedTriggered(100, 3, rvk.commitTx) alice ! WatchTxConfirmedTriggered(110, 0, rvk.mainPenaltyTx.get.tx) - if (!channelVersion.paysDirectlyToWallet) { + if (!channelFeatures.paysDirectlyToWallet) { alice ! WatchTxConfirmedTriggered(110, 1, rvk.claimMainOutputTx.get.tx) } alice ! WatchTxConfirmedTriggered(110, 2, remainingHtlcPenaltyTxs.head.tx) @@ -1440,22 +1441,22 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } test("recv WatchOutputSpentTriggered (one revoked tx, counterparty published htlc-success tx)") { f => - testOutputSpentRevokedTx(f, ChannelVersion.STANDARD) + testOutputSpentRevokedTx(f, ChannelFeatures(Features.empty)) } test("recv WatchOutputSpentTriggered (one revoked tx, counterparty published htlc-success tx, option_static_remotekey)", Tag(StateTestsTags.StaticRemoteKey)) { f => - testOutputSpentRevokedTx(f, ChannelVersion.STATIC_REMOTEKEY) + testOutputSpentRevokedTx(f, ChannelFeatures(Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory))) } test("recv WatchOutputSpentTriggered (one revoked tx, counterparty published htlc-success tx, anchor outputs)", Tag(StateTestsTags.AnchorOutputs)) { f => - testOutputSpentRevokedTx(f, ChannelVersion.ANCHOR_OUTPUTS) + testOutputSpentRevokedTx(f, ChannelFeatures(Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory, Features.AnchorOutputs -> FeatureSupport.Mandatory))) } test("recv WatchOutputSpentTriggered (one revoked tx, counterparty published aggregated htlc tx)", Tag(StateTestsTags.AnchorOutputs)) { f => import f._ // bob publishes one of his revoked txs - val revokedCloseFixture = prepareRevokedClose(f, ChannelVersion.ANCHOR_OUTPUTS) + val revokedCloseFixture = prepareRevokedClose(f, ChannelFeatures(Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory, Features.AnchorOutputs -> FeatureSupport.Mandatory))) val bobRevokedCommit = revokedCloseFixture.bobRevokedTxs(2) val commitmentFormat = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.commitmentFormat alice ! WatchFundingSpentTriggered(bobRevokedCommit.commitTxAndRemoteSig.commitTx.tx) @@ -1529,10 +1530,10 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice2blockchain.expectNoMsg(1 second) } - private def testRevokedTxConfirmed(f: FixtureParam, channelVersion: ChannelVersion): Unit = { + private def testRevokedTxConfirmed(f: FixtureParam, channelFeatures: ChannelFeatures): Unit = { import f._ - assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.channelVersion === channelVersion) - val initOutputCount = if (channelVersion.hasAnchorOutputs) 4 else 2 + assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.channelFeatures === channelFeatures) + val initOutputCount = if (channelFeatures.hasFeature(Features.AnchorOutputs)) 4 else 2 assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.commitTxAndRemoteSig.commitTx.tx.txOut.size === initOutputCount) // bob's second commit tx contains 2 incoming htlcs @@ -1575,11 +1576,11 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } test("recv WatchTxConfirmedTriggered (one revoked tx, pending htlcs)") { f => - testRevokedTxConfirmed(f, ChannelVersion.STANDARD) + testRevokedTxConfirmed(f, ChannelFeatures(Features.empty)) } test("recv WatchTxConfirmedTriggered (one revoked tx, pending htlcs, anchor outputs)", Tag(StateTestsTags.AnchorOutputs)) { f => - testRevokedTxConfirmed(f, ChannelVersion.ANCHOR_OUTPUTS) + testRevokedTxConfirmed(f, ChannelFeatures(Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory, Features.AnchorOutputs -> FeatureSupport.Mandatory))) } test("recv ChannelReestablish") { f => @@ -1588,7 +1589,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val initialState = alice.stateData.asInstanceOf[DATA_CLOSING] val bobCommitments = bob.stateData.asInstanceOf[HasCommitments].commitments val bobCurrentPerCommitmentPoint = TestConstants.Bob.channelKeyManager.commitmentPoint( - TestConstants.Bob.channelKeyManager.keyPath(bobCommitments.localParams, bobCommitments.channelVersion), + TestConstants.Bob.channelKeyManager.keyPath(bobCommitments.localParams, bobCommitments.channelConfig), bobCommitments.localCommit.index) alice ! ChannelReestablish(channelId(bob), 42, 42, PrivateKey(ByteVector32.Zeroes), bobCurrentPerCommitmentPoint) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/keymanager/LocalChannelKeyManagerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/keymanager/LocalChannelKeyManagerSpec.scala index fa382b0e72..a3b51a684e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/keymanager/LocalChannelKeyManagerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/keymanager/LocalChannelKeyManagerSpec.scala @@ -18,12 +18,11 @@ package fr.acinq.eclair.crypto.keymanager import java.io.File import java.nio.file.Files - import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.DeterministicWallet.KeyPath import fr.acinq.bitcoin.{Block, ByteVector32, DeterministicWallet} import fr.acinq.eclair.Setup.Seeds -import fr.acinq.eclair.channel.ChannelVersion +import fr.acinq.eclair.channel.ChannelConfig import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.{NodeParams, TestConstants, TestUtils} import org.scalatest.funsuite.AnyFunSuite @@ -69,7 +68,7 @@ class LocalChannelKeyManagerSpec extends AnyFunSuite { val fundingPub = channelKeyManager.fundingPublicKey(fundingKeyPath) val localParams = TestConstants.Alice.channelParams.copy(fundingKeyPath = fundingKeyPath) - val channelKeyPath = channelKeyManager.keyPath(localParams, ChannelVersion.STANDARD) + val channelKeyPath = channelKeyManager.keyPath(localParams, ChannelConfig.standard) assert(fundingPub.publicKey == PrivateKey(hex"216414970b4216b197a1040367419ad6922f80e8b73ced083e9afe5e6ddd8e4c").publicKey) assert(channelKeyManager.revocationPoint(channelKeyPath).publicKey == PrivateKey(hex"a4e7ab3c54752a3487b3c474467843843f28d3bb9113e65e92056ad45d1e318e").publicKey) @@ -86,7 +85,7 @@ class LocalChannelKeyManagerSpec extends AnyFunSuite { val fundingPub = channelKeyManager.fundingPublicKey(fundingKeyPath) val localParams = TestConstants.Alice.channelParams.copy(fundingKeyPath = fundingKeyPath) - val channelKeyPath = channelKeyManager.keyPath(localParams, ChannelVersion.STANDARD) + val channelKeyPath = channelKeyManager.keyPath(localParams, ChannelConfig.standard) assert(fundingPub.publicKey == PrivateKey(hex"7bb8019c99fcba1c6bd0cc7f3c635c14c658d26751232d6a6350d8b6127d53c3").publicKey) assert(channelKeyManager.revocationPoint(channelKeyPath).publicKey == PrivateKey(hex"26510db99546c9b08418fe9df2da710a92afa6cc4e5681141610dfb8019052e6").publicKey) @@ -103,7 +102,7 @@ class LocalChannelKeyManagerSpec extends AnyFunSuite { val fundingPub = channelKeyManager.fundingPublicKey(fundingKeyPath) val localParams = TestConstants.Alice.channelParams.copy(fundingKeyPath = fundingKeyPath) - val channelKeyPath = channelKeyManager.keyPath(localParams, ChannelVersion.STANDARD) + val channelKeyPath = channelKeyManager.keyPath(localParams, ChannelConfig.standard) assert(fundingPub.publicKey == PrivateKey(hex"b97c04796850e9d74a06c9d7230d85e2ecca3598b162ddf902895ece820c8f09").publicKey) assert(channelKeyManager.revocationPoint(channelKeyPath).publicKey == PrivateKey(hex"ee13db7f2d7e672f21395111ee169af8462c6e8d1a6a78d808f7447b27155ffb").publicKey) @@ -120,7 +119,7 @@ class LocalChannelKeyManagerSpec extends AnyFunSuite { val fundingPub = channelKeyManager.fundingPublicKey(fundingKeyPath) val localParams = TestConstants.Alice.channelParams.copy(fundingKeyPath = fundingKeyPath) - val channelKeyPath = channelKeyManager.keyPath(localParams, ChannelVersion.STANDARD) + val channelKeyPath = channelKeyManager.keyPath(localParams, ChannelConfig.standard) assert(fundingPub.publicKey == PrivateKey(hex"46a4e818615a48a99ce9f6bd73eea07d5822dcfcdff18081ea781d4e5e6c036c").publicKey) assert(channelKeyManager.revocationPoint(channelKeyPath).publicKey == PrivateKey(hex"c2cd9e2f9f8203f16b1751bd252285bb2e7fc4688857d620467b99645ebdfbe6").publicKey) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/ChannelIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/ChannelIntegrationSpec.scala index 0f851a24b4..c9b75590e7 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/ChannelIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/ChannelIntegrationSpec.scala @@ -431,7 +431,7 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec { val finalAddressC = scriptPubKeyToAddress(sender.expectMsgType[RES_GETSTATEDATA[DATA_NORMAL]].data.commitments.localParams.defaultFinalScriptPubKey) // we prepare the revoked transactions F will publish val keyManagerF = nodes("F").nodeParams.channelKeyManager - val channelKeyPathF = keyManagerF.keyPath(commitmentsF.localParams, commitmentsF.channelVersion) + val channelKeyPathF = keyManagerF.keyPath(commitmentsF.localParams, commitmentsF.channelConfig) val localPerCommitmentPointF = keyManagerF.commitmentPoint(channelKeyPathF, commitmentsF.localCommit.index.toInt) val revokedCommitTx = { val commitTx = localCommitF.commitTxAndRemoteSig.commitTx diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/interop/rustytests/RustyTestsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/interop/rustytests/RustyTestsSpec.scala index ffdc8b88a5..577ae1e5de 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/interop/rustytests/RustyTestsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/interop/rustytests/RustyTestsSpec.scala @@ -29,7 +29,7 @@ import fr.acinq.eclair.channel.publish.TxPublisher import fr.acinq.eclair.channel.states.StateTestsHelperMethods.FakeTxPublisherFactory import fr.acinq.eclair.payment.receive.{ForwardHandler, PaymentHandler} import fr.acinq.eclair.wire.protocol.Init -import fr.acinq.eclair.{MilliSatoshiLong, TestKitBaseClass, TestUtils} +import fr.acinq.eclair.{Features, MilliSatoshiLong, TestKitBaseClass, TestUtils} import org.scalatest.funsuite.FixtureAnyFunSuiteLike import org.scalatest.matchers.should.Matchers import org.scalatest.{BeforeAndAfterAll, Outcome} @@ -66,16 +66,17 @@ class RustyTestsSpec extends TestKitBaseClass with Matchers with FixtureAnyFunSu val feeEstimator = new TestFeeEstimator val aliceNodeParams = Alice.nodeParams.copy(blockCount = blockCount, onChainFeeConf = Alice.nodeParams.onChainFeeConf.copy(feeEstimator = feeEstimator)) val bobNodeParams = Bob.nodeParams.copy(blockCount = blockCount, onChainFeeConf = Bob.nodeParams.onChainFeeConf.copy(feeEstimator = feeEstimator)) - val channelVersion = ChannelVersion.STANDARD + val channelConfig = ChannelConfig.standard + val channelFeatures = ChannelFeatures(Features.empty) val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(aliceNodeParams, wallet, Bob.nodeParams.nodeId, alice2blockchain.ref, relayer, FakeTxPublisherFactory(alice2blockchain)), alicePeer.ref) val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bobNodeParams, wallet, Alice.nodeParams.nodeId, bob2blockchain.ref, relayer, FakeTxPublisherFactory(bob2blockchain)), bobPeer.ref) val aliceInit = Init(Alice.channelParams.features) val bobInit = Init(Bob.channelParams.features) // alice and bob will both have 1 000 000 sat feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(10000 sat))) - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, 2000000 sat, 1000000000 msat, feeEstimator.getFeeratePerKw(target = 2), feeEstimator.getFeeratePerKw(target = 6), None, Alice.channelParams, pipe, bobInit, ChannelFlags.Empty, channelVersion) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, 2000000 sat, 1000000000 msat, feeEstimator.getFeeratePerKw(target = 2), feeEstimator.getFeeratePerKw(target = 6), None, Alice.channelParams, pipe, bobInit, ChannelFlags.Empty, channelConfig, channelFeatures) alice2blockchain.expectMsgType[TxPublisher.SetChannelId] - bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams, pipe, aliceInit, channelVersion) + bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams, pipe, aliceInit, channelConfig, channelFeatures) bob2blockchain.expectMsgType[TxPublisher.SetChannelId] pipe ! (alice, bob) within(30 seconds) { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala index 1040f115a0..55a3a159bd 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala @@ -22,7 +22,7 @@ import akka.testkit.{TestFSMRef, TestProbe} import com.google.common.net.HostAndPort import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.{Block, Btc, SatoshiLong, Script} -import fr.acinq.eclair.FeatureSupport.Optional +import fr.acinq.eclair.FeatureSupport.{Mandatory, Optional} import fr.acinq.eclair.Features.{AnchorOutputs, StaticRemoteKey, Wumbo} import fr.acinq.eclair.TestConstants._ import fr.acinq.eclair._ @@ -313,7 +313,8 @@ class PeerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Paralle val relayFees = Some(100 msat, 1000) probe.send(peer, Peer.OpenChannel(remoteNodeId, 12300 sat, 0 msat, None, relayFees, None, None)) val init = channel.expectMsgType[INPUT_INIT_FUNDER] - assert(init.channelVersion === ChannelVersion.STANDARD) + assert(init.channelConfig === ChannelConfig.standard) + assert(init.channelFeatures === ChannelFeatures(Features.empty)) assert(init.fundingAmount === 12300.sat) assert(init.initialRelayFees_opt === relayFees) awaitCond(peer.stateData.channels.nonEmpty) @@ -331,7 +332,7 @@ class PeerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Paralle feeEstimator.setFeerate(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw * 2)) probe.send(peer, Peer.OpenChannel(remoteNodeId, 15000 sat, 0 msat, None, None, None, None)) val init = channel.expectMsgType[INPUT_INIT_FUNDER] - assert(init.channelVersion.hasAnchorOutputs) + assert(init.channelFeatures === ChannelFeatures(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Mandatory))) assert(init.fundingAmount === 15000.sat) assert(init.initialRelayFees_opt === None) assert(init.initialFeeratePerKw === TestConstants.anchorOutputsFeeratePerKw) @@ -342,10 +343,10 @@ class PeerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Paralle import f._ val probe = TestProbe() - connect(remoteNodeId, peer, peerConnection, remoteInit = protocol.Init(Features(StaticRemoteKey -> Optional))) + connect(remoteNodeId, peer, peerConnection, remoteInit = protocol.Init(Features(StaticRemoteKey -> Mandatory))) probe.send(peer, Peer.OpenChannel(remoteNodeId, 24000 sat, 0 msat, None, None, None, None)) val init = channel.expectMsgType[INPUT_INIT_FUNDER] - assert(init.channelVersion.hasStaticRemotekey) + assert(init.channelFeatures === ChannelFeatures(Features(StaticRemoteKey -> Mandatory))) assert(init.localParams.walletStaticPaymentBasepoint.isDefined) assert(init.localParams.defaultFinalScriptPubKey === Script.write(Script.pay2wpkh(init.localParams.walletStaticPaymentBasepoint.get))) } 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 6b1e259799..231b14c22e 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 @@ -31,7 +31,7 @@ import fr.acinq.eclair.transactions.Transactions.InputInfo import fr.acinq.eclair.wire.protocol.Onion.{ChannelRelayTlvPayload, FinalTlvPayload, RelayLegacyPayload} import fr.acinq.eclair.wire.protocol.OnionTlv.{AmountToForward, OutgoingCltv, PaymentData} import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, MilliSatoshi, MilliSatoshiLong, ShortChannelId, TestConstants, nodeFee, randomBytes32, randomKey} +import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, Features, MilliSatoshi, MilliSatoshiLong, ShortChannelId, TestConstants, nodeFee, randomBytes32, randomKey} import org.scalatest.BeforeAndAfterAll import org.scalatest.funsuite.AnyFunSuite import scodec.Attempt @@ -365,9 +365,9 @@ object PaymentPacketSpec { def makeCommitments(channelId: ByteVector32, testAvailableBalanceForSend: MilliSatoshi = 50000000 msat, testAvailableBalanceForReceive: MilliSatoshi = 50000000 msat, testCapacity: Satoshi = 100000 sat): Commitments = { val params = LocalParams(null, null, null, null, null, null, null, 0, isFunder = true, null, None, null) - val remoteParams = RemoteParams(randomKey().publicKey, null, null, null, null, null, maxAcceptedHtlcs = 0, null, null, null, null, null, null) + val remoteParams = RemoteParams(randomKey().publicKey, null, null, null, null, null, maxAcceptedHtlcs = 0, null, null, null, null, null, null, None) val commitInput = InputInfo(OutPoint(randomBytes32(), 1), TxOut(testCapacity, Nil), Nil) - new Commitments(ChannelVersion.STANDARD, params, remoteParams, 0.toByte, null, null, null, null, 0, 0, Map.empty, null, commitInput, null, channelId) { + new Commitments(ChannelConfig.standard, ChannelFeatures(Features.empty), params, remoteParams, 0.toByte, null, null, null, null, 0, 0, Map.empty, null, commitInput, null, channelId) { override lazy val availableBalanceForSend: MilliSatoshi = testAvailableBalanceForSend.max(0 msat) override lazy val availableBalanceForReceive: MilliSatoshi = testAvailableBalanceForReceive.max(0 msat) } 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 d6a2c159ea..df974d22e7 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 @@ -19,12 +19,12 @@ package fr.acinq.eclair.transactions import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.{ByteVector32, Crypto, SatoshiLong, Script, ScriptFlags, Transaction} import fr.acinq.eclair.blockchain.fee.FeeratePerKw -import fr.acinq.eclair.channel.ChannelVersion +import fr.acinq.eclair.channel.ChannelFeatures import fr.acinq.eclair.channel.Helpers.Funding import fr.acinq.eclair.crypto.Generators import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.wire.protocol.UpdateAddHtlc -import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, MilliSatoshi, MilliSatoshiLong, TestConstants} +import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, FeatureSupport, Features, MilliSatoshi, MilliSatoshiLong, TestConstants} import grizzled.slf4j.Logging import org.scalatest.funsuite.AnyFunSuite import scodec.bits._ @@ -35,7 +35,7 @@ trait TestVectorsSpec extends AnyFunSuite with Logging { // @formatter:off def filename: String - def channelVersion: ChannelVersion + def channelFeatures: ChannelFeatures def commitmentFormat: CommitmentFormat // @formatter:on @@ -95,7 +95,7 @@ trait TestVectorsSpec extends AnyFunSuite with Logging { val funding_pubkey = funding_privkey.publicKey val per_commitment_point = PublicKey(hex"025f7117a78150fe2ef97db7cfc83bd57b2e2c0d0dd25eaf467a4a1c2a45ce1486") val htlc_privkey = Generators.derivePrivKey(payment_basepoint_secret, per_commitment_point) - val payment_privkey = if (channelVersion.hasStaticRemotekey) payment_basepoint_secret else htlc_privkey + val payment_privkey = if (channelFeatures.hasFeature(Features.StaticRemoteKey)) payment_basepoint_secret else htlc_privkey val delayed_payment_privkey = Generators.derivePrivKey(delayed_payment_basepoint_secret, per_commitment_point) val revocation_pubkey = PublicKey(hex"0212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b19") val feerate_per_kw = 15000 @@ -112,7 +112,7 @@ trait TestVectorsSpec extends AnyFunSuite with Logging { val funding_privkey = PrivateKey(hex"1552dfba4f6cf29a62a0af13c8d6981d36d0ef8d61ba10fb0fe90da7634d7e1301") val funding_pubkey = funding_privkey.publicKey val htlc_privkey = Generators.derivePrivKey(payment_basepoint_secret, Local.per_commitment_point) - val payment_privkey = if (channelVersion.hasStaticRemotekey) payment_basepoint_secret else htlc_privkey + val payment_privkey = if (channelFeatures.hasFeature(Features.StaticRemoteKey)) payment_basepoint_secret else htlc_privkey } val coinbaseTx = Transaction.read("01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff03510101ffffffff0100f2052a010000001976a9143ca33c2e4446f4a305f23c80df8ad1afdcf652f988ac00000000") @@ -399,7 +399,7 @@ 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 channelVersion: ChannelVersion = ChannelVersion.STANDARD + override def channelFeatures: ChannelFeatures = ChannelFeatures(Features.empty) override def commitmentFormat: CommitmentFormat = DefaultCommitmentFormat // @formatter:on } @@ -407,7 +407,7 @@ class DefaultCommitmentTestVectorSpec extends TestVectorsSpec { class StaticRemoteKeyTestVectorSpec extends TestVectorsSpec { // @formatter:off override def filename: String = "/bolt3-tx-test-vectors-static-remotekey-format.txt" - override def channelVersion: ChannelVersion = ChannelVersion.STATIC_REMOTEKEY + override def channelFeatures: ChannelFeatures = ChannelFeatures(Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory)) override def commitmentFormat: CommitmentFormat = DefaultCommitmentFormat // @formatter:on } @@ -415,7 +415,7 @@ class StaticRemoteKeyTestVectorSpec extends TestVectorsSpec { class AnchorOutputsTestVectorSpec extends TestVectorsSpec { // @formatter:off override def filename: String = "/bolt3-tx-test-vectors-anchor-outputs-format.txt" - override def channelVersion: ChannelVersion = ChannelVersion.ANCHOR_OUTPUTS + override def channelFeatures: ChannelFeatures = ChannelFeatures(Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory, Features.AnchorOutputs -> FeatureSupport.Mandatory)) override def commitmentFormat: CommitmentFormat = AnchorOutputsCommitmentFormat // @formatter:on } 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 07467683b1..b43f18a3c4 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 @@ -147,6 +147,8 @@ class ChannelCodecsSpec extends AnyFunSuite { val oldjson = Serialization.write(oldnormal)(JsonSupport.formats) .replace(""","unknownFields":""""", "") .replace(""""channelVersion":"00000000000000000000000000000000",""", "") + .replace(""""channelConfig":[],""", "") + .replace(""""channelFeatures":{"features":{"activated":{},"unknown":[]}},""", "") .replace(""""dustLimit"""", """"dustLimitSatoshis"""") .replace(""""channelReserve"""", """"channelReserveSatoshis"""") .replace(""""htlcMinimum"""", """"htlcMinimumMsat"""") @@ -161,6 +163,8 @@ class ChannelCodecsSpec extends AnyFunSuite { val newjson = Serialization.write(newnormal)(JsonSupport.formats) .replace(""","unknownFields":""""", "") .replace(""""channelVersion":"00000000000000000000000000000000",""", "") + .replace(""""channelConfig":[],""", "") + .replace(""""channelFeatures":{"features":{"activated":{},"unknown":[]}},""", "") .replace(""""dustLimit"""", """"dustLimitSatoshis"""") .replace(""""channelReserve"""", """"channelReserveSatoshis"""") .replace(""""htlcMinimum"""", """"htlcMinimumMsat"""") @@ -298,7 +302,8 @@ object ChannelCodecsSpec { paymentBasepoint = PrivateKey(ByteVector.fill(32)(3)).publicKey, delayedPaymentBasepoint = PrivateKey(ByteVector.fill(32)(4)).publicKey, htlcBasepoint = PrivateKey(ByteVector.fill(32)(6)).publicKey, - features = Features.empty) + features = Features.empty, + shutdownScript = None) val paymentPreimages = Seq( ByteVector32(hex"0000000000000000000000000000000000000000000000000000000000000000"), @@ -337,7 +342,7 @@ object ChannelCodecsSpec { ) val localCommit = LocalCommit(0, CommitmentSpec(htlcs.toSet, FeeratePerKw(1500 sat), 50000000 msat, 70000000 msat), CommitTxAndRemoteSig(CommitTx(commitmentInput, commitTx), remoteSig), Nil) val remoteCommit = RemoteCommit(0, CommitmentSpec(htlcs.map(_.opposite).toSet, FeeratePerKw(1500 sat), 50000 msat, 700000 msat), ByteVector32(hex"0303030303030303030303030303030303030303030303030303030303030303"), PrivateKey(ByteVector.fill(32)(4)).publicKey) - val commitments = Commitments(ChannelVersion.STANDARD, localParams, remoteParams, channelFlags = 0x01.toByte, localCommit, remoteCommit, LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), + val commitments = Commitments(ChannelConfig.standard, ChannelFeatures(Features.empty), localParams, remoteParams, channelFlags = 0x01.toByte, localCommit, remoteCommit, LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), localNextHtlcId = 32L, remoteNextHtlcId = 4L, originChannels = origins, @@ -435,10 +440,10 @@ object ChannelCodecsSpec { case _: PrivateKey => JString("XXX") })) - class ChannelVersionSerializer extends CustomSerializer[ChannelVersion](_ => ( { + class ChannelConfigSerializer extends CustomSerializer[ChannelConfig](_ => ( { null }, { - case x: ChannelVersion => JString(x.bits.toBin) + case x: ChannelConfig => JArray(x.options.toList.map(o => JString(o.name))) })) class TransactionSerializer extends CustomSerializer[TransactionWithInputInfo](_ => ( { @@ -510,7 +515,7 @@ object ChannelCodecsSpec { new OutPointSerializer + new OutPointKeySerializer + new FeatureKeySerializer + - new ChannelVersionSerializer + + new ChannelConfigSerializer + new InputInfoSerializer } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0Spec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0Spec.scala index f66ca6b587..5e42cc1dfd 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0Spec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0Spec.scala @@ -1,9 +1,9 @@ package fr.acinq.eclair.wire.internal.channel.version0 import fr.acinq.bitcoin.ByteVector32 -import fr.acinq.eclair.channel.ChannelVersion import fr.acinq.eclair.transactions.{IncomingHtlc, OutgoingHtlc} import fr.acinq.eclair.wire.internal.channel.version0.ChannelCodecs0.Codecs._ +import fr.acinq.eclair.wire.internal.channel.version0.ChannelTypes0.ChannelVersion import fr.acinq.eclair.wire.protocol.{OnionRoutingPacket, UpdateAddHtlc} import fr.acinq.eclair.{CltvExpiry, MilliSatoshiLong} import org.scalatest.funsuite.AnyFunSuite diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1Spec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1Spec.scala index 95f4ebdf15..efb6040bdc 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1Spec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1Spec.scala @@ -6,9 +6,9 @@ import fr.acinq.bitcoin.Crypto.PrivateKey import fr.acinq.bitcoin.DeterministicWallet.KeyPath import fr.acinq.bitcoin.{DeterministicWallet, OutPoint, Satoshi, SatoshiLong, Script} import fr.acinq.eclair.blockchain.fee.FeeratePerKw -import fr.acinq.eclair.channel.{ChannelVersion, LocalParams, Origin, RemoteParams} +import fr.acinq.eclair.channel.{LocalParams, Origin, RemoteParams} import fr.acinq.eclair.transactions.{CommitmentSpec, DirectedHtlc, IncomingHtlc, OutgoingHtlc} -import fr.acinq.eclair.wire.internal.channel.ChannelCodecsSpec.normal +import fr.acinq.eclair.wire.internal.channel.version0.ChannelTypes0.ChannelVersion import fr.acinq.eclair.wire.internal.channel.version1.ChannelCodecs1.Codecs._ import fr.acinq.eclair.wire.protocol.UpdateAddHtlc import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, Features, MilliSatoshi, MilliSatoshiLong, TestConstants, UInt64, randomBytes, randomBytes32, randomKey} @@ -78,7 +78,6 @@ class ChannelCodecs1Spec extends AnyFunSuite { roundtrip(o, localParamsCodec(ChannelVersion.ANCHOR_OUTPUTS)) } - test("encode/decode remoteparams") { val o = RemoteParams( nodeId = randomKey().publicKey, @@ -93,7 +92,8 @@ class ChannelCodecs1Spec extends AnyFunSuite { paymentBasepoint = randomKey().publicKey, delayedPaymentBasepoint = randomKey().publicKey, htlcBasepoint = randomKey().publicKey, - features = TestConstants.Alice.nodeParams.features) + features = TestConstants.Alice.nodeParams.features, + shutdownScript = None) val encoded = remoteParamsCodec.encode(o).require val decoded = remoteParamsCodec.decodeValue(encoded).require assert(o === decoded) 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 b790d58a5d..d129b00f64 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,12 +1,18 @@ package fr.acinq.eclair.wire.internal.channel.version2 -import fr.acinq.bitcoin.{OutPoint, Transaction} -import fr.acinq.eclair.randomBytes32 +import fr.acinq.bitcoin.{ByteVector64, OutPoint, Transaction} +import fr.acinq.eclair.channel.{ChannelConfig, ChannelFeatures, HtlcTxAndRemoteSig} import fr.acinq.eclair.wire.internal.channel.version2.ChannelCodecs2.Codecs._ +import fr.acinq.eclair.wire.internal.channel.version2.ChannelCodecs2.stateDataCodec +import fr.acinq.eclair.{FeatureSupport, Features, randomBytes32} import org.scalatest.funsuite.AnyFunSuite +import scodec.bits.HexStringSyntax class ChannelCodecs2Spec extends AnyFunSuite { + // Data from a channel in NORMAL state using codecs version 2. + val dataNormal = hex"00020000000103af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d000091b1206959fd8fcd024797f81605e598b4a08e2b4bf103197ec17b79f1c4c2c7680000001000000000000044c000000001dcd65000000000000002710000000000000000000900064ff1600140097be5a335f9c489d4ef63d4af8710b9ee8ec760000186b0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024982039dc0e0b1d25905e44fdf6f8e89755a5e219685840d0bc1d28d3308f9628a358500000000000003e8ffffffffffffffff0000000000004e2000000000000003e80090001e02790de7416ff75ad5028f42e81819043dd007565aa21267fba62a35615b7f2a300336c13e3b3294c1c80e41c1ef3f7d976ed8c85cb262807bb8697db3ff05240e4d0342f01378b499ef16b888617838635230d9f60a37efcd1fe5f7daa80b10d455100348e3aa6f37ab809ae57d0739ca31bbb001ac1ffac363f75d7ed33a2d142a28b302281a45ca766c3b34937985d3d539d8cfaae58a87ce94dc230708aea0c260ca9800000003024982000000000000000001000200fd05aa7997c9c34f9ff781e6eeb6a27867a89d63ac7da76c5dbb175c092f74803ebd3900000000000000000000000011e1a300b5982a1ddd30d66654d27234f7f31921ee7011ed6290dd92043a50d6e1ab08b200061b1000038ec7647194925944599ae67df7bcf07dbf3453f249a07b3aaf4c61bb038be497080cf1e3f2854f1fbfcf5ca6be6284e402c89ba5bfc717e2d0c6fa1223be4277809ca9a2b2e625e83e06c561d7004a9725d3dc8c7e24f371592ab0954c020d1813984f96e70f39f057b404300f09487fb21cfa815dd8b5ceaf171155af7ea9a14e0711109f26fc854e7b2750c394f55c47193fd72f810c063a8506bc494403b52fdd14fa18005009b8f79c520f530c9fc2c9ac5f1f4f4926e3e671dd336d233c59878c2d04f309a118b9a71424d681a4d781df60cf89f3ca16aee95116a0a9445730b4ed1708ff296023c5c0f7a6e17d6b56b3916a1a32937f550680c6121e74c3a67717c98a5f97c3fcac8054a642476460678ed09b75da6d0a3825b8844b4babd3d29e8fdd09764f0415e95811b564a708e8b44084f336af96ad690f0207060234fe649a3b4f7c835fa391261ff13551079261dc5ad9ab29e3ded1561f1f8b5ea07c02b084a0c80ee49a4b137ebf201bef8ae39b74d20e39eb414be67dbad1b56d5b774a4897cb8a9703e76b69a8f64d154148e0e161f9b32f038cb1bff774dd1cbe5bc20a119cef8a1140960ced2681c9c03a4f45c08be8d553b986ec9a2bc4fa1e5ced2d40806ce87958145cf54fce170dcdc71902eede20cc7ac29235696a49c762b7ecaa2fd92e1e40cb2b951b67e6d076f7de966220571d9068c14153e9fc20ea078a81443114a2817bffe4d8b52b20476e09ece02185ebf1a35dad53680bda5c6e72cc78f35b0ac0758f0929800f1a4f7d2d8c3dee7eb43fd013dd08c2400be7a5b8431433423f85557f6dfbe81906fc1e5d365cf98ffa21af3bb8ce793a4a8531fe46978f6442a802e4c86967923e1466a40574e3f85e27c34267f31edeffca7e3309e810ba887afb09d17d2e6d6e2a806466767c16b5237bb49a84f9a2bd6cecfdfffca5059a78259323ab1b368639dc796e30bea42f942b68b6baf055398cdba211cc3af4e29a0648829cfd8cdfeb35cefef1e141da79a1bff107dbb6dc717fd9dce1b3c925cedcbf8f2cbf5f25d8fb8c301275ba70706395698ccd158eb6cff35343c60934bb6ee48b9edd2d3ff7625334954b61435f545d4ffddbf6264bc798857b244c4b8928d4967e0da4cca8077af0766e6e99975afe993d37f83363996df9d75cb59440a2c9411aa1e044f3b1739c7b59cae26984bdb49c661684705f38c363d0ee39e4ed2a297a1025a9cc1e972ec7963c1be54bb5e847feda1b14bbe118d617a1a41f1251c3ec8b2f8ddc7cad3a4d8a1d45af14e1c0c2a1bbcabae9df671a399894890bc49080ea75c9c4068bb89fb0eec586389efc0fff11704b69fd10bca02c06f764844334e58abba3b554f64c4e4e26529042a2a1ae9965846816d1e8a411fee4d27ce3fcb1776d40526aa1f83dfb583fa5e2e06c46197c5032c2c48c3f0e0136e601ca5f8ce508441ff2ddc7e46f7c58811071669c00bed4008e8c0dbd7ed2fe00e9e0a656aa22acd22485fba08efb3065a099fd6769a520a45a8f778f5dfdfef959a26d8e6b8d45df8e3641de245a83b0ed1a312159b728333ab457b72ffc2cec97fd6ce29275e754b7ac8aa122be7db85d7d0c3d7a67dc82a406301fd79936f4ff3e2ee491de4756144868304d1afed8df91580eeecb1af2446c035b2debb6ddf3f6a19fccfcbdf5d73df3420df8153d8a8307e37969a52978e673a0fc003d6dad6ba8269349ef1b8dfd61da4c8568f52033c352b0422b3d8298ddccc684708aa5797b32bce5b97236b7002c158b8c878b1bfa0c34b54636c295f71e2f281f19d5c6cc796a6c06cc56d9490a41911f279be3cf73de1dd214e1d344c26525456c4493dd28096c518d4a2aac87570145f577059c7f371e53df6d2cd90e7b75720a5da71bd29d264d00fd05aa7997c9c34f9ff781e6eeb6a27867a89d63ac7da76c5dbb175c092f74803ebd390000000000000001000000000bebc200382fbabf52c177971225c0ea45319a772f6be42a8b099c818b1ce88bba017f8900061b1000027c752ef61af1ec61f41ba11ffb40f4fb6253469db722f57a38ca88f2acd98a7d486eb77ae94a3b80a5e37240879dd708a56d6bae9293af899fc288ab04207db8318ef957fb928c1308fe0be1f3b4660a4f06e61735dc598270d6e6500adff32599467596b83332e7a10ce38b42c1f0d04a4d1e12ad0ffaaef319de97c6d2ddad04f0e47f952c5bb924e6635aa696878ba4153afe362e9ac5f707b6d944033a9bafef4e4340af88c9da8ce0b922a9b2094092320c912afd20f57e456e17c52ec3acae731bddb200b669f69b557fe50e2842d9f68add54879b4f7f132c8882d2829389966ff98e9ad69c9472c4af68b765f63a5104fb04f0cecae0c8c1915015926eb47a7afe36854f8406a03b6f7be35528a2b6e01c546a95342767e092f0ee0956cd2268322428486364831d8a3ae8d1fc5cfe4125e91886a16b5fc6a563df8a8dea688f677c8b18fe030938fced39cdf6576d1a4017aa01aa13bde00b2cfda58f388a5b8f135c18c53e527e70fe9c2900db541cbaeea6b48437a05828865d34c9a9a05c4660e3d1e9f6c3852d646dcf270a945cfebc82d83cdbc80c59ae5d69913118007b71183eb3e13fd9161e19ed2f50b5f91d60968b2497fd67849bce826a5fb2cd9de5fb5e04df52d2a3f8cd7e4472533bf4669435efe0015d1d6d7492dea0f76330859ca32d71c38c388ac861f02f1e77b874f7db43fa444408b51787f1f92ed6a4d96b35ab75fd237c19fc4dd1df39d6264372d6b1efaf684776aaf98c7c832010e4473069e3049a8122d2701b61f0968dfece018a2efad783d8842ff94c828e8a7b3444f92a00d27e8731dee02b9f9b5edaa919a1265bd48518e709e34282cb94b6a635c6b482956eb46516361096e79255592cd0203a2141e29366e22d9259c60c9273e7a2c9e95d4b69507c57b91b465be0c1304021f8a2cee0d25c587ba82707b4fadff569d74bbdff5251d3d8872f2769e27de692c0de4993fc5d6fdffb6dba82603d5042edbc44c784a2287b48a359c992486350ca6a3ead2a4dd9e82f226e266ca114d7a0b0188061672850e16c5c886b3e5d34d991ae880a2193d26408ab68dae073a17e18cfee9f0e08fca3fb9f48d949e4e4cafd221736c38db13091dbcd16c9c9bb89d731b65f2b970323be4c0fb34a44fb3c622e6ab39a7f4c8aaf3512729aee55c9ba5a5de3f1ea6fb06afd96d3c99378390fae7a553ad832fa44d9470a6f4569bb86dcfc2f10bcbc6459ad9f97c174c559a416906a258197d20d82c550a5a0e79beedf230539daa5cafa782c9890cd040ccaaf68d7ec7a44f319e2375486766a63265cab324c4d56eef1172d0116d9802a0aad40eafd31ea924b11b5c9aeb43b38e73de8c5accbb3eacd320eca9483809d73b898f25c798047522344bbb210d6c7eadff0327b4c09c605b7f9f80b6e3cf0f8a675233ddbfc7de0332b6e8174a271f9a3cb8f0acb23b30e345cba48f8b59131aa4b57de6b7d12e8c275745ad6376c6f871689aaad7930c8f1d7c2024d0d73b308842fe826c6d6cdf1d3a05b3776e03a2963d8da9ef5f6bc15d5b9c9f67d3695f3b1a5748c96cc60ec8b2dabac816a400dfc917dddf2846c3f0855367bf6487408bf442a2652ba17011a71708356ad05a55600adccd07467287b6820546caf6e01274ac98617107a2752d5da76ce2ebe50e0b4345186faed59708d4524a8e8d07a8ead3d2d502cef7c32024b06e62defd69053a67aa9482453b22b445f3bae0e6550ff758c0da8c404a5a8cd860cd8cae21fa7731d96fb53c6aba0526c65d370fb0aec1e917ab4e1020a8d22c2a87e121305de81c3a754e415b8843eb7574a0b825807260f192c1af37bcc099269cba32aa72a9bbd5d33764b3a3470573987afea776b409d15a7dc9ee64c464e2038eebd75584fafa9bb000027100000000011e1a300000000000bebc200247997c9c34f9ff781e6eeb6a27867a89d63ac7da76c5dbb175c092f74803ebd39000000002b40420f0000000000220020aa081f2d59ff746a00257804e21092927e75f7530ad6c2cf00008e3359e18a6547522102790de7416ff75ad5028f42e81819043dd007565aa21267fba62a35615b7f2a3021029dc09099a99c869d47de24d4d9dc7aab0311496b0941421aa02baaf67c18a52b52aefd01af020000000001017997c9c34f9ff781e6eeb6a27867a89d63ac7da76c5dbb175c092f74803ebd3900000000000bcafd8004400d0300000000001600149a9b19387a6ad09813cbb83ef4f454291e70be49400d03000000000022002080f0c5ff9a0048c10092f2a37e9bca6940c5b75bf002d184ce79acc84e80c338286a04000000000022002098c0086284e401aca234212eddcc5f0438129b1db646935ef88be6436074c723e093040000000000220020bf6f1c0351aeb2a95ec7013f5adfcad06eecf6ec490bb9413b82c7cd032976600400473044022026b8117ceb8c1bfafd5f48f693244a486a8b8353757273840aba9360b1e63895022015823314a8315b2a5b56028c27dc5e6576e53616c303f49e5e1a7896b988b5d9014730440220312f27ed593dab7e9a8cc3dadbd54ae149f0b08d6b073f7a115a0f445b163b530220394be760728211085d5a97ddf7d4782c124592427abb6496b6f90ec9ff89b92d0147522102790de7416ff75ad5028f42e81819043dd007565aa21267fba62a35615b7f2a3021029dc09099a99c869d47de24d4d9dc7aab0311496b0941421aa02baaf67c18a52b52aed0b5f5200002022418f81a9801c2ef879bc892123c0d54874a35440132fe0094b1c7c264352f875a010000002b400d03000000000022002080f0c5ff9a0048c10092f2a37e9bca6940c5b75bf002d184ce79acc84e80c3388576a914861c563a4226e265ed1ca742307c7ace9444cc4e8763ac672103d9170264d4e477451c4acad19a32b905b7a6cddc24b40d10de12c7b2ceb59dec7c820120876475527c2102cbd109cb3882917fda2a8bb8c85db26efec6c3d3f078e86ca322b6067b0ffa9152ae67a914665582422423b9d14aca94b0420fba776a5d9a0888ac68685e020000000118f81a9801c2ef879bc892123c0d54874a35440132fe0094b1c7c264352f875a010000000000000000015af302000000000022002098c0086284e401aca234212eddcc5f0438129b1db646935ef88be6436074c723101b0600000000000000000140d70567f99e57bdc912717bd2f3574eb97007982dfd00f23e2ad65c0b936bc04277f388f63fa76b6e76088ed4ff9078cb19912aa7d4b6eaf193c2e55e051077124031b18ab87d66ccc4f03303805a4852a8dce66fee01f9e8f7863a8a0d84d676ce5b020800e520197afc71836b36fdbd3e67747d93229908fdfd8cd28f120d0fa4022418f81a9801c2ef879bc892123c0d54874a35440132fe0094b1c7c264352f875a030000002be093040000000000220020bf6f1c0351aeb2a95ec7013f5adfcad06eecf6ec490bb9413b82c7cd032976608576a914861c563a4226e265ed1ca742307c7ace9444cc4e8763ac672103d9170264d4e477451c4acad19a32b905b7a6cddc24b40d10de12c7b2ceb59dec7c820120876475527c2102cbd109cb3882917fda2a8bb8c85db26efec6c3d3f078e86ca322b6067b0ffa9152ae67a914f39c581de7435ed6fd334295d4bd4892c86127a988ac68685e020000000118f81a9801c2ef879bc892123c0d54874a35440132fe0094b1c7c264352f875a03000000000000000001fa7904000000000022002098c0086284e401aca234212eddcc5f0438129b1db646935ef88be6436074c723101b060000000000000000004089a4e5cecdfc3c25f4e4210f1a37d58a6b977c71afb91bac5e727f632a6d4a2330c71587d91c98fdad3a9b662409e7c75cf4c0d8ad180c342d4d3fb761fef31e403e925f9d57997647d41647a1f3d28024a902d175425c5d76e00925d13da452115255a02ca2164a58d2863583cff636006f1e5fe1c8273def053cbaaa5a8059d100000000000000010002fffd05aa7997c9c34f9ff781e6eeb6a27867a89d63ac7da76c5dbb175c092f74803ebd3900000000000000000000000011e1a300b5982a1ddd30d66654d27234f7f31921ee7011ed6290dd92043a50d6e1ab08b200061b1000038ec7647194925944599ae67df7bcf07dbf3453f249a07b3aaf4c61bb038be497080cf1e3f2854f1fbfcf5ca6be6284e402c89ba5bfc717e2d0c6fa1223be4277809ca9a2b2e625e83e06c561d7004a9725d3dc8c7e24f371592ab0954c020d1813984f96e70f39f057b404300f09487fb21cfa815dd8b5ceaf171155af7ea9a14e0711109f26fc854e7b2750c394f55c47193fd72f810c063a8506bc494403b52fdd14fa18005009b8f79c520f530c9fc2c9ac5f1f4f4926e3e671dd336d233c59878c2d04f309a118b9a71424d681a4d781df60cf89f3ca16aee95116a0a9445730b4ed1708ff296023c5c0f7a6e17d6b56b3916a1a32937f550680c6121e74c3a67717c98a5f97c3fcac8054a642476460678ed09b75da6d0a3825b8844b4babd3d29e8fdd09764f0415e95811b564a708e8b44084f336af96ad690f0207060234fe649a3b4f7c835fa391261ff13551079261dc5ad9ab29e3ded1561f1f8b5ea07c02b084a0c80ee49a4b137ebf201bef8ae39b74d20e39eb414be67dbad1b56d5b774a4897cb8a9703e76b69a8f64d154148e0e161f9b32f038cb1bff774dd1cbe5bc20a119cef8a1140960ced2681c9c03a4f45c08be8d553b986ec9a2bc4fa1e5ced2d40806ce87958145cf54fce170dcdc71902eede20cc7ac29235696a49c762b7ecaa2fd92e1e40cb2b951b67e6d076f7de966220571d9068c14153e9fc20ea078a81443114a2817bffe4d8b52b20476e09ece02185ebf1a35dad53680bda5c6e72cc78f35b0ac0758f0929800f1a4f7d2d8c3dee7eb43fd013dd08c2400be7a5b8431433423f85557f6dfbe81906fc1e5d365cf98ffa21af3bb8ce793a4a8531fe46978f6442a802e4c86967923e1466a40574e3f85e27c34267f31edeffca7e3309e810ba887afb09d17d2e6d6e2a806466767c16b5237bb49a84f9a2bd6cecfdfffca5059a78259323ab1b368639dc796e30bea42f942b68b6baf055398cdba211cc3af4e29a0648829cfd8cdfeb35cefef1e141da79a1bff107dbb6dc717fd9dce1b3c925cedcbf8f2cbf5f25d8fb8c301275ba70706395698ccd158eb6cff35343c60934bb6ee48b9edd2d3ff7625334954b61435f545d4ffddbf6264bc798857b244c4b8928d4967e0da4cca8077af0766e6e99975afe993d37f83363996df9d75cb59440a2c9411aa1e044f3b1739c7b59cae26984bdb49c661684705f38c363d0ee39e4ed2a297a1025a9cc1e972ec7963c1be54bb5e847feda1b14bbe118d617a1a41f1251c3ec8b2f8ddc7cad3a4d8a1d45af14e1c0c2a1bbcabae9df671a399894890bc49080ea75c9c4068bb89fb0eec586389efc0fff11704b69fd10bca02c06f764844334e58abba3b554f64c4e4e26529042a2a1ae9965846816d1e8a411fee4d27ce3fcb1776d40526aa1f83dfb583fa5e2e06c46197c5032c2c48c3f0e0136e601ca5f8ce508441ff2ddc7e46f7c58811071669c00bed4008e8c0dbd7ed2fe00e9e0a656aa22acd22485fba08efb3065a099fd6769a520a45a8f778f5dfdfef959a26d8e6b8d45df8e3641de245a83b0ed1a312159b728333ab457b72ffc2cec97fd6ce29275e754b7ac8aa122be7db85d7d0c3d7a67dc82a406301fd79936f4ff3e2ee491de4756144868304d1afed8df91580eeecb1af2446c035b2debb6ddf3f6a19fccfcbdf5d73df3420df8153d8a8307e37969a52978e673a0fc003d6dad6ba8269349ef1b8dfd61da4c8568f52033c352b0422b3d8298ddccc684708aa5797b32bce5b97236b7002c158b8c878b1bfa0c34b54636c295f71e2f281f19d5c6cc796a6c06cc56d9490a41911f279be3cf73de1dd214e1d344c26525456c4493dd28096c518d4a2aac87570145f577059c7f371e53df6d2cd90e7b75720a5da71bd29d264dfffd05aa7997c9c34f9ff781e6eeb6a27867a89d63ac7da76c5dbb175c092f74803ebd390000000000000001000000000bebc200382fbabf52c177971225c0ea45319a772f6be42a8b099c818b1ce88bba017f8900061b1000027c752ef61af1ec61f41ba11ffb40f4fb6253469db722f57a38ca88f2acd98a7d486eb77ae94a3b80a5e37240879dd708a56d6bae9293af899fc288ab04207db8318ef957fb928c1308fe0be1f3b4660a4f06e61735dc598270d6e6500adff32599467596b83332e7a10ce38b42c1f0d04a4d1e12ad0ffaaef319de97c6d2ddad04f0e47f952c5bb924e6635aa696878ba4153afe362e9ac5f707b6d944033a9bafef4e4340af88c9da8ce0b922a9b2094092320c912afd20f57e456e17c52ec3acae731bddb200b669f69b557fe50e2842d9f68add54879b4f7f132c8882d2829389966ff98e9ad69c9472c4af68b765f63a5104fb04f0cecae0c8c1915015926eb47a7afe36854f8406a03b6f7be35528a2b6e01c546a95342767e092f0ee0956cd2268322428486364831d8a3ae8d1fc5cfe4125e91886a16b5fc6a563df8a8dea688f677c8b18fe030938fced39cdf6576d1a4017aa01aa13bde00b2cfda58f388a5b8f135c18c53e527e70fe9c2900db541cbaeea6b48437a05828865d34c9a9a05c4660e3d1e9f6c3852d646dcf270a945cfebc82d83cdbc80c59ae5d69913118007b71183eb3e13fd9161e19ed2f50b5f91d60968b2497fd67849bce826a5fb2cd9de5fb5e04df52d2a3f8cd7e4472533bf4669435efe0015d1d6d7492dea0f76330859ca32d71c38c388ac861f02f1e77b874f7db43fa444408b51787f1f92ed6a4d96b35ab75fd237c19fc4dd1df39d6264372d6b1efaf684776aaf98c7c832010e4473069e3049a8122d2701b61f0968dfece018a2efad783d8842ff94c828e8a7b3444f92a00d27e8731dee02b9f9b5edaa919a1265bd48518e709e34282cb94b6a635c6b482956eb46516361096e79255592cd0203a2141e29366e22d9259c60c9273e7a2c9e95d4b69507c57b91b465be0c1304021f8a2cee0d25c587ba82707b4fadff569d74bbdff5251d3d8872f2769e27de692c0de4993fc5d6fdffb6dba82603d5042edbc44c784a2287b48a359c992486350ca6a3ead2a4dd9e82f226e266ca114d7a0b0188061672850e16c5c886b3e5d34d991ae880a2193d26408ab68dae073a17e18cfee9f0e08fca3fb9f48d949e4e4cafd221736c38db13091dbcd16c9c9bb89d731b65f2b970323be4c0fb34a44fb3c622e6ab39a7f4c8aaf3512729aee55c9ba5a5de3f1ea6fb06afd96d3c99378390fae7a553ad832fa44d9470a6f4569bb86dcfc2f10bcbc6459ad9f97c174c559a416906a258197d20d82c550a5a0e79beedf230539daa5cafa782c9890cd040ccaaf68d7ec7a44f319e2375486766a63265cab324c4d56eef1172d0116d9802a0aad40eafd31ea924b11b5c9aeb43b38e73de8c5accbb3eacd320eca9483809d73b898f25c798047522344bbb210d6c7eadff0327b4c09c605b7f9f80b6e3cf0f8a675233ddbfc7de0332b6e8174a271f9a3cb8f0acb23b30e345cba48f8b59131aa4b57de6b7d12e8c275745ad6376c6f871689aaad7930c8f1d7c2024d0d73b308842fe826c6d6cdf1d3a05b3776e03a2963d8da9ef5f6bc15d5b9c9f67d3695f3b1a5748c96cc60ec8b2dabac816a400dfc917dddf2846c3f0855367bf6487408bf442a2652ba17011a71708356ad05a55600adccd07467287b6820546caf6e01274ac98617107a2752d5da76ce2ebe50e0b4345186faed59708d4524a8e8d07a8ead3d2d502cef7c32024b06e62defd69053a67aa9482453b22b445f3bae0e6550ff758c0da8c404a5a8cd860cd8cae21fa7731d96fb53c6aba0526c65d370fb0aec1e917ab4e1020a8d22c2a87e121305de81c3a754e415b8843eb7574a0b825807260f192c1af37bcc099269cba32aa72a9bbd5d33764b3a3470573987afea776b409d15a7dc9ee64c464e2038eebd75584fafa9bb00002710000000000bebc2000000000011e1a300e88c922c64297e36d03de41bfe52a7d1d8898ef4ee5152fd79e112d217ff20ca0378f690d4be831f1af3ad558fbb2ebf72976262657a31f34b6c5a654ee07de5b800000000000000000000000000000000000000020000000000000000000200000000000000000003c1a3d97b25374e3ca3aba318297d0e7800000000000000010003519b0c1acfcd44e98d240aae9f6d575eff03c16438e110e8f196905ccc0feee52086773b5b63815a4b8524db2c4bc59005d3247997c9c34f9ff781e6eeb6a27867a89d63ac7da76c5dbb175c092f74803ebd39000000002b40420f0000000000220020aa081f2d59ff746a00257804e21092927e75f7530ad6c2cf00008e3359e18a6547522102790de7416ff75ad5028f42e81819043dd007565aa21267fba62a35615b7f2a3021029dc09099a99c869d47de24d4d9dc7aab0311496b0941421aa02baaf67c18a52b52ae000100400000ffffffffffff0020ab16d22ee6ffe5e30b4f77d4cb61282e8e2489fe2fbf13ca6c4d3a5263b91ae780007fffffffffff807997c9c34f9ff781e6eeb6a27867a89d63ac7da76c5dbb175c092f74803ebd39061a8000002a00000000884bf9f9a48c2cb42b70045b55dfab8c1e14a1538a7b504810ee4973f274aa673e049baa1586f2acf25a831494d401d05af8cdf57c570a75ed38946568d1bdcda306226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f061a8000002a000060db51bf0101009000000000000003e8000854d00000000a000000003b9aca000000" + test("encode/decode map of spending txs") { val map = Map( OutPoint(randomBytes32(), 42) -> Transaction.read("020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219701000000000000000001d0070000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100d5275b3619953cb0c3b5aa577f04bc512380e60fa551762ce3d7a1bb7401cff9022037237ab0dac3fe100cde094e82e2bed9ba0ed1bb40154b48e56aa70f259e608b01483045022100c89172099507ff50f4c925e6c5150e871fb6e83dd73ff9fbb72f6ce829a9633f02203a63821d9162e99f9be712a68f9e589483994feae2661e4546cd5b6cec007be501008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868f6010000"), @@ -17,4 +23,47 @@ class ChannelCodecs2Spec extends AnyFunSuite { assert(spentMapCodec.decodeValue(spentMapCodec.encode(map).require).require === map) } + test("remove signatures from commitment txs") { + val commitments = stateDataCodec.decode(dataNormal.bits).require.value.commitments + commitments.localCommit.commitTxAndRemoteSig.commitTx.tx.txIn.foreach(txIn => assert(txIn.witness.isNull)) + assert(commitments.localCommit.htlcTxsAndRemoteSigs.nonEmpty) + commitments.localCommit.htlcTxsAndRemoteSigs.foreach { + case HtlcTxAndRemoteSig(htlcTx, remoteSig) => + assert(remoteSig !== ByteVector64.Zeroes) + htlcTx.tx.txIn.foreach(txIn => assert(txIn.witness.isNull)) + } + } + + test("split channel version into channel config and channel features") { + { + // Standard channel + val commitments = stateDataCodec.decode(dataNormal.bits).require.value.commitments + assert(commitments.channelConfig === ChannelConfig.standard) + assert(commitments.channelFeatures === ChannelFeatures(Features.empty)) + } + { + val staticRemoteKeyChannel = hex"000200000003039dc0e0b1d25905e44fdf6f8e89755a5e219685840d0bc1d28d3308f9628a358500092eaee758da6a5beab04167c5b889732a98ec556cf92689da41744b10159ace488000000000000000000003e8ffffffffffffffff0000000000004e2000000000000003e80090001e001600140a46fc26a4ef7b50aaf16759d2f0ca8b014fd0a6028feba10d0eafd0fad8fe20e6d9206e6bd30242826de05c63f459a00aced24b120000000302698203af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d0000000000000044c000000001dcd6500000000000000271000000000000000000090006402a87d8f1844a3809284885b41cfc9713a561780b2b784a7a027851f68aaa838730328e2fd423323abcd86b15335ff44679068acd4db71b8052f63a2a7f0b47432bd028feba10d0eafd0fad8fe20e6d9206e6bd30242826de05c63f459a00aced24b12025ee0984b83efe2369c23c3754e89d19905f8094a86486de3b241cf8735208292031163dfd4eee879b9a333290cf10e52eb010d1da45bd44efee6fab16d1035120c0000186b02000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000269820000000000000000010001fffd05aab125c87884677613fe191e097b3806667482e0ff7668c64bca637300fe02307200000000000000000000000002faf080a2fbf30338f78b739d2b0a0a806506d6877408a995791901c43f6ef98d8a7a2e00061b10000223bb38126f1131ad95b37e0b30adf0ae315f66f8cf7460afccba393fa848d036073b6577f39b0e54043691e2eec74f46b86584ffab7aa01d15e6a7ff5ff9d7c60d095c7952a19e263c267f94fbe0208fdd212b304fa8d3df1379017d184fbcce7ffd5175997774c9de7ef2f3733c7056a22888c9f302e4a9bac35f581d4f571a7ef29e6cf3b1d8a7cc05cd155a047ae498a18fccb6589338242d91e00493a86b2b78810de20644f0f065399894854bd7347927a0ce8f9c1102c9c7227ffba4b71c4325f301933397aa8f54a28d3844c71021d19b72795141af019c9a0abae3e2cc29033b7f8be8465ac8406d4c1d61d0bc3c87dee2faac4d649fc166917c81a1e237c91d5c8091f696a72241c67047cfcec893bb41e97753b3d362695bc31f776cd4a744bf09f2e428dc189fa4838f34d130d5fd4fc3d79bc7546d30d543ca99818a8c39f6c17e74ff45976c95fd6de261d68e2c5fc585d28fa9ae5abb1db475f24d16d9cb0562a589c6dec07f3b81a37b4c7f2c4642df1fabf5d7343e038706e786d2cea71f76663f57a88751287db3f8e9a7a896990374daf72eb776ee67c9ec61c1a10f164688ecbc88caa086e24daa79808899e3c3f6870ed326f5253fc038147774fa8795351f44cbadc53b67743822b89f27f0c03b2b0f4e4c774598db681cadde0e4e28f1f4edbbc8ec68649fe05e3c8cdbc46b07011c9b81b9cb3ad92ec08ddf8209a501ce064ee0bd9befce196c0c559cffcb3b0c447922ad50fbceb288037ec36abb17d93deebd40e83d8c197248b14ee12dcfe72e46f0826ef1e6e94700ad6428df77b54eb8ca767af1fd84507adc52beef32f48e3968c24d990d04cd8b35c87e270a04e2e8fc9d0ab6a6398c2168986321cc0dc297f8e0e4887226db20958e934bc35151de72d896511e3b850d548402a0e1f32fb01d5050a8ff9d7ba6818600a2dafa1c4b3ca11c0d091796190c05a1edc8146d7ef534d67bb39f76f37279dff6be5e053b7315c3f3406e9240c6b618b18545fccb46d668c007a1b640634c663bbd27c5d00a48f5c3702bac6202b55cce4212920be059ecf708db85f07c0fcc0f480fcd06aef1128fc491b2b384d89ce15120c81bd65c225d679f8cd38fb1b0ddb7d8edd3455506fa3406578f43ba4caab00593b6623b7b69c863409f0350b8b8783b9ee2ada9b9f410ab309527b7f67758f543261f52695da19e597f9457483d188e8a2bd7248a6ee5f2d01cccacd2561239959f0ce80cbb83503891a8b4e3ab88d3a02de5f8c784c0700e8bef68445615f25dd3d5baea34bc8e694a7282b054f99fa2b150af00ec62c2fd2227a23e0c0a36090b5986271f964e969cc9313ef42c3b7a9ff9e608342c73da023019e983ee6b6f630e12a02ae79802bfbee10ba20199895d7f1302d2c3b8d7ba6ad08cbb27265f9bb709bf6225a81bdabf7fc5fe9ee4794b00181038051cae0a52c08d8a7750ee46c99b7dd6cd452c52a347f12873dc697786b89ddd13e2d584aa3244cdbdb7809bc7a42728d756485070ff84aaba81b1506a7b20d2acfef11df724ee78e8b0f92376676d1e01aed7ad8d84919b8296684dafc152680b0f964db5a1b7ee4093f453fe24750c6857252e4befef3df2d648e32cc20c1ef48fd5865c242951cd359f92985680f0d626602f164b6285971a194a4baf4eb1d9a3e88841269b3e84a96e6784407f869bca5db2bb2d31e784c35e210dcaf5d3441edea9a4a348e3ead6c9b920e1ff8f1c76c41d564827cd3db006e741a8dbb5db9bdbe13033faac7e6b676bc1cf88677d10676ddca3e187caeeac52b69212503f7acca29a49c699d68f0d982c4c41806c8d9bbbc4e7d2d77a1a0655c73cfcd21b93d95e70d2bad6c306dc172b67120335bd2d03878b3d9e5967c54dd567d307c15e305b5200002710000000000bebc200000000002cb4178024b125c87884677613fe191e097b3806667482e0ff7668c64bca637300fe023072000000002b40420f000000000022002090750411c84891017ca3ab8e6212a198dbf957143d6753f7b48264c7faf45cf44752210257f2ca56049071462716385481d04d3c7c859d7e9b3f31c019b126aa0eeed1e82102a87d8f1844a3809284885b41cfc9713a561780b2b784a7a027851f68aaa8387352aefd018602000000000101b125c87884677613fe191e097b3806667482e0ff7668c64bca637300fe023072000000000036a2ab800350c300000000000022002042ee833f99ec58f28a3ffc13033c29d4e02f72f6169e0e8e039ad9323e912e30400d030000000000220020981c45edf62e238461337b25d5fd7de72a305e7f4c7755a693f877b1a564831cb04e0b0000000000160014761879f7b274ce995f87150a02e75cc0c037e8e30400483045022100dc11d7761cdf7c708ea2aaaed409a4d24697bb07948704e77b7cbe911d0268fd02204a81721e85bb1ff6cb34741ed5c5ee89c4ea8f5bb1515792625d32043682c14301483045022100af2d760500664483ea81ccaa3bc925c5587cd3cd4358ee27110f62929903c49602201ce1a97498227eaef1ab5c04da2eb9ea8114abe6af1bd35494d9cbb0e64da3e7014752210257f2ca56049071462716385481d04d3c7c859d7e9b3f31c019b126aa0eeed1e82102a87d8f1844a3809284885b41cfc9713a561780b2b784a7a027851f68aaa8387352aead678920000101241bfa1a637efc49b76b41be6ec36c47c546f69b1646a11e8420957a1dbf731487000000002b50c300000000000022002042ee833f99ec58f28a3ffc13033c29d4e02f72f6169e0e8e039ad9323e912e308b76a91420fe4794a7f993489c0477c442fed3be2c9294678763ac67210230714b6385889be70b5299603fbc508a01517a3b754d03e33e5bcf125104adc57c8201208763a914e5a9c4cf993e5053ca47ddd2a194a3c522c1fdc588527c2103bff704aae937cd0b98704a4791140cab835c5bd2406897104602c881352869ae52ae677503101b06b175ac68685e02000000011bfa1a637efc49b76b41be6ec36c47c546f69b1646a11e8420957a1dbf73148700000000000000000001daa7000000000000220020981c45edf62e238461337b25d5fd7de72a305e7f4c7755a693f877b1a564831c00000000a2fbf30338f78b739d2b0a0a806506d6877408a995791901c43f6ef98d8a7a2e000000000000000040c5c678fda30c1e23bbb68093b1d9a34169304ff3d36bfef74157f129df8d01e31ddcd8dd04f3ac9521ac13b7fed5d7997ce0c72c942054711d924ea197c7ab7640cd0045f41ffd38fa637cfc09679c628b29a8273223db1150e262d5159cf5d7852542c4ad7334f67d08ca835cd5b16b1df96544c454dff121856685471d2255300000000000000001000100fd05aab125c87884677613fe191e097b3806667482e0ff7668c64bca637300fe02307200000000000000000000000002faf080a2fbf30338f78b739d2b0a0a806506d6877408a995791901c43f6ef98d8a7a2e00061b10000223bb38126f1131ad95b37e0b30adf0ae315f66f8cf7460afccba393fa848d036073b6577f39b0e54043691e2eec74f46b86584ffab7aa01d15e6a7ff5ff9d7c60d095c7952a19e263c267f94fbe0208fdd212b304fa8d3df1379017d184fbcce7ffd5175997774c9de7ef2f3733c7056a22888c9f302e4a9bac35f581d4f571a7ef29e6cf3b1d8a7cc05cd155a047ae498a18fccb6589338242d91e00493a86b2b78810de20644f0f065399894854bd7347927a0ce8f9c1102c9c7227ffba4b71c4325f301933397aa8f54a28d3844c71021d19b72795141af019c9a0abae3e2cc29033b7f8be8465ac8406d4c1d61d0bc3c87dee2faac4d649fc166917c81a1e237c91d5c8091f696a72241c67047cfcec893bb41e97753b3d362695bc31f776cd4a744bf09f2e428dc189fa4838f34d130d5fd4fc3d79bc7546d30d543ca99818a8c39f6c17e74ff45976c95fd6de261d68e2c5fc585d28fa9ae5abb1db475f24d16d9cb0562a589c6dec07f3b81a37b4c7f2c4642df1fabf5d7343e038706e786d2cea71f76663f57a88751287db3f8e9a7a896990374daf72eb776ee67c9ec61c1a10f164688ecbc88caa086e24daa79808899e3c3f6870ed326f5253fc038147774fa8795351f44cbadc53b67743822b89f27f0c03b2b0f4e4c774598db681cadde0e4e28f1f4edbbc8ec68649fe05e3c8cdbc46b07011c9b81b9cb3ad92ec08ddf8209a501ce064ee0bd9befce196c0c559cffcb3b0c447922ad50fbceb288037ec36abb17d93deebd40e83d8c197248b14ee12dcfe72e46f0826ef1e6e94700ad6428df77b54eb8ca767af1fd84507adc52beef32f48e3968c24d990d04cd8b35c87e270a04e2e8fc9d0ab6a6398c2168986321cc0dc297f8e0e4887226db20958e934bc35151de72d896511e3b850d548402a0e1f32fb01d5050a8ff9d7ba6818600a2dafa1c4b3ca11c0d091796190c05a1edc8146d7ef534d67bb39f76f37279dff6be5e053b7315c3f3406e9240c6b618b18545fccb46d668c007a1b640634c663bbd27c5d00a48f5c3702bac6202b55cce4212920be059ecf708db85f07c0fcc0f480fcd06aef1128fc491b2b384d89ce15120c81bd65c225d679f8cd38fb1b0ddb7d8edd3455506fa3406578f43ba4caab00593b6623b7b69c863409f0350b8b8783b9ee2ada9b9f410ab309527b7f67758f543261f52695da19e597f9457483d188e8a2bd7248a6ee5f2d01cccacd2561239959f0ce80cbb83503891a8b4e3ab88d3a02de5f8c784c0700e8bef68445615f25dd3d5baea34bc8e694a7282b054f99fa2b150af00ec62c2fd2227a23e0c0a36090b5986271f964e969cc9313ef42c3b7a9ff9e608342c73da023019e983ee6b6f630e12a02ae79802bfbee10ba20199895d7f1302d2c3b8d7ba6ad08cbb27265f9bb709bf6225a81bdabf7fc5fe9ee4794b00181038051cae0a52c08d8a7750ee46c99b7dd6cd452c52a347f12873dc697786b89ddd13e2d584aa3244cdbdb7809bc7a42728d756485070ff84aaba81b1506a7b20d2acfef11df724ee78e8b0f92376676d1e01aed7ad8d84919b8296684dafc152680b0f964db5a1b7ee4093f453fe24750c6857252e4befef3df2d648e32cc20c1ef48fd5865c242951cd359f92985680f0d626602f164b6285971a194a4baf4eb1d9a3e88841269b3e84a96e6784407f869bca5db2bb2d31e784c35e210dcaf5d3441edea9a4a348e3ead6c9b920e1ff8f1c76c41d564827cd3db006e741a8dbb5db9bdbe13033faac7e6b676bc1cf88677d10676ddca3e187caeeac52b69212503f7acca29a49c699d68f0d982c4c41806c8d9bbbc4e7d2d77a1a0655c73cfcd21b93d95e70d2bad6c306dc172b67120335bd2d03878b3d9e5967c54dd567d307c15e305b5200002710000000002cb41780000000000bebc200b5c42b5c67eb6043b2776c0e9a8bd91d8ffdf7280b81dce825558ad5258746fb030d9b4186ad4f32aae9ca90701dc66e8df3f7b73e1f9590c0f6cd690aae8ad097000000000000000000000000000000000000000000000000000000010000ff0250b3cac5267f9448efcb8c2a89944f8bb4e4e7f1a3ae72d4961dfe187105b3f324b125c87884677613fe191e097b3806667482e0ff7668c64bca637300fe023072000000002b40420f000000000022002090750411c84891017ca3ab8e6212a198dbf957143d6753f7b48264c7faf45cf44752210257f2ca56049071462716385481d04d3c7c859d7e9b3f31c019b126aa0eeed1e82102a87d8f1844a3809284885b41cfc9713a561780b2b784a7a027851f68aaa8387352ae000100400000ffffffffffff0020b0e394cbe4d79e47a73926b89d17d8835e8f042d7adb244a84b6c30496c6355f80007fffffffffff80b125c87884677613fe191e097b3806667482e0ff7668c64bca637300fe023072061a8000002a00000000888bc37730827b0ccb54f13689babb8d211a6cbbec55113caad14a97f6bfce7d85313c9cefa398d15213abf841e3c9a8b88f62fa3fc742a6c746619d3e76aa71fb06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f061a8000002a000060e32b8d010000900000000000000000000854d00000000a000000003b9aca000000" + val commitments = stateDataCodec.decode(staticRemoteKeyChannel.bits).require.value.commitments + assert(commitments.channelConfig === ChannelConfig.standard) + assert(commitments.channelFeatures === ChannelFeatures(Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory))) + } + { + val anchorOutputsChannel = hex"00020000000703af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d000094b82816a3f15ab3e324ddf6f0f5a116cef8c3200a353240db684337d2dcaa81e80000001000000000000044c000000001dcd65000000000000002710000000000000000000900064ff160014f09f6f93de60c6af417ed31d52c2c883b6113f260000186b0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000225982039dc0e0b1d25905e44fdf6f8e89755a5e219685840d0bc1d28d3308f9628a358500000000000003e8ffffffffffffffff0000000000004e2000000000000003e80090001e02e9cd12509fbc345c10e2b2e10427ae43eabbf0788bda8118b34344e5576d5d5002eb112dd8d61a02bf3434d5417e1020ed2d951e75bd1e14615d473dd5f1991b4402ca8b6891d6a53aea035259fa0d57a1e672d3cc59a2c696a96247d3557ee726cd030f2aa0ca0ae0e00d6da847d897cf7288ae286dd12037f06250937dc7b37fe0b5028d59e68c5799980a98b120acb891dabfd091e80c6596b0c2a7de57a298d48512000000032259820000000000000000000000000009c4000000002faf0800000000000bebc2002432e16e6e875d1f02a9512943ba2eb9309f4715390b52e4965dd6e39a2989ae63000000002b40420f00000000002200204de81ee2e9850dabdb9c364a12cc2dd75dd20c21d4ca0a809a2e6af3caeeaf9647522102e9cd12509fbc345c10e2b2e10427ae43eabbf0788bda8118b34344e5576d5d502103683425bf640d5e4338553ca6f6afea948ff2bf378eed1b3e9497a3f18f477d5352aefd01bc0200000000010132e16e6e875d1f02a9512943ba2eb9309f4715390b52e4965dd6e39a2989ae63000000000089273f80044a010000000000002200202102815a0b14d50a2a8c028cd47413e743f02ad5460151bd4ae5930639cbbd484a010000000000002200204e81bd6470c45c349068c2b0ad544934b718ef49c1615f2a0b0cd8766400b562400d0300000000002200208eadab92c95168438acfb73f2d3d92f56d6fc30d3f79fc4527f4e77df88b8add72270c0000000000220020895c0fef7efb7aa0e4805381434363b30386d31f6cb621abb0ca4a52b269fbc304004830450221009f09a584b1af9ba86f1618aad1ca9817324f97c3d3789b2490c3b9435c7bf1dd02205e4dcca3d7937f6b80a0ed39084e4ea9e9c531b80698ebd0c116fd9319a0db1201473044022001e4104ec25ba9bc43bf3f623d6e98f371434442b134255667ddeedd9cd9cd1b02201f057d2c15ee4e5d62a73f1db262846ca7ad35deaed2496abdb5b16cd91c5cb80147522102e9cd12509fbc345c10e2b2e10427ae43eabbf0788bda8118b34344e5576d5d502103683425bf640d5e4338553ca6f6afea948ff2bf378eed1b3e9497a3f18f477d5352ae25899520000000000000000000000000000009c4000000000bebc200000000002faf0800acdb0bf90ed7350d429c2d70fce66300ac9b27d8dbb3bceb5bd2cc8bd443938f03f54d00601f1b95e82836540d760ab6b38bc6fc1701df5d5e3b410f1936212d54000000000000000000000000000000000000000000000000000000000000ff03658b1e02efd8464688f87a052518691ff4d818a7c36addf4c6520ae07b7e20912432e16e6e875d1f02a9512943ba2eb9309f4715390b52e4965dd6e39a2989ae63000000002b40420f00000000002200204de81ee2e9850dabdb9c364a12cc2dd75dd20c21d4ca0a809a2e6af3caeeaf9647522102e9cd12509fbc345c10e2b2e10427ae43eabbf0788bda8118b34344e5576d5d502103683425bf640d5e4338553ca6f6afea948ff2bf378eed1b3e9497a3f18f477d5352ae00000032e16e6e875d1f02a9512943ba2eb9309f4715390b52e4965dd6e39a2989ae63061a8000002a000000008833a9275befbedbf928e743a71c9476f46528baf6e832ad25478c2607e4108dea67657c51db7344032627c855518179dc7a94b59f306faf9a3b4eef7684d5d73106226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f061a8000002a000060e32a590101009000000000000003e8000854d00000000a000000003b9aca000000" + val commitments = stateDataCodec.decode(anchorOutputsChannel.bits).require.value.commitments + assert(commitments.channelConfig === ChannelConfig.standard) + assert(commitments.channelFeatures === ChannelFeatures(Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory, Features.AnchorOutputs -> FeatureSupport.Mandatory))) + } + { + val wumboChannel = hex"00000000000103af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d00009b3b6da18b7e5e43405415a43e61abd77edf4d300c131af17a955950837d7db4980000001000000000000044c000000001dcd65000000000000002710000000000000000000900064ff1600142946bdbb3141be9a40ed8133ef159ee0022176e90000186b02000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a4982039dc0e0b1d25905e44fdf6f8e89755a5e219685840d0bc1d28d3308f9628a358500000000000003e8ffffffffffffffff0000000000004e2000000000000003e80090001e03a6be2613a9ff761a769b077abb43d76c5f06944fc32010891156179bf8b81607025c24f0c3572631ea09dade913ccbb1cd8a55585fc152cc2165bf4f43af702c8b0306c241c667eefee031639052c4a35c4ae076ab56b49f76f7e12f63dfb7bb32a503fdbff1ca92c99c09a9c0eac3cfe1ca35deb658d5fdb815fe7a240df25da5e20f02c47d9e4e1fb501c81933cfc4d3ba7f0e8203003cda2683569f1f67aac2b6e20d000000030a4982000000000000000000000000002710000000745e66c600000000000bebc2002464fc06b43e94d9ce0a5a0e1ef13f63ba78e56d920e7d606d0c6e7a631c48c32c000000002b0065cd1d00000000220020d1b88cc50b09abce945ce6703f30dae3d71ec626be2201925db9379a3e8b981047522103044d393b0da6bcd92030f3131d0780b0d6fa017a0ff45b7fed708903c2950ced2103a6be2613a9ff761a769b077abb43d76c5f06944fc32010891156179bf8b8160752aefd01590200000000010164fc06b43e94d9ce0a5a0e1ef13f63ba78e56d920e7d606d0c6e7a631c48c32c000000000018e2678002400d030000000000160014cf3c9d08ec27d3ffefbf7e2640d3773e11a60ceb783bca1d00000000220020153e514ade07f347db9ed32cf6b2096d066d48e0cf8c504590cd1155de68be2204004730440220056ac83d5b2eb9eb9714e85294d080a9b64f73916d8206b15533613b4303ed3d022044da40ba433e27ddb8743c75acc0f6b050c346bc9d241ab3a1b65051820ce06a0147304402207683c10f33682e2389e5f02dd34090bbe4d786633d49e1777d5ec2ab71a7924902206d99dceb74ff809237ae8e06c118b38047e2d4a49f9956c538bedca47cb4b1da0147522103044d393b0da6bcd92030f3131d0780b0d6fa017a0ff45b7fed708903c2950ced2103a6be2613a9ff761a769b077abb43d76c5f06944fc32010891156179bf8b8160752aef5c4602000000000000000000000000000002710000000000bebc200000000745e66c6000bdebd6cf39db4c2ce1a675281b0a714e525525149032ba048d75c89e5a9b355021a87b53e12e4b36287635cdf887991a732c2fd56b12f5377d755b8b890795db6000000000000000000000000000000000000000000000000000000000000ff032c5f941f78a00105b995fba7f3c340afe53e287a60361135386ae1448d6f3d632464fc06b43e94d9ce0a5a0e1ef13f63ba78e56d920e7d606d0c6e7a631c48c32c000000002b0065cd1d00000000220020d1b88cc50b09abce945ce6703f30dae3d71ec626be2201925db9379a3e8b981047522103044d393b0da6bcd92030f3131d0780b0d6fa017a0ff45b7fed708903c2950ced2103a6be2613a9ff761a769b077abb43d76c5f06944fc32010891156179bf8b8160752ae00000064fc06b43e94d9ce0a5a0e1ef13f63ba78e56d920e7d606d0c6e7a631c48c32cff5e020000000101010101010101010101010101010101010101010101010101010101010101012a00000000ffffffff010065cd1d00000000220020d1b88cc50b09abce945ce6703f30dae3d71ec626be2201925db9379a3e8b9810000000000000000060e32bf9000082000000000000000000000000000000000000000000000000000000000000000064fc06b43e94d9ce0a5a0e1ef13f63ba78e56d920e7d606d0c6e7a631c48c32c0000f4957823b0a6b76697d7c545bcca66abec8522a0f6350a722e45f3f2830f7f824c84efed8daffa04bb0822ce1a08fb2de77dd20c23a91cced7a3966808956644" + val commitments = stateDataCodec.decode(wumboChannel.bits).require.value.commitments + assert(commitments.channelConfig === ChannelConfig.standard) + assert(commitments.channelFeatures === ChannelFeatures(Features(Features.Wumbo -> FeatureSupport.Mandatory))) + } + } + + test("ensure remote shutdown script is not set") { + val commitments = stateDataCodec.decode(dataNormal.bits).require.value.commitments + assert(commitments.remoteParams.shutdownScript === None) + } + } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3Spec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3Spec.scala index ddad8eb4b8..5851bef80d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3Spec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3Spec.scala @@ -1,8 +1,10 @@ package fr.acinq.eclair.wire.internal.channel.version3 +import fr.acinq.eclair.channel.{ChannelConfigOption, ChannelConfig} import fr.acinq.eclair.wire.internal.channel.ChannelCodecsSpec.normal -import fr.acinq.eclair.wire.internal.channel.version3.ChannelCodecs3.Codecs.DATA_NORMAL_Codec +import fr.acinq.eclair.wire.internal.channel.version3.ChannelCodecs3.Codecs.{DATA_NORMAL_Codec, channelConfigCodec} import org.scalatest.funsuite.AnyFunSuite +import scodec.bits.HexStringSyntax class ChannelCodecs3Spec extends AnyFunSuite { @@ -14,4 +16,43 @@ class ChannelCodecs3Spec extends AnyFunSuite { 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])) + assert(channelConfigCodec.decode(hex"01f0".bits).require.value === ChannelConfig(Set.empty[ChannelConfigOption])) + assert(channelConfigCodec.decode(hex"020000".bits).require.value === ChannelConfig(Set.empty[ChannelConfigOption])) + + assert(channelConfigCodec.encode(ChannelConfig.standard).require.bytes === hex"0101") + assert(channelConfigCodec.encode(ChannelConfig(ChannelConfig.FundingPubKeyBasedChannelKeyPath)).require.bytes === hex"0101") + assert(channelConfigCodec.decode(hex"0101".bits).require.value === ChannelConfig(ChannelConfig.FundingPubKeyBasedChannelKeyPath)) + assert(channelConfigCodec.decode(hex"01ff".bits).require.value === ChannelConfig(ChannelConfig.FundingPubKeyBasedChannelKeyPath)) + assert(channelConfigCodec.decode(hex"020001".bits).require.value === ChannelConfig(ChannelConfig.FundingPubKeyBasedChannelKeyPath)) + } + + test("decode all known channel configuration options") { + import scala.reflect.ClassTag + import scala.reflect.runtime.universe._ + import scala.reflect.runtime.{universe => runtime} + val mirror = runtime.runtimeMirror(ClassLoader.getSystemClassLoader) + + def extract[T: TypeTag](container: T)(implicit c: ClassTag[T]): Set[ChannelConfigOption] = { + typeOf[T].decls.filter(_.isPublic).flatMap(symbol => { + if (symbol.isTerm && symbol.isModule) { + mirror.reflectModule(symbol.asModule).instance match { + case f: ChannelConfigOption => Some(f) + case _ => None + } + } else { + None + } + }).toSet + } + + val declaredOptions = extract(ChannelConfig) + assert(declaredOptions.nonEmpty) + val encoded = channelConfigCodec.encode(ChannelConfig(declaredOptions)).require + val decoded = channelConfigCodec.decode(encoded).require.value + assert(decoded.options === declaredOptions) + } + } diff --git a/eclair-node/src/main/scala/fr/acinq/eclair/api/serde/JsonSerializers.scala b/eclair-node/src/main/scala/fr/acinq/eclair/api/serde/JsonSerializers.scala index b8cdb2f9a9..03aad09012 100644 --- a/eclair-node/src/main/scala/fr/acinq/eclair/api/serde/JsonSerializers.scala +++ b/eclair-node/src/main/scala/fr/acinq/eclair/api/serde/JsonSerializers.scala @@ -135,10 +135,10 @@ class PrivateKeySerializer extends CustomSerializer[PrivateKey](_ => ( { case _: PrivateKey => JString("XXX") })) -class ChannelVersionSerializer extends CustomSerializer[ChannelVersion](_ => ( { +class ChannelConfigSerializer extends CustomSerializer[ChannelConfig](_ => ( { null }, { - case x: ChannelVersion => JString(x.bits.toBin) + case x: ChannelConfig => JArray(x.options.toList.map(o => JString(o.name))) })) class ChannelOpenResponseSerializer extends CustomSerializer[ChannelOpenResponse](_ => ( { @@ -448,7 +448,7 @@ object JsonSupport extends Json4sSupport { new InetSocketAddressSerializer + new OutPointSerializer + new OutPointKeySerializer + - new ChannelVersionSerializer + + new ChannelConfigSerializer + new ChannelOpenResponseSerializer + new CommandResponseSerializer + new InputInfoSerializer +