diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/rpc/BitcoinCoreClient.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/rpc/BitcoinCoreClient.scala index 182c560190..1d511d3244 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/rpc/BitcoinCoreClient.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/rpc/BitcoinCoreClient.scala @@ -213,7 +213,12 @@ class BitcoinCoreClient(val chainHash: ByteVector32, val rpcClient: BitcoinJsonR } private def signPsbtOrUnlock(psbt: Psbt)(implicit ec: ExecutionContext): Future[ProcessPsbtResponse] = { - val f = processPsbt(psbt).withFilter(_.complete == true) + val f1 = processPsbt(psbt).withFilter(_.complete == true) + + val f = for { + ProcessPsbtResponse(psbt1, complete) <- processPsbt(psbt) + _ = if (!complete) throw JsonRPCError(Error(0, "cannot sign psbt")) + } yield ProcessPsbtResponse(psbt1, complete) // if signature fails (e.g. because wallet is encrypted) we need to unlock the utxos f.recoverWith { case _ => unlockOutpoints(psbt.global.tx.txIn.map(_.outPoint)) @@ -240,21 +245,33 @@ class BitcoinCoreClient(val chainHash: ByteVector32, val rpcClient: BitcoinJsonR case _ => return Future.failed(new IllegalArgumentException("invalid pubkey script")) } - for { - // we ask bitcoin core to create and fund the funding tx - feerate <- mempoolMinFee().map(minFee => FeeratePerKw(minFee).max(feeRatePerKw)) - FundPsbtResponse(psbt, fee, Some(changePos)) <- fundPsbt(Seq(fundingAddress -> amount), 0, FundPsbtOptions(feerate, lockUtxos = true)) - ourbip32path = localFundingKey.path.drop(2) - output = psbt.outputs(1 - changePos).copy( + def updatePsbt(psbt: Psbt, changepos_opt: Option[Int], ourbip32path: Seq[Long]): Psbt = { + val outputIndex = changepos_opt match { + case None => 0 + case Some(changePos) => 1 - changePos + } + val output = psbt.outputs(outputIndex).copy( derivationPaths = Map( localFundingKey.publicKey -> Psbt.KeyPathWithMaster(localFundingKey.parent, ourbip32path), remoteFundingKey -> Psbt.KeyPathWithMaster(0L, DeterministicWallet.KeyPath("1/2/3/4")) ) ) - psbt1 = psbt.copy(outputs = psbt.outputs.updated(1 - changePos, output)) + psbt.copy(outputs = psbt.outputs.updated(outputIndex, output)) + } + + for { + // we ask bitcoin core to create and fund the funding tx + feerate <- mempoolMinFee().map(minFee => FeeratePerKw(minFee).max(feeRatePerKw)) + FundPsbtResponse(psbt, fee, changePos_opt) <- fundPsbt(Seq(fundingAddress -> amount), 0, FundPsbtOptions(feerate, lockUtxos = true, changePosition = Some(1))) + ourbip32path = localFundingKey.path.drop(2) + _ = logger.info(s"funded psbt = $psbt") + psbt1 = updatePsbt(psbt, changePos_opt, ourbip32path) // now let's sign the funding tx - ProcessPsbtResponse(signedPsbt, true) <- signPsbtOrUnlock(psbt1) - Success(fundingTx) = signedPsbt.extract() + ProcessPsbtResponse(signedPsbt, complete) <- signPsbtOrUnlock(psbt1) + _ = logger.info(s"psbt signing complete = $complete") + extracted = signedPsbt.extract() + _ = if (extracted.isFailure) logger.error(s"psbt failure $extracted") + fundingTx = extracted.get // there will probably be a change output, so we need to find which output is ours outputIndex <- Transactions.findPubKeyScriptIndex(fundingTx, fundingPubkeyScript) match { case Right(outputIndex) => Future.successful(outputIndex) @@ -262,7 +279,6 @@ class BitcoinCoreClient(val chainHash: ByteVector32, val rpcClient: BitcoinJsonR } _ = logger.debug(s"created funding txid=${fundingTx.txid} outputIndex=$outputIndex fee=${fee}") } yield MakeFundingTxResponse(signedPsbt, outputIndex, fee) - } def commit(tx: Transaction)(implicit ec: ExecutionContext): Future[Boolean] = publishTransaction(tx).transformWith { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreClientSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreClientSpec.scala index 1b3152cb92..6e57675ade 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreClientSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreClientSpec.scala @@ -65,7 +65,7 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A bitcoinClient.makeFundingTx(ExtendedPublicKey(randomKey().publicKey.value, randomBytes32(), 4, KeyPath("m/1/2/3/4"), 0), randomKey().publicKey, 50 millibtc, FeeratePerKw(10000 sat)).pipeTo(sender.ref) val error = sender.expectMsgType[Failure].cause.asInstanceOf[JsonRPCError].error - assert(error.message.contains("Please enter the wallet passphrase with walletpassphrase first")) + assert(error.message.contains("cannot sign psbt")) sender.send(bitcoincli, BitcoinReq("walletpassphrase", walletPassword, 10)) sender.expectMsgType[JValue] @@ -189,7 +189,7 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A } val sender = TestProbe() - val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, bitcoinrpcclient) + val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, rpcClient) bitcoinClient.onChainBalance().pipeTo(sender.ref) assert(sender.expectMsgType[OnChainBalance] === OnChainBalance(Satoshi(satoshi), Satoshi(satoshi))) 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 ac7eddb4b6..c7506ace11 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 @@ -90,7 +90,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w // Create a unique wallet for this test and ensure it has some btc. val testId = UUID.randomUUID() val walletRpcClient = createWallet(s"lightning-$testId") - val walletClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, bitcoinrpcclient) + val walletClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, walletRpcClient) val probe = TestProbe() // Ensure our wallet has some funds. diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsBatchValidationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsBatchValidationSpec.scala index e6c12cefea..cf9a59c67d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsBatchValidationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsBatchValidationSpec.scala @@ -92,7 +92,7 @@ object AnnouncementsBatchValidationSpec { val node2BitcoinKey = randomKey() val amount = 1000000 sat // first we publish the funding tx - val fundingTxFuture = bitcoinClient.makeFundingTx(ExtendedPublicKey(randomKey().publicKey.value, randomBytes32(), 4, KeyPath("m/1/2/3/4"), 0), randomKey().publicKey, amount, FeeratePerKw(10000 sat)) + val fundingTxFuture = bitcoinClient.makeFundingTx(ExtendedPublicKey(node1BitcoinKey.publicKey.value, randomBytes32(), 4, KeyPath("m/1/2/3/4"), 0), node2BitcoinKey.publicKey, amount, FeeratePerKw(10000 sat)) val res = Await.result(fundingTxFuture, 10 seconds) Await.result(bitcoinClient.publishTransaction(res.psbt.extract().get), 10 seconds) SimulatedChannel(node1Key, node2Key, node1BitcoinKey, node2BitcoinKey, amount, res.psbt.extract().get, res.fundingTxOutputIndex)