Correctly fill PSBT for taproot `interactive-tx` (#3169)
What changed, and why it matters
This commit fixes a bug in Eclair's dual-funded Lightning channel logic. When opening or splicing a channel using taproot (P2TR) inputs, each participant's wallet needs full details about every input in the transaction, not just its own. Previously, the PSBT (a data structure used to pass transaction signing information to the wallet) only included the shared splice input, but omitted the other peer's inputs. That caused the local wallet to fail when signing taproot inputs, because it couldn't compute the signature that covers all inputs. The fix adds all remote inputs to the PSBT before signing. The rest of the diff is mostly test cleanup: separate wallets for Alice and Bob and removal of an unused test helper.
Review and merge the fix, then ensure test coverage includes separate wallets for each party in taproot dual-funding and splice scenarios. Operators running nodes with taproot wallet inputs and dual-funding/splicing enabled should upgrade to avoid failed channel opens or splices.
Security signals we found
Fixes a signing failure in taproot interactive dual-funding/splicing
PSBT now includes all remote inputs, satisfying BIP-341 all-inputs signature commitment
Previously tests hid the bug by sharing a single dummy wallet between both parties
No direct funds-loss or theft path shown; primarily a liveness/DoS issue for channel open/splice
Error message improved to include failing PSBT input position
Evidence from the diff
In InteractiveTxBuilder.scala, the code that builds the PSBT for wallet signing was updated. Previously it conditionally added only unsignedTx.sharedInput_opt to the PSBT for splices. Now it folds over unsignedTx.remoteInputs plus the optional shared input, calling psbt.updateWitnessInput for each, so the PSBT contains metadata for every input in the transaction. This is required because BIP-341 taproot signatures commit to all transaction inputs (the common signature message). Without those remote inputs, the wallet cannot produce a valid SIGHASH_DEFAULT signature for local taproot inputs. LocalOnChainKeyManager.scala only gets a minor error-message improvement (position of failing PSBT input). The bulk of the diff updates tests to use distinct SingleKeyOnChainWallet instances for Alice and Bob and removes the unused NoOpOnChainWallet class.
Changed components
eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxBuilder.scalaeclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/LocalOnChainKeyManager.scalaDual-funded channel opening and splicing (interactive-tx)Taproot (P2TR) on-chain wallet signing pathInspect captured patch +95 / −144
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxBuilder.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxBuilder.scala
index 731b9ae..9a10fd1 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxBuilder.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxBuilder.scala
@@ -1017,11 +1017,12 @@ private class InteractiveTxBuilder(replyTo: ActorRef[InteractiveTxBuilder.Respon
// We trust that non-change outputs are valid: this only works if the entry point for creating such outputs is trusted (for example, a secure API call).
case _: Output.Local.NonChange => None
}
- // If this is a splice, the PSBT we create must contain the shared input, because if we use taproot wallet inputs
- // we need information about *all* of the transaction's inputs, not just the one we're signing.
- val psbt = unsignedTx.sharedInput_opt.flatMap {
- si => new Psbt(tx).updateWitnessInput(si.outPoint, si.txOut, null, null, null, java.util.Map.of(), null, null, java.util.Map.of()).toOption
- }.getOrElse(new Psbt(tx))
+ // We must include all remote inputs in the PSBT we create, because if we use taproot wallet inputs we need
+ // information about *all* of the transaction's inputs, not just the one we're signing. If this is a splice,
+ // we also need to include the shared input, for the same reason.
+ val psbt = (unsignedTx.remoteInputs ++ unsignedTx.sharedInput_opt.toSeq).foldLeft(new Psbt(tx)) {
+ case (psbt, input) => psbt.updateWitnessInput(input.outPoint, input.txOut, null, null, null, java.util.Map.of(), null, null, java.util.Map.of()).toOption.getOrElse(psbt)
+ }
context.pipeToSelf(wallet.signPsbt(psbt, ourWalletInputs, ourWalletOutputs).map {
response =>
val localOutpoints = unsignedTx.localInputs.map(_.outPoint).toSet
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/LocalOnChainKeyManager.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/LocalOnChainKeyManager.scala
index f1dc83b..7145e2c 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/LocalOnChainKeyManager.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/LocalOnChainKeyManager.scala
@@ -290,7 +290,7 @@ class LocalOnChainKeyManager(override val walletName: String, seed: ByteVector,
s.getPsbt.finalizeWitnessInput(pos, Script.witnessPay2wpkh(pub, s.getSig))
}) match {
case Right(psbt) => psbt
- case Left(failure) => throw new RuntimeException(s"cannot sign psbt input, error = $failure")
+ case Left(failure) => throw new RuntimeException(s"cannot sign psbt input at position $pos, error = $failure")
}
}
@@ -315,7 +315,7 @@ class LocalOnChainKeyManager(override val walletName: String, seed: ByteVector,
.sign(priv, pos)
.flatMap(s => s.getPsbt.finalizeWitnessInput(pos, Script.witnessKeyPathPay2tr(ByteVector64(s.getSig), SigHash.SIGHASH_DEFAULT))) match {
case Right(psbt) => psbt
- case Left(failure) => throw new RuntimeException(s"cannot sign psbt input, error = $failure")
+ case Left(failure) => throw new RuntimeException(s"cannot sign psbt input at position $pos, error = $failure")
}
}
}
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/DummyOnChainWallet.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/DummyOnChainWallet.scala
index b02a3ee..d6f10df 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/DummyOnChainWallet.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/DummyOnChainWallet.scala
@@ -30,7 +30,7 @@ import fr.acinq.eclair.transactions.Transactions
import fr.acinq.eclair.{TimestampSecond, randomBytes32}
import scodec.bits._
-import scala.concurrent.{ExecutionContext, Future, Promise}
+import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Random, Success}
/**
@@ -97,56 +97,6 @@ class DummyOnChainWallet extends OnChainWallet with OnChainPubkeyCache {
override def getReceivePublicKeyScript(renew: Boolean): Seq[ScriptElt] = Script.pay2tr(dummyReceivePubkey.xOnly)
}
-class NoOpOnChainWallet extends OnChainWallet with OnChainPubkeyCache {
-
- import DummyOnChainWallet._
-
- var rolledback = Seq.empty[Transaction]
- var doubleSpent = Set.empty[TxId]
- var abandoned = Set.empty[TxId]
-
- override def onChainBalance()(implicit ec: ExecutionContext): Future[OnChainBalance] = Future.successful(OnChainBalance(1105 sat, 561 sat))
-
- override def getReceivePublicKeyScript(addressType: Option[AddressType] = None)(implicit ec: ExecutionContext): Future[Seq[ScriptElt]] = Future.successful(addressType match {
- case Some(AddressType.P2tr) => Script.pay2tr(dummyReceivePubkey.xOnly)
- case _ => Script.pay2wpkh(dummyReceivePubkey)
- })
-
- override def getP2wpkhPubkey()(implicit ec: ExecutionContext): Future[Crypto.PublicKey] = Future.successful(dummyReceivePubkey)
-
- override def fundTransaction(tx: Transaction, feeRate: FeeratePerKw, replaceable: Boolean, changePosition: Option[Int], externalInputsWeight: Map[OutPoint, Long], minInputConfirmations_opt: Option[Int], feeBudget_opt: Option[Satoshi])(implicit ec: ExecutionContext): Future[FundTransactionResponse] = Promise().future // will never be completed
-
- override def signPsbt(psbt: Psbt, ourInputs: Seq[Int], ourOutputs: Seq[Int])(implicit ec: ExecutionContext): Future[ProcessPsbtResponse] = Promise().future // will never be completed
-
- override def publishTransaction(tx: Transaction)(implicit ec: ExecutionContext): Future[TxId] = Future.successful(tx.txid)
-
- override def makeFundingTx(pubkeyScript: ByteVector, amount: Satoshi, feeRatePerKw: FeeratePerKw, feeBudget_opt: Option[Satoshi])(implicit ec: ExecutionContext): Future[MakeFundingTxResponse] = Promise().future // will never be completed
-
- override def commit(tx: Transaction)(implicit ec: ExecutionContext): Future[Boolean] = Future.successful(true)
-
- override def getTransaction(txId: TxId)(implicit ec: ExecutionContext): Future[Transaction] = Promise().future // will never be completed
-
- override def getTxConfirmations(txid: TxId)(implicit ec: ExecutionContext): Future[Option[Int]] = Promise().future // will never be completed
-
- override def isTransactionOutputSpendable(txid: TxId, outputIndex: Int, includeMempool: Boolean)(implicit ec: ExecutionContext): Future[Boolean] = Future.successful(true)
-
- override def rollback(tx: Transaction)(implicit ec: ExecutionContext): Future[Boolean] = {
- rolledback = rolledback :+ tx
- Future.successful(true)
- }
-
- override def abandon(txId: TxId)(implicit ec: ExecutionContext): Future[Boolean] = {
- abandoned = abandoned + txId
- Future.successful(true)
- }
-
- override def doubleSpent(tx: Transaction)(implicit ec: ExecutionContext): Future[Boolean] = Future.successful(doubleSpent.contains(tx.txid))
-
- override def getP2wpkhPubkey(renew: Boolean): PublicKey = dummyReceivePubkey
-
- override def getReceivePublicKeyScript(renew: Boolean): Seq[ScriptElt] = Script.pay2tr(dummyReceivePubkey.xOnly)
-}
-
class SingleKeyOnChainWallet extends OnChainWallet with OnChainPubkeyCache {
import fr.acinq.bitcoin.scalacompat.KotlinUtils._
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/RestoreSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/RestoreSpec.scala
index 5c1f7cc..493817c 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/RestoreSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/RestoreSpec.scala
@@ -60,7 +60,7 @@ class RestoreSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Chan
alice.stop()
// we restart Alice
- val newAlice: TestFSMRef[ChannelState, ChannelData, Channel] = TestFSMRef(new Channel(Alice.nodeParams, Alice.channelKeys(), wallet, Bob.nodeParams.nodeId, alice2blockchain.ref, alice2relayer.ref, FakeTxPublisherFactory(alice2blockchain)), alicePeer.ref)
+ val newAlice: TestFSMRef[ChannelState, ChannelData, Channel] = TestFSMRef(new Channel(Alice.nodeParams, Alice.channelKeys(), aliceWallet, Bob.nodeParams.nodeId, alice2blockchain.ref, alice2relayer.ref, FakeTxPublisherFactory(alice2blockchain)), alicePeer.ref)
newAlice ! INPUT_RESTORED(oldStateData)
// then we reconnect them
@@ -136,7 +136,7 @@ class RestoreSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Chan
alice.stop()
// we restart Alice
- val newAlice: TestFSMRef[ChannelState, ChannelData, Channel] = TestFSMRef(new Channel(Alice.nodeParams, Alice.channelKeys(), wallet, Bob.nodeParams.nodeId, alice2blockchain.ref, alice2relayer.ref, FakeTxPublisherFactory(alice2blockchain)), alicePeer.ref)
+ val newAlice: TestFSMRef[ChannelState, ChannelData, Channel] = TestFSMRef(new Channel(Alice.nodeParams, Alice.channelKeys(), aliceWallet, Bob.nodeParams.nodeId, alice2blockchain.ref, alice2relayer.ref, FakeTxPublisherFactory(alice2blockchain)), alicePeer.ref)
newAlice ! INPUT_RESTORED(oldStateData)
newAlice ! INPUT_RECONNECTED(alice2bob.ref, aliceInit, bobInit)
@@ -188,7 +188,7 @@ class RestoreSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Chan
.modify(_.relayParams.privateChannelFees.feeProportionalMillionths).setTo(2345)
.modify(_.channelConf.expiryDelta).setTo(CltvExpiryDelta(147)),
) foreach { newConfig =>
- val newAlice: TestFSMRef[ChannelState, ChannelData, Channel] = TestFSMRef(new Channel(newConfig, Alice.channelKeys(), wallet, Bob.nodeParams.nodeId, alice2blockchain.ref, alice2relayer.ref, FakeTxPublisherFactory(alice2blockchain)), alicePeer.ref)
+ val newAlice: TestFSMRef[ChannelState, ChannelData, Channel] = TestFSMRef(new Channel(newConfig, Alice.channelKeys(), aliceWallet, Bob.nodeParams.nodeId, alice2blockchain.ref, alice2relayer.ref, FakeTxPublisherFactory(alice2blockchain)), alicePeer.ref)
newAlice ! INPUT_RESTORED(oldStateData)
val u1 = channelUpdateListener.expectMsgType[ChannelUpdateParametersChanged]
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala
index 61accbd..b8d84e5 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala
@@ -163,7 +163,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w
val blockHeight = new AtomicLong()
blockHeight.set(currentBlockHeight(probe).toLong)
val aliceNodeParams = TestConstants.Alice.nodeParams.copy(blockHeight = blockHeight)
- val setup = init(aliceNodeParams, TestConstants.Bob.nodeParams.copy(blockHeight = blockHeight), wallet_opt = Some(walletClient))
+ val setup = init(aliceNodeParams, TestConstants.Bob.nodeParams.copy(blockHeight = blockHeight), walletA_opt = Some(walletClient))
val testTags = channelType match {
case _: ChannelTypes.AnchorOutputsZeroFeeHtlcTx => Set(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)
case _: ChannelTypes.AnchorOutputs => Set(ChannelStateTestsTags.AnchorOutputs)
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/TxTimeLocksMonitorSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/TxTimeLocksMonitorSpec.scala
index bebd58c..2f77b3e 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/TxTimeLocksMonitorSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/TxTimeLocksMonitorSpec.scala
@@ -20,7 +20,7 @@ import akka.actor.typed.ActorRef
import akka.actor.typed.scaladsl.adapter.{ClassicActorSystemOps, actorRefAdapter}
import akka.testkit.TestProbe
import fr.acinq.bitcoin.scalacompat.{OutPoint, SatoshiLong, Script, Transaction, TxId, TxIn, TxOut}
-import fr.acinq.eclair.blockchain.NoOpOnChainWallet
+import fr.acinq.eclair.blockchain.DummyOnChainWallet
import fr.acinq.eclair.channel.publish.TxPublisher.TxPublishContext
import fr.acinq.eclair.channel.publish.TxTimeLocksMonitor.{CheckTx, TimeLocksOk, WrappedCurrentBlockHeight}
import fr.acinq.eclair.{NodeParams, TestConstants, TestKitBaseClass, randomKey}
@@ -36,7 +36,7 @@ class TxTimeLocksMonitorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
case class FixtureParam(nodeParams: NodeParams, monitor: ActorRef[TxTimeLocksMonitor.Command], bitcoinClient: BitcoinTestClient, probe: TestProbe)
- case class BitcoinTestClient() extends NoOpOnChainWallet {
+ case class BitcoinTestClient() extends DummyOnChainWallet {
private val requests = collection.concurrent.TrieMap.empty[TxId, Promise[Option[Int]]]
override def getTxConfirmations(txId: TxId)(implicit ec: ExecutionContext): Future[Option[Int]] = {
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala
index 1e83d05..f700595 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala
@@ -27,7 +27,7 @@ import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair._
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._
import fr.acinq.eclair.blockchain.fee.{FeeratePerKw, FeeratesPerKw}
-import fr.acinq.eclair.blockchain.{DummyOnChainWallet, OnChainPubkeyCache, OnChainWallet, SingleKeyOnChainWallet}
+import fr.acinq.eclair.blockchain.{OnChainPubkeyCache, OnChainWallet, SingleKeyOnChainWallet}
import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.fsm.Channel
import fr.acinq.eclair.channel.publish.TxPublisher.{PublishFinalTx, PublishReplaceableTx}
@@ -119,7 +119,8 @@ trait ChannelStateTestsBase extends Assertions with Eventually {
alice2relayer: TestProbe,
bob2relayer: TestProbe,
channelUpdateListener: TestProbe,
- wallet: OnChainWallet with OnChainPubkeyCache,
+ aliceWallet: OnChainWallet with OnChainPubkeyCache,
+ bobWallet: OnChainWallet with OnChainPubkeyCache,
alicePeer: TestProbe,
bobPeer: TestProbe) {
def currentBlockHeight: BlockHeight = alice.underlyingActor.nodeParams.currentBlockHeight
@@ -203,7 +204,7 @@ trait ChannelStateTestsBase extends Assertions with Eventually {
system.registerOnTermination(TestKit.shutdownActorSystem(systemA))
system.registerOnTermination(TestKit.shutdownActorSystem(systemB))
- def init(nodeParamsA: NodeParams = TestConstants.Alice.nodeParams, nodeParamsB: NodeParams = TestConstants.Bob.nodeParams, wallet_opt: Option[OnChainWallet with OnChainPubkeyCache] = None, tags: Set[String] = Set.empty): SetupFixture = {
+ def init(nodeParamsA: NodeParams = TestConstants.Alice.nodeParams, nodeParamsB: NodeParams = TestConstants.Bob.nodeParams, walletA_opt: Option[OnChainWallet with OnChainPubkeyCache] = None, walletB_opt: Option[OnChainWallet with OnChainPubkeyCache] = None, tags: Set[String] = Set.empty): SetupFixture = {
val aliceOpenReplyTo = TestProbe()
val alice2bob = TestProbe()
val bob2alice = TestProbe()
@@ -246,19 +247,17 @@ trait ChannelStateTestsBase extends Assertions with Eventually {
.modify(_.onChainFeeConf.defaultFeerateTolerance.ratioHigh).setToIf(tags.contains(ChannelStateTestsTags.HighFeerateMismatchTolerance))(1000000)
.modify(_.onChainFeeConf.spendAnchorWithoutHtlcs).setToIf(tags.contains(ChannelStateTestsTags.DontSpendAnchorWithoutHtlcs))(false)
.modify(_.channelConf.balanceThresholds).setToIf(tags.contains(ChannelStateTestsTags.AdaptMaxHtlcAmount))(Seq(Channel.BalanceThreshold(1_000 sat, 0 sat), Channel.BalanceThreshold(5_000 sat, 1_000 sat), Channel.BalanceThreshold(10_000 sat, 5_000 sat)))
- val wallet = wallet_opt match {
- case Some(wallet) => wallet
- case None => if (tags.contains(ChannelStateTestsTags.DualFunding)) new SingleKeyOnChainWallet() else new DummyOnChainWallet()
- }
+ val aliceWallet = walletA_opt.getOrElse(new SingleKeyOnChainWallet())
val alice: TestFSMRef[ChannelState, ChannelData, Channel] = {
implicit val system: ActorSystem = systemA
- TestFSMRef(new Channel(finalNodeParamsA, TestConstants.Alice.channelKeys(), wallet, finalNodeParamsB.nodeId, alice2blockchain.ref, alice2relayer.ref, FakeTxPublisherFactory(alice2blockchain)), alicePeer.ref)
+ TestFSMRef(new Channel(finalNodeParamsA, TestConstants.Alice.channelKeys(), aliceWallet, finalNodeParamsB.nodeId, alice2blockchain.ref, alice2relayer.ref, FakeTxPublisherFactory(alice2blockchain)), alicePeer.ref)
}
+ val bobWallet = walletB_opt.getOrElse(new SingleKeyOnChainWallet())
val bob: TestFSMRef[ChannelState, ChannelData, Channel] = {
implicit val system: ActorSystem = systemB
- TestFSMRef(new Channel(finalNodeParamsB, TestConstants.Bob.channelKeys(), wallet, finalNodeParamsA.nodeId, bob2blockchain.ref, bob2relayer.ref, FakeTxPublisherFactory(bob2blockchain)), bobPeer.ref)
+ TestFSMRef(new Channel(finalNodeParamsB, TestConstants.Bob.channelKeys(), bobWallet, finalNodeParamsA.nodeId, bob2blockchain.ref, bob2relayer.ref, FakeTxPublisherFactory(bob2blockchain)), bobPeer.ref)
}
- SetupFixture(alice, bob, aliceOpenReplyTo, alice2bob, bob2alice, alice2blockchain, bob2blockchain, router, alice2relayer, bob2relayer, channelUpdateListener, wallet, alicePeer, bobPeer)
+ SetupFixture(alice, bob, aliceOpenReplyTo, alice2bob, bob2alice, alice2blockchain, bob2blockchain, router, alice2relayer, bob2relayer, channelUpdateListener, aliceWallet, bobWallet, alicePeer, bobPeer)
}
def updateInitFeatures(nodeParamsA: NodeParams, nodeParamsB: NodeParams, tags: Set[String]): (NodeParams, NodeParams) = {
@@ -309,9 +308,9 @@ trait ChannelStateTestsBase extends Assertions with Eventually {
implicit val ec: scala.concurrent.ExecutionContext = scala.concurrent.ExecutionContext.global
val aliceChannelParams = Alice.channelParams
.modify(_.initFeatures).setTo(aliceInitFeatures)
- .modify(_.walletStaticPaymentBasepoint).setToIf(channelType.paysDirectlyToWallet)(Some(Await.result(wallet.getP2wpkhPubkey(), 10 seconds)))
+ .modify(_.walletStaticPaymentBasepoint).setToIf(channelType.paysDirectlyToWallet)(Some(Await.result(aliceWallet.getP2wpkhPubkey(), 10 seconds)))
.modify(_.initialRequestedChannelReserve_opt).setToIf(tags.contains(ChannelStateTestsTags.DualFunding))(None)
- .modify(_.upfrontShutdownScript_opt).setToIf(tags.contains(ChannelStateTestsTags.UpfrontShutdownScript))(Some(Script.write(Script.pay2wpkh(Await.result(wallet.getP2wpkhPubkey(), 10 seconds)))))
+ .modify(_.upfrontShutdownScript_opt).setToIf(tags.contains(ChannelStateTestsTags.UpfrontShutdownScript))(Some(Script.write(Script.pay2wpkh(Await.result(aliceWallet.getP2wpkhPubkey(), 10 seconds)))))
val aliceCommitParams = CommitParams(nodeParamsA.channelConf.dustLimit, nodeParamsA.channelConf.htlcMinimum, nodeParamsA.channelConf.maxHtlcValueInFlight(TestConstants.fundingSatoshis, unlimited = false), nodeParamsA.channelConf.maxAcceptedHtlcs, nodeParamsB.channelConf.toRemoteDelay)
.modify(_.maxHtlcValueInFlight).setToIf(tags.contains(ChannelStateTestsTags.NoMaxHtlcValueInFlight))(UInt64.MaxValue)
.modify(_.maxHtlcValueInFlight).setToIf(tags.contains(ChannelStateTestsTags.AliceLowMaxHtlcValueInFlight))(UInt64(150_000_000))
@@ -319,9 +318,9 @@ trait ChannelStateTestsBase extends Assertions with Eventually {
.modify(_.dustLimit).setToIf(tags.contains(ChannelStateTestsTags.HighDustLimitDifferenceBobAlice))(1000 sat)
val bobChannelParams = Bob.channelParams
.modify(_.initFeatures).setTo(bobInitFeatures)
- .modify(_.walletStaticPaymentBasepoint).setToIf(channelType.paysDirectlyToWallet)(Some(Await.result(wallet.getP2wpkhPubkey(), 10 seconds)))
+ .modify(_.walletStaticPaymentBasepoint).setToIf(channelType.paysDirectlyToWallet)(Some(Await.result(bobWallet.getP2wpkhPubkey(), 10 seconds)))
.modify(_.initialRequestedChannelReserve_opt).setToIf(tags.contains(ChannelStateTestsTags.DualFunding))(None)
- .modify(_.upfrontShutdownScript_opt).setToIf(tags.contains(ChannelStateTestsTags.UpfrontShutdownScript))(Some(Script.write(Script.pay2wpkh(Await.result(wallet.getP2wpkhPubkey(), 10 seconds)))))
+ .modify(_.upfrontShutdownScript_opt).setToIf(tags.contains(ChannelStateTestsTags.UpfrontShutdownScript))(Some(Script.write(Script.pay2wpkh(Await.result(bobWallet.getP2wpkhPubkey(), 10 seconds)))))
val bobCommitParams = CommitParams(nodeParamsB.channelConf.dustLimit, nodeParamsB.channelConf.htlcMinimum, nodeParamsB.channelConf.maxHtlcValueInFlight(TestConstants.fundingSatoshis, unlimited = false), nodeParamsB.channelConf.maxAcceptedHtlcs, nodeParamsA.channelConf.toRemoteDelay)
.modify(_.maxHtlcValueInFlight).setToIf(tags.contains(ChannelStateTestsTags.NoMaxHtlcValueInFlight))(UInt64.MaxValue)
.modify(_.dustLimit).setToIf(tags.contains(ChannelStateTestsTags.HighDustLimitDifferenceAliceBob))(1000 sat)
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 5a8a3d1..5f79b82 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
@@ -20,7 +20,6 @@ import akka.testkit.{TestFSMRef, TestProbe}
import com.softwaremill.quicklens.ModifyPimp
import fr.acinq.bitcoin.scalacompat.{ByteVector32, SatoshiLong, TxId}
import fr.acinq.eclair.TestConstants.{Alice, Bob}
-import fr.acinq.eclair.blockchain.NoOpOnChainWallet
import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.fsm.Channel
import fr.acinq.eclair.channel.fsm.Channel.TickChannelOpenTimeout
@@ -50,7 +49,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS
import com.softwaremill.quicklens._
val aliceNodeParams = Alice.nodeParams.modify(_.channelConf.maxRemoteDustLimit).setToIf(test.tags.contains(HighRemoteDustLimit))(15_000 sat)
- val setup = init(aliceNodeParams, Bob.nodeParams, wallet_opt = Some(new NoOpOnChainWallet()), test.tags)
+ val setup = init(aliceNodeParams, Bob.nodeParams, tags = test.tags)
import setup._
val channelParams = computeChannelParams(setup, test.tags)
@@ -284,7 +283,6 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS
.modify(_.tlvStream.records).using(_.filterNot(_.isInstanceOf[ChannelTlv.UpfrontShutdownScriptTlv]))
.modify(_.tlvStream.records).using(_ + ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty))
bob2alice.forward(alice, accept1)
- alice2bob.expectNoMessage(100 millis)
awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL)
assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_INTERNAL].channelParams.remoteParams.upfrontShutdownScript_opt.isEmpty)
aliceOpenReplyTo.expectNoMessage()
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForDualFundingCreatedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForDualFundingCreatedStateSpec.scala
index beadb3f..ed35603 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForDualFundingCreatedStateSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForDualFundingCreatedStateSpec.scala
@@ -36,11 +36,12 @@ import scala.concurrent.duration.DurationInt
class WaitForDualFundingCreatedStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with ChannelStateTestsBase {
- case class FixtureParam(alice: TestFSMRef[ChannelState, ChannelData, Channel], bob: TestFSMRef[ChannelState, ChannelData, Channel], aliceOpenReplyTo: TestProbe, alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe, bob2blockchain: TestProbe, wallet: SingleKeyOnChainWallet, aliceListener: TestProbe, bobListener: TestProbe)
+ case class FixtureParam(alice: TestFSMRef[ChannelState, ChannelData, Channel], bob: TestFSMRef[ChannelState, ChannelData, Channel], aliceOpenReplyTo: TestProbe, alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe, bob2blockchain: TestProbe, aliceWallet: SingleKeyOnChainWallet, bobWallet: SingleKeyOnChainWallet, aliceListener: TestProbe, bobListener: TestProbe)
override def withFixture(test: OneArgTest): Outcome = {
- val wallet = new SingleKeyOnChainWallet()
- val setup = init(wallet_opt = Some(wallet), tags = test.tags)
+ val aliceWallet = new SingleKeyOnChainWallet()
+ val bobWallet = new SingleKeyOnChainWallet()
+ val setup = init(walletA_opt = Some(aliceWallet), walletB_opt = Some(bobWallet), tags = test.tags)
import setup._
val channelParams = computeChannelParams(setup, test.tags)
val aliceListener = TestProbe()
@@ -60,7 +61,7 @@ class WaitForDualFundingCreatedStateSpec extends TestKitBaseClass with FixtureAn
bob2blockchain.expectMsgType[TxPublisher.SetChannelId] // final channel id
awaitCond(alice.stateName == WAIT_FOR_DUAL_FUNDING_CREATED)
awaitCond(bob.stateName == WAIT_FOR_DUAL_FUNDING_CREATED)
- withFixture(test.toNoArgTest(FixtureParam(alice, bob, aliceOpenReplyTo, alice2bob, bob2alice, alice2blockchain, bob2blockchain, wallet, aliceListener, bobListener)))
+ withFixture(test.toNoArgTest(FixtureParam(alice, bob, aliceOpenReplyTo, alice2bob, bob2alice, alice2blockchain, bob2blockchain, aliceWallet, bobWallet, aliceListener, bobListener)))
}
}
@@ -97,14 +98,14 @@ class WaitForDualFundingCreatedStateSpec extends TestKitBaseClass with FixtureAn
// Invalid serial_id.
alice2bob.forward(bob, inputA.copy(serialId = UInt64(1)))
bob2alice.expectMsgType[TxAbort]
- awaitCond(wallet.rolledback.length == 1)
+ awaitCond(bobWallet.rolledback.length == 1)
bobListener.expectMsgType[ChannelAborted]
awaitCond(bob.stateName == CLOSED)
// Below dust.
bob2alice.forward(alice, TxAddOutput(channelId(bob), UInt64(1), 150 sat, Script.write(Script.pay2wpkh(randomKey().publicKey))))
alice2bob.expectMsgType[TxAbort]
- awaitCond(wallet.rolledback.length == 2)
+ awaitCond(aliceWallet.rolledback.length == 1)
aliceListener.expectMsgType[ChannelAborted]
awaitCond(alice.stateName == CLOSED)
aliceOpenReplyTo.expectMsgType[OpenChannelResponse.Rejected]
@@ -136,12 +137,12 @@ class WaitForDualFundingCreatedStateSpec extends TestKitBaseClass with FixtureAn
alice2bob.expectMsgType[TxAddInput]
alice2bob.forward(bob, TxAbort(channelId(alice), hex"deadbeef"))
val bobTxAbort = bob2alice.expectMsgType[TxAbort]
- awaitCond(wallet.rolledback.size == 1)
+ awaitCond(bobWallet.rolledback.size == 1)
bobListener.expectMsgType[ChannelAborted]
awaitCond(bob.stateName == CLOSED)
bob2alice.forward(alice, bobTxAbort)
- awaitCond(wallet.rolledback.size == 2)
+ awaitCond(aliceWallet.rolledback.size == 1)
aliceListener.expectMsgType[ChannelAborted]
awaitCond(alice.stateName == CLOSED)
aliceOpenReplyTo.expectMsgType[OpenChannelResponse.RemoteError]
@@ -159,7 +160,7 @@ class WaitForDualFundingCreatedStateSpec extends TestKitBaseClass with FixtureAn
alice2bob.expectMsgType[Warning]
assert(alice.stateName == WAIT_FOR_DUAL_FUNDING_CREATED)
aliceOpenReplyTo.expectNoMessage(100 millis)
- assert(wallet.rolledback.isEmpty)
+ assert(aliceWallet.rolledback.isEmpty)
}
test("recv TxAckRbf", Tag(ChannelStateTestsTags.DualFunding)) { f =>
@@ -174,7 +175,7 @@ class WaitForDualFundingCreatedStateSpec extends TestKitBaseClass with FixtureAn
alice2bob.expectMsgType[Warning]
assert(alice.stateName == WAIT_FOR_DUAL_FUNDING_CREATED)
aliceOpenReplyTo.expectNoMessage(100 millis)
- assert(wallet.rolledback.isEmpty)
+ assert(aliceWallet.rolledback.isEmpty)
}
test("recv Error", Tag(ChannelStateTestsTags.DualFunding)) { f =>
@@ -182,13 +183,13 @@ class WaitForDualFundingCreatedStateSpec extends TestKitBaseClass with FixtureAn
val finalChannelId = channelId(alice)
alice ! Error(finalChannelId, "oops")
- awaitCond(wallet.rolledback.size == 1)
+ awaitCond(aliceWallet.rolledback.size == 1)
aliceListener.expectMsgType[ChannelAborted]
awaitCond(alice.stateName == CLOSED)
aliceOpenReplyTo.expectMsgType[OpenChannelResponse.RemoteError]
bob ! Error(finalChannelId, "oops")
- awaitCond(wallet.rolledback.size == 2)
+ awaitCond(bobWallet.rolledback.size == 1)
bobListener.expectMsgType[ChannelAborted]
awaitCond(bob.stateName == CLOSED)
}
@@ -202,14 +203,14 @@ class WaitForDualFundingCreatedStateSpec extends TestKitBaseClass with FixtureAn
alice ! c
sender.expectMsg(RES_SUCCESS(c, finalChannelId))
- awaitCond(wallet.rolledback.size == 1)
+ awaitCond(aliceWallet.rolledback.size == 1)
aliceListener.expectMsgType[ChannelAborted]
awaitCond(alice.stateName == CLOSED)
aliceOpenReplyTo.expectMsg(OpenChannelResponse.Cancelled)
bob ! c
sender.expectMsg(RES_SUCCESS(c, finalChannelId))
- awaitCond(wallet.rolledback.size == 2)
+ awaitCond(bobWallet.rolledback.size == 1)
bobListener.expectMsgType[ChannelAborted]
awaitCond(bob.stateName == CLOSED)
}
@@ -218,13 +219,13 @@ class WaitForDualFundingCreatedStateSpec extends TestKitBaseClass with FixtureAn
import f._
alice ! INPUT_DISCONNECTED
- awaitCond(wallet.rolledback.size == 1)
+ awaitCond(aliceWallet.rolledback.size == 1)
aliceListener.expectMsgType[ChannelAborted]
awaitCond(alice.stateName == CLOSED)
aliceOpenReplyTo.expectMsg(OpenChannelResponse.Disconnected)
bob ! INPUT_DISCONNECTED
- awaitCond(wallet.rolledback.size == 2)
+ awaitCond(bobWallet.rolledback.size == 1)
bobListener.expectMsgType[ChannelAborted]
awaitCond(bob.stateName == CLOSED)
}
@@ -270,7 +271,7 @@ class WaitForDualFundingCreatedStateSpec extends TestKitBaseClass with FixtureAn
import f._
alice ! TickChannelOpenTimeout
- awaitCond(wallet.rolledback.size == 1)
+ awaitCond(aliceWallet.rolledback.size == 1)
aliceListener.expectMsgType[ChannelAborted]
awaitCond(alice.stateName == CLOSED)
aliceOpenReplyTo.expectMsg(OpenChannelResponse.TimedOut)
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForDualFundingSignedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForDualFundingSignedStateSpec.scala
index d0a2fc9..4d5f307 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForDualFundingSignedStateSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForDualFundingSignedStateSpec.scala
@@ -40,11 +40,12 @@ import scala.concurrent.duration.DurationInt
class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with ChannelStateTestsBase {
- case class FixtureParam(alice: TestFSMRef[ChannelState, ChannelData, Channel], bob: TestFSMRef[ChannelState, ChannelData, Channel], alicePeer: TestProbe, bobPeer: TestProbe, alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe, bob2blockchain: TestProbe, wallet: SingleKeyOnChainWallet, aliceListener: TestProbe, bobListener: TestProbe)
+ case class FixtureParam(alice: TestFSMRef[ChannelState, ChannelData, Channel], bob: TestFSMRef[ChannelState, ChannelData, Channel], alicePeer: TestProbe, bobPeer: TestProbe, alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe, bob2blockchain: TestProbe, aliceWallet: SingleKeyOnChainWallet, bobWallet: SingleKeyOnChainWallet, aliceListener: TestProbe, bobListener: TestProbe)
override def withFixture(test: OneArgTest): Outcome = {
- val wallet = new SingleKeyOnChainWallet()
- val setup = init(wallet_opt = Some(wallet), tags = test.tags)
+ val aliceWallet = new SingleKeyOnChainWallet()
+ val bobWallet = new SingleKeyOnChainWallet()
+ val setup = init(walletA_opt = Some(aliceWallet), walletB_opt = Some(bobWallet), tags = test.tags)
import setup._
val channelParams = computeChannelParams(setup, test.tags)
val bobContribution = if (channelParams.channelType.features.contains(Features.ZeroConf)) None else Some(LiquidityAds.AddFunding(TestConstants.nonInitiatorFundingSatoshis, Some(TestConstants.defaultLiquidityRates)))
@@ -89,7 +90,7 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny
aliceOpenReplyTo.expectMsgType[OpenChannelResponse.Created]
awaitCond(alice.stateName == WAIT_FOR_DUAL_FUNDING_SIGNED)
awaitCond(bob.stateName == WAIT_FOR_DUAL_FUNDING_SIGNED)
- withFixture(test.toNoArgTest(FixtureParam(alice, bob, alicePeer, bobPeer, alice2bob, bob2alice, alice2blockchain, bob2blockchain, wallet, aliceListener, bobListener)))
+ withFixture(test.toNoArgTest(FixtureParam(alice, bob, alicePeer, bobPeer, alice2bob, bob2alice, alice2blockchain, bob2blockchain, aliceWallet, bobWallet, aliceListener, bobListener)))
}
}
@@ -240,13 +241,13 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny
bob2alice.forward(alice, bobCommitSig.copy(signature = IndividualSignature(ByteVector64.Zeroes)))
alice2bob.expectMsgType[Error]
- awaitCond(wallet.rolledback.length == 1)
+ awaitCond(aliceWallet.rolledback.length == 1)
aliceListener.expectMsgType[ChannelAborted]
awaitCond(alice.stateName == CLOSED)
alice2bob.forward(bob, aliceCommitSig.copy(signature = IndividualSignature(ByteVector64.Zeroes)))
bob2alice.expectMsgType[Error]
- awaitCond(wallet.rolledback.length == 2)
+ awaitCond(bobWallet.rolledback.length == 1)
bobListener.expectMsgType[ChannelAborted]
awaitCond(bob.stateName == CLOSED)
}
@@ -262,14 +263,14 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny
val invalidSigBob = bobCommitSig.partialSignature_opt.get.copy(partialSig = randomBytes32())
bob2alice.forward(alice, bobCommitSig.copy(tlvStream = TlvStream(CommitSigTlv.PartialSignatureWithNonceTlv(invalidSigBob))))
alice2bob.expectMsgType[Error]
- awaitCond(wallet.rolledback.length == 1)
+ awaitCond(aliceWallet.rolledback.length == 1)
aliceListener.expectMsgType[ChannelAborted]
awaitCond(alice.stateName == CLOSED)
val invalidSigAlice = aliceCommitSig.partialSignature_opt.get.copy(nonce = NonceGenerator.signingNonce(randomKey().publicKey, randomKey().publicKey, randomTxId()).publicNonce)
alice2bob.forward(bob, aliceCommitSig.copy(tlvStream = TlvStream(CommitSigTlv.PartialSignatureWithNonceTlv(invalidSigAlice))))
bob2alice.expectMsgType[Error]
- awaitCond(wallet.rolledback.length == 2)
+ awaitCond(bobWallet.rolledback.length == 1)
bobListener.expectMsgType[ChannelAborted]
awaitCond(bob.stateName == CLOSED)
}
@@ -286,7 +287,7 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny
bob2blockchain.expectMsgType[WatchFundingConfirmed]
bob2alice.forward(alice, bobSigs.copy(txId = randomTxId(), witnesses = Nil))
alice2bob.expectMsgType[Error]
- awaitCond(wallet.rolledback.size == 1)
+ awaitCond(aliceWallet.rolledback.size == 1)
aliceListener.expectMsgType[ChannelAborted]
awaitCond(alice.stateName == CLOSED)
@@ -294,6 +295,7 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny
alice2bob.forward(bob, TxSignatures(channelId(alice), randomTxId(), Nil))
bob2alice.expectMsgType[Error]
bob2blockchain.expectNoMessage(100 millis)
+ assert(bobWallet.rolledback.isEmpty)
assert(bob.stateName == WAIT_FOR_DUAL_FUNDING_CONFIRMED)
}
@@ -310,7 +312,7 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny
bob2alice.forward(alice, TxInitRbf(channelId(bob), 0, FeeratePerKw(15_000 sat)))
alice2bob.expectMsgType[Warning]
assert(alice.stateName == WAIT_FOR_DUAL_FUNDING_SIGNED)
- assert(wallet.rolledback.isEmpty)
+ assert(aliceWallet.rolledback.isEmpty)
}
test("recv TxAckRbf", Tag(ChannelStateTestsTags.DualFunding)) { f =>
@@ -326,7 +328,7 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny
bob2alice.forward(alice, TxAckRbf(channelId(bob)))
alice2bob.expectMsgType[Warning]
assert(alice.stateName == WAIT_FOR_DUAL_FUNDING_SIGNED)
- assert(wallet.rolledback.isEmpty)
+ assert(aliceWallet.rolledback.isEmpty)
}
test("recv Error", Tag(ChannelStateTestsTags.DualFunding)) { f =>
@@ -334,12 +336,12 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny
val finalChannelId = channelId(alice)
alice ! Error(finalChannelId, "oops")
- awaitCond(wallet.rolledback.size == 1)
+ awaitCond(aliceWallet.rolledback.size == 1)
aliceListener.expectMsgType[ChannelAborted]
awaitCond(alice.stateName == CLOSED)
bob ! Error(finalChannelId, "oops")
- awaitCond(wallet.rolledback.size == 2)
+ awaitCond(bobWallet.rolledback.size == 1)
bobListener.expectMsgType[ChannelAborted]
awaitCond(bob.stateName == CLOSED)
}
@@ -353,13 +355,13 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny
alice ! c
sender.expectMsg(RES_SUCCESS(c, finalChannelId))
- awaitCond(wallet.rolledback.size == 1)
+ awaitCond(aliceWallet.rolledback.size == 1)
aliceListener.expectMsgType[ChannelAborted]
awaitCond(alice.stateName == CLOSED)
bob ! c
sender.expectMsg(RES_SUCCESS(c, finalChannelId))
- awaitCond(wallet.rolledback.size == 2)
+ awaitCond(bobWallet.rolledback.size == 1)
bobListener.expectMsgType[ChannelAborted]
awaitCond(bob.stateName == CLOSED)
}
@@ -378,13 +380,13 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny
alice ! c
sender.expectMsg(RES_SUCCESS(c, finalChannelId))
- awaitCond(wallet.rolledback.size == 1)
+ awaitCond(aliceWallet.rolledback.size == 1)
aliceListener.expectMsgType[ChannelAborted]
awaitCond(alice.stateName == CLOSED)
bob ! c
sender.expectMsg(RES_SUCCESS(c, finalChannelId))
- awaitCond(wallet.rolledback.size == 2)
+ awaitCond(bobWallet.rolledback.size == 1)
bobListener.expectMsgType[ChannelAborted]
awaitCond(bob.stateName == CLOSED)
}
@@ -403,7 +405,7 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny
awaitCond(bob.stateName == OFFLINE)
assert(bob.stateData == bobData)
bobListener.expectNoMessage(100 millis)
- assert(wallet.rolledback.isEmpty)
+ assert(bobWallet.rolledback.isEmpty)
}
def testReconnectCommitSigNotReceived(f: FixtureParam, commitmentFormat: CommitmentFormat): Unit = {
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingInternalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingInternalStateSpec.scala
index 3b26231..dc831d4 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingInternalStateSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingInternalStateSpec.scala
@@ -19,7 +19,6 @@ package fr.acinq.eclair.channel.states.b
import akka.actor.Status
import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.bitcoin.scalacompat.ByteVector32
-import fr.acinq.eclair.blockchain.NoOpOnChainWallet
import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.fsm.Channel
import fr.acinq.eclair.channel.fsm.Channel.TickChannelOpenTimeout
@@ -41,7 +40,7 @@ class WaitForFundingInternalStateSpec extends TestKitBaseClass with FixtureAnyFu
case class FixtureParam(alice: TestFSMRef[ChannelState, ChannelData, Channel], aliceOpenReplyTo: TestProbe, alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe, listener: TestProbe)
override def withFixture(test: OneArgTest): Outcome = {
- val setup = init(wallet_opt = Some(new NoOpOnChainWallet()), tags = test.tags)
+ val setup = init(tags = test.tags)
import setup._
val channelParams = computeChannelParams(setup, test.tags)
val listener = TestProbe()
@@ -79,7 +78,7 @@ class WaitForFundingInternalStateSpec extends TestKitBaseClass with FixtureAnyFu
val sender = TestProbe()
val c = CMD_CLOSE(sender.ref, None, None)
alice ! c
- sender.expectMsg(RES_SUCCESS(c, ByteVector32.Zeroes))
+ assert(sender.expectMsgType[RES_SUCCESS[CMD_CLOSE]].cmd == c)
listener.expectMsgType[ChannelAborted]
awaitCond(alice.stateName == CLOSED)
aliceOpenReplyTo.expectMsg(OpenChannelResponse.Cancelled)
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 cb34d82..2b53fbe 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
@@ -20,7 +20,7 @@ import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.bitcoin.scalacompat.{Btc, ByteVector32, ByteVector64, SatoshiLong}
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.TestUtils.randomTxId
-import fr.acinq.eclair.blockchain.DummyOnChainWallet
+import fr.acinq.eclair.blockchain.SingleKeyOnChainWallet
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._
import fr.acinq.eclair.channel.ChannelSpendSignature.PartialSignatureWithNonce
import fr.acinq.eclair.channel._
@@ -173,10 +173,10 @@ class WaitForFundingSignedStateSpec extends TestKitBaseClass with FixtureAnyFunS
test("recv INPUT_DISCONNECTED") { f =>
import f._
val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_SIGNED].fundingTx
- assert(alice.underlyingActor.wallet.asInstanceOf[DummyOnChainWallet].rolledback.isEmpty)
+ assert(alice.underlyingActor.wallet.asInstanceOf[SingleKeyOnChainWallet].rolledback.isEmpty)
alice ! INPUT_DISCONNECTED
awaitCond(alice.stateName == CLOSED)
- assert(alice.underlyingActor.wallet.asInstanceOf[DummyOnChainWallet].rolledback.contains(fundingTx))
+ assert(alice.underlyingActor.wallet.asInstanceOf[SingleKeyOnChainWallet].rolledback.contains(fundingTx))
aliceOpenReplyTo.expectMsg(OpenChannelResponse.Disconnected)
listener.expectMsgType[ChannelAborted]
}
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForDualFundingConfirmedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForDualFundingConfirmedStateSpec.scala
index c7aa3ba..f307e2d 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForDualFundingConfirmedStateSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForDualFundingConfirmedStateSpec.scala
@@ -48,11 +48,12 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
val noFundingContribution = "no_funding_contribution"
val liquidityPurchase = "liquidity_purchase"
- case class FixtureParam(alice: TestFSMRef[ChannelState, ChannelData, Channel], bob: TestFSMRef[ChannelState, ChannelData, Channel], alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe, bob2blockchain: TestProbe, aliceListener: TestProbe, bobListener: TestProbe, wallet: SingleKeyOnChainWallet)
+ case class FixtureParam(alice: TestFSMRef[ChannelState, ChannelData, Channel], bob: TestFSMRef[ChannelState, ChannelData, Channel], alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe, bob2blockchain: TestProbe, aliceListener: TestProbe, bobListener: TestProbe, aliceWallet: SingleKeyOnChainWallet, bobWallet: SingleKeyOnChainWallet)
override def withFixture(test: OneArgTest): Outcome = {
- val wallet = new SingleKeyOnChainWallet()
- val setup = init(wallet_opt = Some(wallet), tags = test.tags)
+ val aliceWallet = new SingleKeyOnChainWallet()
+ val bobWallet = new SingleKeyOnChainWallet()
+ val setup = init(walletA_opt = Some(aliceWallet), walletB_opt = Some(bobWallet), tags = test.tags)
import setup._
val aliceListener = TestProbe()
@@ -143,7 +144,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
val expectedBalanceBob = bobContribution.map(_.fundingAmount).getOrElse(0 sat) + liquidityFees.total + initiatorPushAmount.getOrElse(0 msat) - nonInitiatorPushAmount.getOrElse(0 msat) - bobReserve
assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].commitments.availableBalanceForSend == expectedBalanceBob)
}
- withFixture(test.toNoArgTest(FixtureParam(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, aliceListener, bobListener, wallet)))
+ withFixture(test.toNoArgTest(FixtureParam(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, aliceListener, bobListener, aliceWallet, bobWallet)))
}
}
@@ -325,7 +326,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
assert(alice2.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_READY].commitments.active.size == 1)
assert(alice2.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_READY].commitments.inactive.isEmpty)
assert(alice2.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_READY].commitments.latest.fundingTxId == fundingTx1.signedTx.txid)
- testUnusedInputsUnlocked(wallet, Seq(fundingTx2))
+ testUnusedInputsUnlocked(aliceWallet, Seq(fundingTx2))
bob2 ! WatchFundingConfirmedTriggered(BlockHeight(42000), 42, fundingTx1.signedTx)
assert(bobListener.expectMsgType[TransactionConfirmed].tx == fundingTx1.signedTx)
@@ -446,7 +447,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
// The initial funding transaction confirms: we rollback unused inputs.
alice ! WatchFundingConfirmedTriggered(BlockHeight(42000), 42, fundingTx1.signedTx)
- testUnusedInputsUnlocked(wallet, Seq(fundingTx2, fundingTx3, fundingTx4))
+ testUnusedInputsUnlocked(aliceWallet, Seq(fundingTx2, fundingTx3, fundingTx4))
}
test("recv CMD_BUMP_FUNDING_FEE (liquidity ads)", Tag(ChannelStateTestsTags.DualFunding), Tag(liquidityPurchase)) { f =>
@@ -637,11 +638,11 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
import f._
val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].latestFundingTx.sharedTx.asInstanceOf[FullySignedSharedTransaction].signedTx
val currentBlock = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].waitingSince + 10
- wallet.doubleSpent = Set(fundingTx.txid)
+ aliceWallet.doubleSpent = Set(fundingTx.txid)
alice ! ProcessCurrentBlockHeight(CurrentBlockHeight(currentBlock))
alice2bob.expectMsgType[Error]
alice2blockchain.expectNoMessage(100 millis)
- awaitCond(wallet.rolledback.map(_.txid) == Seq(fundingTx.txid))
+ awaitCond(aliceWallet.rolledback.map(_.txid) == Seq(fundingTx.txid))
aliceListener.expectMsgType[ChannelAborted]
awaitCond(alice.stateName == CLOSED)
}
@@ -652,11 +653,11 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
val currentBlock = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].waitingSince + 10
alice ! INPUT_DISCONNECTED
awaitCond(alice.stateName == OFFLINE)
- wallet.doubleSpent = Set(fundingTx.txid)
+ aliceWallet.doubleSpent = Set(fundingTx.txid)
alice ! ProcessCurrentBlockHeight(CurrentBlockHeight(currentBlock))
alice2bob.expectMsgType[Error]
alice2blockchain.expectNoMessage(100 millis)
- awaitCond(wallet.rolledback.map(_.txid) == Seq(fundingTx.txid))
+ awaitCond(aliceWallet.rolledback.map(_.txid) == Seq(fundingTx.txid))
aliceListener.expectMsgType[ChannelAborted]
awaitCond(alice.stateName == CLOSED)
}
@@ -803,7 +804,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
alice2bob.expectNoMessage(100 millis)
awaitCond(alice.stateData.isInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_READY])
assert(alice.stateName == OFFLINE)
- testUnusedInputsUnlocked(wallet, Seq(fundingTx2))
+ testUnusedInputsUnlocked(aliceWallet, Seq(fundingTx2))
// Bob broadcasts his commit tx.
alice ! WatchFundingSpentTriggered(bobCommitTx1)
@@ -876,7 +877,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
alice2blockchain.expectWatchTxConfirmed(bobCommitTx1.txid)
alice2blockchain.expectWatchOutputSpent(claimMainAlice.input)
awaitCond(alice2.stateName == CLOSING)
- testUnusedInputsUnlocked(wallet, Seq(fundingTx2))
+ testUnusedInputsUnlocked(aliceWallet, Seq(fundingTx2))
bob2 ! WatchFundingConfirmedTriggered(BlockHeight(42000), 42, fundingTx1)
assert(bobListener.expectMsgType[TransactionConfirmed].tx == fundingTx1)
@@ -1375,7 +1376,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
Transaction.correctlySpends(claimMain1.tx, Seq(aliceCommitTx1), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
alice2blockchain.expectWatchTxConfirmed(aliceCommitTx1.txid)
alice2blockchain.expectWatchOutputsSpent(Seq(anchorTx1.input.outPoint, claimMain1.input))
- testUnusedInputsUnlocked(wallet, Seq(fundingTx2))
+ testUnusedInputsUnlocked(aliceWallet, Seq(fundingTx2))
// Bob publishes his commit tx, Alice reacts by spending her remote main output.
alice ! WatchFundingSpentTriggered(bobCommitTx1)
@@ -1434,14 +1435,14 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
alice.stop()
bob.stop()
- val alice2 = TestFSMRef(new Channel(aliceNodeParams, aliceKeys, wallet, bobNodeParams.nodeId, alice2blockchain.ref, TestProbe().ref, FakeTxPublisherFactory(alice2blockchain)), alicePeer)
+ val alice2 = TestFSMRef(new Channel(aliceNodeParams, aliceKeys, aliceWallet, bobNodeParams.nodeId, alice2blockchain.ref, TestProbe().ref, FakeTxPublisherFactory(alice2blockchain)), alicePeer)
alice2 ! INPUT_RESTORED(aliceData)
alice2blockchain.expectMsgType[SetChannelId]
// When restoring, we watch confirmation of all potential funding transactions to detect offline force-closes.
aliceData.allFundingTxs.foreach(f => alice2blockchain.expectMsgType[WatchFundingConfirmed].txId == f.sharedTx.txId)
awaitCond(alice2.stateName == OFFLINE)
- val bob2 = TestFSMRef(new Channel(bobNodeParams, bobKeys, wallet, aliceNodeParams.nodeId, bob2blockchain.ref, TestProbe().ref, FakeTxPublisherFactory(bob2blockchain)), bobPeer)
+ val bob2 = TestFSMRef(new Channel(bobNodeParams, bobKeys, bobWallet, aliceNodeParams.nodeId, bob2blockchain.ref, TestProbe().ref, FakeTxPublisherFactory(bob2blockchain)), bobPeer)
bob2 ! INPUT_RESTORED(bobData)
bob2blockchain.expectMsgType[SetChannelId]
assert(bob2blockchain.expectMsgType[WatchFundingConfirmed].txId == bobData.commitments.latest.fundingTxId)
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalSplicesStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalSplicesStateSpec.scala
index f1c7658..5f8c3db 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalSplicesStateSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalSplicesStateSpec.scala
@@ -380,7 +380,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
}
test("recv CMD_SPLICE (splice-in, non dual-funded channel)") { () =>
- val f = init(tags = Set.empty, wallet_opt = Some(new SingleKeyOnChainWallet()))
+ val f = init(tags = Set.empty, walletA_opt = Some(new SingleKeyOnChainWallet()), walletB_opt = Some(new SingleKeyOnChainWallet()))
import f._
reachNormal(f, tags = Set.empty) // we open a non dual-funded channel
@@ -3596,7 +3596,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
val remoteMain = alice2blockchain.expectFinalTxPublished("remote-main-delayed")
val claimHtlcTimeout = htlcs.aliceToBob.map(_ => alice2blockchain.expectReplaceableTxPublished[ClaimHtlcTimeoutTx])
claimHtlcTimeout.foreach(htlcTx => Transaction.correctlySpends(htlcTx.sign(), Seq(bobCommitTx1), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS))
- awaitCond(wallet.asInstanceOf[SingleKeyOnChainWallet].abandoned.contains(fundingTx2.txid))
+ awaitCond(aliceWallet.asInstanceOf[SingleKeyOnChainWallet].abandoned.contains(fundingTx2.txid))
// this one fires immediately, tx is already confirmed
alice2blockchain.expectWatchTxConfirmed(bobCommitTx1.txid)
alice ! WatchTxConfirmedTriggered(BlockHeight(400000), 42, bobCommitTx1)
@@ -3699,7 +3699,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
alice2blockchain.expectWatchTxConfirmed(bobRevokedCommitTx.txid)
alice2blockchain.expectWatchOutputsSpent(Seq(remoteMain.input, mainPenalty.input) ++ htlcPenalty.map(_.input))
alice2blockchain.expectNoMessage(100 millis)
- awaitCond(wallet.asInstanceOf[SingleKeyOnChainWallet].abandoned.contains(fundingTx2.txid))
+ awaitCond(aliceWallet.asInstanceOf[SingleKeyOnChainWallet].abandoned.contains(fundingTx2.txid))
// Alice sends a failure upstream for every outgoing HTLC, including the ones that don't appear in the revoked commitment.
val outgoingHtlcs = (htlcs.aliceToBob.map(_._2) ++ Set(htlcOut1, htlcOut2)).map(htlc => (htlc, alice.stateData.asInstanceOf[DATA_CLOSING].commitments.originChannels(htlc.id)))
@@ -4039,7 +4039,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
alice2blockchain.expectNoMessage(100 millis)
bob2blockchain.expectNoMessage(100 millis)
- val alice2 = TestFSMRef(new Channel(aliceNodeParams, aliceKeys, wallet, bobNodeParams.nodeId, alice2blockchain.ref, TestProbe().ref, FakeTxPublisherFactory(alice2blockchain)), alicePeer)
+ val alice2 = TestFSMRef(new Channel(aliceNodeParams, aliceKeys, aliceWallet, bobNodeParams.nodeId, alice2blockchain.ref, TestProbe().ref, FakeTxPublisherFactory(alice2blockchain)), alicePeer)
alice2 ! INPUT_RESTORED(aliceData)
alice2blockchain.expectMsgType[SetChannelId]
alice2blockchain.expectWatchFundingConfirmed(fundingTx2.txid)
@@ -4047,7 +4047,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
alice2blockchain.expectWatchFundingSpent(fundingTxId0)
alice2blockchain.expectNoMessage(100 millis)
- val bob2 = TestFSMRef(new Channel(bobNodeParams, bobKeys, wallet, aliceNodeParams.nodeId, bob2blockchain.ref, TestProbe().ref, FakeTxPublisherFactory(bob2blockchain)), bobPeer)
+ val bob2 = TestFSMRef(new Channel(bobNodeParams, bobKeys, bobWallet, aliceNodeParams.nodeId, bob2blockchain.ref, TestProbe().ref, FakeTxPublisherFactory(bob2blockchain)), bobPeer)
bob2 ! INPUT_RESTORED(bobData)
bob2blockchain.expectMsgType[SetChannelId]
bob2blockchain.expectWatchFundingConfirmed(fundingTx2.txid)
@@ -4100,7 +4100,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
alice2blockchain.expectNoMessage(100 millis)
bob2blockchain.expectNoMessage(100 millis)
- val alice2 = TestFSMRef(new Channel(aliceNodeParams, aliceKeys, wallet, bobNodeParams.nodeId, alice2blockchain.ref, TestProbe().ref, FakeTxPublisherFactory(alice2blockchain)), alicePeer)
+ val alice2 = TestFSMRef(new Channel(aliceNodeParams, aliceKeys, aliceWallet, bobNodeParams.nodeId, alice2blockchain.ref, TestProbe().ref, FakeTxPublisherFactory(alice2blockchain)), alicePeer)
alice2 ! INPUT_RESTORED(aliceData)
alice2blockchain.expectMsgType[SetChannelId]
alice2blockchain.expectWatchPublished(fundingTx2.txid)
@@ -4108,7 +4108,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
alice2blockchain.expectWatchFundingSpent(fundingTx0.txid)
alice2blockchain.expectNoMessage(100 millis)
- val bob2 = TestFSMRef(new Channel(bobNodeParams, bobKeys, wallet, aliceNodeParams.nodeId, bob2blockchain.ref, TestProbe().ref, FakeTxPublisherFactory(bob2blockchain)), bobPeer)
+ val bob2 = TestFSMRef(new Channel(bobNodeParams, bobKeys, bobWallet, aliceNodeParams.nodeId, bob2blockchain.ref, TestProbe().ref, FakeTxPublisherFactory(bob2blockchain)), bobPeer)
bob2 ! INPUT_RESTORED(bobData)
bob2blockchain.expectMsgType[SetChannelId]
bob2blockchain.expectWatchPublished(fundingTx2.txid)
Why this scored 60/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.