What changed, and why it matters
This commit only changes test code. It fixes flaky automated tests in the Eclair Lightning node by introducing a fake wallet that never completes funding transactions, so tests can reliably simulate error events during channel funding. There is no change to production code and no security vulnerability is being fixed or introduced.
No security action required. This is a test reliability improvement. Reviewers may verify that the new blocking wallet is only used in tests and that no production wallet implementation was altered.
Security signals we found
No production code changes
Test-only refactoring
No cryptographic, network, or consensus changes
No input validation, authentication, or authorization changes
Evidence from the diff
The patch modifies three test files. It adds a new BlockingOnChainWallet class whose makeFundingTx returns an uncompleted Promise future, and updates WaitForAcceptChannelStateSpec and WaitForFundingInternalStateSpec to use this wallet. The goal is to prevent race conditions where the dummy wallet completed before the test could assert the WAIT_FOR_FUNDING_INTERNAL state. No production source files are touched.
Changed components
eclair-core/src/test/scala/fr/acinq/eclair/blockchain/DummyOnChainWallet.scalaeclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scalaeclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingInternalStateSpec.scalaInspect captured patch +19 / −6
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 c29ffa4..1ee99db 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
@@ -31,7 +31,7 @@ import fr.acinq.eclair.transactions.Transactions
import fr.acinq.eclair.{TimestampSecond, randomBytes32}
import scodec.bits._
-import scala.concurrent.{ExecutionContext, Future}
+import scala.concurrent.{ExecutionContext, Future, Promise}
import scala.util.{Failure, Random, Success}
/**
@@ -229,6 +229,14 @@ class SingleKeyOnChainWallet extends OnChainWallet with OnChainAddressCache {
override def getReceivePublicKeyScript(renew: Boolean): Seq[ScriptElt] = p2trScript
}
+/** A wallet that blocks when called to fund transactions (useful to test events happening while funding). */
+class BlockingOnChainWallet extends SingleKeyOnChainWallet {
+ override def makeFundingTx(pubkeyScript: ByteVector, amount: Satoshi, feeRatePerKw: FeeratePerKw, feeBudget_opt: Option[Satoshi])(implicit ec: ExecutionContext): Future[MakeFundingTxResponse] = {
+ // We create a dummy promise that will never be completed.
+ Promise().future
+ }
+}
+
object DummyOnChainWallet {
val dummyReceivePubkey: PublicKey = PublicKey(hex"028feba10d0eafd0fad8fe20e6d9206e6bd30242826de05c63f459a00aced24b12")
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 07b371d..94e0953 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,6 +20,7 @@ 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.BlockingOnChainWallet
import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.fsm.Channel
import fr.acinq.eclair.channel.fsm.Channel.TickChannelOpenTimeout
@@ -40,6 +41,7 @@ import scala.concurrent.duration._
class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with ChannelStateTestsBase {
+ private val BlockingOnChainWallet = "blocking_on_chain_wallet"
private val HighRemoteDustLimit = "high_remote_dust_limit"
case class FixtureParam(alice: TestFSMRef[ChannelState, ChannelData, Channel], bob: TestFSMRef[ChannelState, ChannelData, Channel], aliceOpenReplyTo: TestProbe, alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe, listener: TestProbe)
@@ -48,7 +50,8 @@ 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, tags = test.tags)
+ val wallet_opt = if (test.tags.contains(BlockingOnChainWallet)) Some(new BlockingOnChainWallet()) else None
+ val setup = init(aliceNodeParams, Bob.nodeParams, tags = test.tags, walletA_opt = wallet_opt)
import setup._
val channelParams = computeChannelParams(setup, test.tags)
@@ -64,7 +67,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS
}
}
- test("recv AcceptChannel (anchor outputs zero fee htlc txs)") { f =>
+ test("recv AcceptChannel (anchor outputs zero fee htlc txs)", Tag(BlockingOnChainWallet)) { f =>
import f._
val accept = bob2alice.expectMsgType[AcceptChannel]
// Since https://github.com/lightningnetwork/lightning-rfc/pull/714 we must include an empty upfront_shutdown_script.
@@ -76,7 +79,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS
aliceOpenReplyTo.expectNoMessage()
}
- test("recv AcceptChannel (anchor outputs zero fee htlc txs and scid alias)", Tag(ChannelStateTestsTags.ScidAlias)) { f =>
+ test("recv AcceptChannel (anchor outputs zero fee htlc txs and scid alias)", Tag(ChannelStateTestsTags.ScidAlias), Tag(BlockingOnChainWallet)) { f =>
import f._
val accept = bob2alice.expectMsgType[AcceptChannel]
assert(accept.channelType_opt.contains(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true)))
@@ -86,7 +89,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS
aliceOpenReplyTo.expectNoMessage()
}
- test("recv AcceptChannel (simple taproot channels phoenix)", Tag(ChannelStateTestsTags.OptionSimpleTaprootPhoenix)) { f =>
+ test("recv AcceptChannel (simple taproot channels phoenix)", Tag(ChannelStateTestsTags.OptionSimpleTaprootPhoenix), Tag(BlockingOnChainWallet)) { f =>
import f._
val accept = bob2alice.expectMsgType[AcceptChannel]
assert(accept.channelType_opt.contains(ChannelTypes.SimpleTaprootChannelsPhoenix))
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 dc831d4..5c1cb3f 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,6 +19,7 @@ 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.BlockingOnChainWallet
import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.fsm.Channel
import fr.acinq.eclair.channel.fsm.Channel.TickChannelOpenTimeout
@@ -40,7 +41,8 @@ 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(tags = test.tags)
+ // Note that we use a dummy wallet that doesn't complete its transaction funding.
+ val setup = init(tags = test.tags, walletA_opt = Some(new BlockingOnChainWallet()))
import setup._
val channelParams = computeChannelParams(setup, test.tags)
val listener = TestProbe()
Why this scored 15/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.