diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala
index 62ac023704..4c8f9778e3 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala
@@ -241,7 +241,7 @@ class Setup(val datadir: File,
})
_ <- feeratesRetrieved.future
- bitcoinClient = new BitcoinCoreClient(new BatchingBitcoinJsonRPCClient(bitcoin))
+ bitcoinClient = new BitcoinCoreClient(nodeParams.chainHash, new BatchingBitcoinJsonRPCClient(bitcoin))
watcher = {
system.actorOf(SimpleSupervisor.props(Props(new ZMQActor(config.getString("bitcoind.zmqblock"), ZMQActor.Topics.HashBlock, Some(zmqBlockConnected))), "zmqblock", SupervisorStrategy.Restart))
system.actorOf(SimpleSupervisor.props(Props(new ZMQActor(config.getString("bitcoind.zmqtx"), ZMQActor.Topics.RawTx, Some(zmqTxConnected))), "zmqtx", SupervisorStrategy.Restart))
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/OnChainWallet.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/OnChainWallet.scala
index 9a9df7532b..7b98f20d4d 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/OnChainWallet.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/OnChainWallet.scala
@@ -17,7 +17,8 @@
package fr.acinq.eclair.blockchain
import fr.acinq.bitcoin.Crypto.PublicKey
-import fr.acinq.bitcoin.{Satoshi, Transaction}
+import fr.acinq.bitcoin.DeterministicWallet.ExtendedPublicKey
+import fr.acinq.bitcoin.{Psbt, Satoshi, Transaction}
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
import scodec.bits.ByteVector
@@ -33,7 +34,7 @@ trait OnChainChannelFunder {
import OnChainWallet.MakeFundingTxResponse
/** Create a channel funding transaction with the provided pubkeyScript. */
- def makeFundingTx(pubkeyScript: ByteVector, amount: Satoshi, feeRatePerKw: FeeratePerKw)(implicit ec: ExecutionContext): Future[MakeFundingTxResponse]
+ def makeFundingTx(localFundingKey: ExtendedPublicKey, remoteFundingKey: PublicKey, amount: Satoshi, feeRatePerKw: FeeratePerKw)(implicit ec: ExecutionContext): Future[MakeFundingTxResponse]
/**
* Committing *must* include publishing the transaction on the network.
@@ -95,6 +96,6 @@ object OnChainWallet {
final case class OnChainBalance(confirmed: Satoshi, unconfirmed: Satoshi)
- final case class MakeFundingTxResponse(fundingTx: Transaction, fundingTxOutputIndex: Int, fee: Satoshi)
+ final case class MakeFundingTxResponse(psbt: Psbt, fundingTxOutputIndex: Int, fee: Satoshi)
}
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 fc97fea8ab..c9d8ffa2c2 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
@@ -17,6 +17,7 @@
package fr.acinq.eclair.blockchain.bitcoind.rpc
import fr.acinq.bitcoin.Crypto.PublicKey
+import fr.acinq.bitcoin.DeterministicWallet.ExtendedPublicKey
import fr.acinq.bitcoin._
import fr.acinq.eclair.ShortChannelId.coordinates
import fr.acinq.eclair.TxCoordinates
@@ -24,7 +25,7 @@ import fr.acinq.eclair.blockchain.OnChainWallet
import fr.acinq.eclair.blockchain.OnChainWallet.{MakeFundingTxResponse, OnChainBalance}
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.{GetTxWithMetaResponse, UtxoStatus, ValidateResult}
import fr.acinq.eclair.blockchain.fee.{FeeratePerKB, FeeratePerKw}
-import fr.acinq.eclair.transactions.Transactions
+import fr.acinq.eclair.transactions.{Scripts, Transactions}
import fr.acinq.eclair.wire.protocol.ChannelAnnouncement
import grizzled.slf4j.Logging
import org.json4s.Formats
@@ -41,7 +42,7 @@ import scala.util.{Failure, Success, Try}
/**
* The Bitcoin Core client provides some high-level utility methods to interact with Bitcoin Core.
*/
-class BitcoinCoreClient(val rpcClient: BitcoinJsonRPCClient) extends OnChainWallet with Logging {
+class BitcoinCoreClient(val chainHash: ByteVector32, val rpcClient: BitcoinJsonRPCClient) extends OnChainWallet with Logging {
import BitcoinCoreClient._
@@ -180,25 +181,102 @@ class BitcoinCoreClient(val rpcClient: BitcoinJsonRPCClient) extends OnChainWall
})
}
- def makeFundingTx(pubkeyScript: ByteVector, amount: Satoshi, targetFeerate: FeeratePerKw)(implicit ec: ExecutionContext): Future[MakeFundingTxResponse] = {
- val partialFundingTx = Transaction(
- version = 2,
- txIn = Seq.empty[TxIn],
- txOut = TxOut(amount, pubkeyScript) :: Nil,
- lockTime = 0)
+ def fundPsbt(inputs: Seq[FundPsbtInput], outputs: Seq[(String, Satoshi)], locktime: Long, options: FundPsbtOptions)(implicit ec: ExecutionContext): Future[FundPsbtResponse] = {
+ rpcClient.invoke("walletcreatefundedpsbt", inputs.toArray, outputs.map { case (a, b) => a -> b.toBtc.toBigDecimal }, locktime, options).map(json => {
+ val JString(base64) = json \ "psbt"
+ val JInt(changePos) = json \ "changepos"
+ val JDecimal(fee) = json \ "fee"
+ val psbt = Psbt.fromBase64(base64).get
+ val changePos_opt = if (changePos >= 0) Some(changePos.intValue) else None
+ FundPsbtResponse(psbt, toSatoshi(fee), changePos_opt)
+ })
+ }
+
+ def fundPsbt(outputs: Seq[(String, Satoshi)], locktime: Long, options: FundPsbtOptions)(implicit ec: ExecutionContext): Future[FundPsbtResponse] =
+ fundPsbt(Seq(), outputs, locktime, options)
+
+ def processPsbt(psbt: Psbt, sign: Boolean = true, sighashType: Int = SIGHASH_ALL)(implicit ec: ExecutionContext): Future[ProcessPsbtResponse] = {
+ val sighashStrings = Map(
+ SIGHASH_ALL -> "ALL",
+ SIGHASH_NONE -> "NONE",
+ SIGHASH_SINGLE -> "SINGLE",
+ (SIGHASH_ALL | SIGHASH_ANYONECANPAY) -> "ALL|ANYONECANPAY",
+ (SIGHASH_NONE | SIGHASH_ANYONECANPAY) -> "NONE|ANYONECANPAY",
+ (SIGHASH_SINGLE | SIGHASH_ANYONECANPAY) -> "SINGLE|ANYONECANPAY")
+ val sighash = sighashStrings.getOrElse(sighashType, throw new IllegalArgumentException(s"invalid sighash flag ${sighashType}"))
+ rpcClient.invoke("walletprocesspsbt", Psbt.toBase64(psbt), sign, sighash).map(json => {
+ val JString(base64) = json \ "psbt"
+ val JBool(complete) = json \ "complete"
+ val psbt = Psbt.fromBase64(base64).get
+ ProcessPsbtResponse(psbt, complete)
+ })
+ }
+
+ private def signPsbtOrUnlock(psbt: Psbt)(implicit ec: ExecutionContext): Future[ProcessPsbtResponse] = {
+ 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))
+ .recover { case t: Throwable => // no-op, just add a log in case of failure
+ logger.warn(s"Cannot unlock failed transaction's UTXOs txid=${psbt.global.tx.txid}", t)
+ t
+ }
+ .flatMap(_ => f) // return signTransaction error
+ .recoverWith { case _ => f } // return signTransaction error
+ }
+ }
+
+ override def makeFundingTx(localFundingKey: ExtendedPublicKey, remoteFundingKey: PublicKey, amount: Satoshi, feeRatePerKw: FeeratePerKw)(implicit ec: ExecutionContext): Future[MakeFundingTxResponse] = {
+ val hrp = chainHash match {
+ case Block.RegtestGenesisBlock.hash => "bcrt"
+ case Block.TestnetGenesisBlock.hash => "tb"
+ case Block.LivenetGenesisBlock.hash => "bc"
+ case _ => return Future.failed(new IllegalArgumentException(s"invalid chain hash ${chainHash}"))
+ }
+ logger.info(s"funding psbt with local_funding_key=$localFundingKey and remote_funding_key=$remoteFundingKey")
+ val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(localFundingKey.publicKey, remoteFundingKey)))
+ val fundingAddress = Script.parse(fundingPubkeyScript) match {
+ case OP_0 :: OP_PUSHDATA(data, _) :: Nil if data.size == 20 || data.size == 32 => Bech32.encodeWitnessAddress(hrp, 0, data)
+ case _ => return Future.failed(new IllegalArgumentException("invalid pubkey script"))
+ }
+
+ 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
+ }
+ psbt.updateWitnessOutput(outputIndex, derivationPaths = Map(
+ localFundingKey.publicKey -> Psbt.KeyPathWithMaster(localFundingKey.parent, ourbip32path),
+ remoteFundingKey -> Psbt.KeyPathWithMaster(0L, DeterministicWallet.KeyPath("1/2/3/4"))
+ )
+ ).get
+ }
+
for {
- feerate <- mempoolMinFee().map(minFee => FeeratePerKw(minFee).max(targetFeerate))
- // we ask bitcoin core to add inputs to the funding tx, and use the specified change address
- fundTxResponse <- fundTransaction(partialFundingTx, FundTransactionOptions(feerate, lockUtxos = true))
+ // 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
- SignTransactionResponse(fundingTx, true) <- signTransactionOrUnlock(fundTxResponse.tx)
+ 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, pubkeyScript) match {
+ outputIndex <- Transactions.findPubKeyScriptIndex(fundingTx, fundingPubkeyScript) match {
case Right(outputIndex) => Future.successful(outputIndex)
case Left(skipped) => Future.failed(new RuntimeException(skipped.toString))
}
- _ = logger.debug(s"created funding txid=${fundingTx.txid} outputIndex=$outputIndex fee=${fundTxResponse.fee}")
- } yield MakeFundingTxResponse(fundingTx, outputIndex, fundTxResponse.fee)
+ _ = 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 {
@@ -437,6 +515,20 @@ object BitcoinCoreClient {
val amountIn: Satoshi = fee + tx.txOut.map(_.amount).sum
}
+ case class FundPsbtInput(txid: ByteVector32, vout: Int, sequence_opt: Option[Long] = None)
+
+ case class FundPsbtOptions(feeRate: BigDecimal, replaceable: Boolean, lockUnspents: Boolean, changePosition: Option[Int], add_inputs: Boolean)
+
+ object FundPsbtOptions {
+ def apply(feerate: FeeratePerKw, replaceable: Boolean = true, lockUtxos: Boolean = false, changePosition: Option[Int] = None, add_inputs: Boolean = true): FundPsbtOptions = {
+ FundPsbtOptions(BigDecimal(FeeratePerKB(feerate).toLong).bigDecimal.scaleByPowerOfTen(-8), replaceable, lockUtxos, changePosition, add_inputs)
+ }
+ }
+
+ case class FundPsbtResponse(psbt: Psbt, fee: Satoshi, changePosition: Option[Int]) {
+ val amountIn: Satoshi = psbt.computeFees().get + psbt.global.tx.txOut.map(_.amount).sum
+ }
+
case class PreviousTx(txid: ByteVector32, vout: Long, scriptPubKey: String, redeemScript: String, witnessScript: String, amount: BigDecimal)
object PreviousTx {
@@ -452,6 +544,8 @@ object BitcoinCoreClient {
case class SignTransactionResponse(tx: Transaction, complete: Boolean)
+ case class ProcessPsbtResponse(psbt: Psbt, complete: Boolean)
+
/**
* Information about a transaction currently in the mempool.
*
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 c92e9fcff4..eeff04957a 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
@@ -413,8 +413,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, remo
shutdownScript = remoteShutdownScript)
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)
+ wallet.makeFundingTx(localFundingPubkey, remoteParams.fundingPubKey, fundingSatoshis, fundingTxFeeratePerKw).pipeTo(self)
goto(WAIT_FOR_FUNDING_INTERNAL) using DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId, localParams, remoteParams, fundingSatoshis, pushMsat, initialFeeratePerKw, accept.firstPerCommitmentPoint, channelConfig, channelFeatures, open)
}
@@ -436,7 +435,8 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, remo
})
when(WAIT_FOR_FUNDING_INTERNAL)(handleExceptions {
- case Event(MakeFundingTxResponse(fundingTx, fundingTxOutputIndex, fundingTxFee), d@DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId, localParams, remoteParams, fundingAmount, pushMsat, initialFeeratePerKw, remoteFirstPerCommitmentPoint, channelConfig, channelFeatures, open)) =>
+ case Event(MakeFundingTxResponse(psbt, fundingTxOutputIndex, fundingTxFee), d@DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId, localParams, remoteParams, fundingAmount, pushMsat, initialFeeratePerKw, remoteFirstPerCommitmentPoint, channelConfig, channelFeatures, open)) =>
+ val fundingTx = psbt.extract().get
// let's create the first commitment tx that spends the yet uncommitted funding tx
Funding.makeFirstCommitTxs(keyManager, channelConfig, channelFeatures, temporaryChannelId, localParams, remoteParams, fundingAmount, pushMsat, initialFeeratePerKw, fundingTx.hash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint) match {
case Left(ex) => handleLocalError(ex, d, None)
@@ -1574,10 +1574,10 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, remo
log.info("shutting down")
stop(FSM.Normal)
- case Event(MakeFundingTxResponse(fundingTx, _, _), _) =>
+ case Event(MakeFundingTxResponse(psbt, _, _), _) =>
// this may happen if connection is lost, or remote sends an error while we were waiting for the funding tx to be created by our wallet
// in that case we rollback the tx
- wallet.rollback(fundingTx)
+ wallet.rollback(psbt.extract().get)
stay()
case Event(INPUT_DISCONNECTED, _) => stay() // we are disconnected, but it doesn't matter anymore
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 f18ca3574b..ea61a0f08d 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
@@ -18,11 +18,11 @@ package fr.acinq.eclair.channel.publish
import akka.actor.typed.scaladsl.{ActorContext, Behaviors, TimerScheduler}
import akka.actor.typed.{ActorRef, Behavior}
-import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Crypto, OutPoint, Satoshi, Script, Transaction, TxOut}
-import fr.acinq.eclair.NodeParams
+import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Crypto, OutPoint, Psbt, Satoshi, Script, Transaction, TxOut, computeP2WpkhAddress}
+import fr.acinq.eclair.{NodeParams, publicKeyScriptToAddress}
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher
import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient
-import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient.FundTransactionOptions
+import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient.{FundPsbtOptions, FundPsbtResponse, FundTransactionOptions}
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
import fr.acinq.eclair.channel.publish.TxPublisher.{TxPublishLogContext, TxRejectedReason}
import fr.acinq.eclair.channel.publish.TxTimeLocksMonitor.CheckTx
@@ -53,7 +53,7 @@ object ReplaceableTxPublisher {
private case object RemoteCommitTxPublished extends RuntimeException with Command
private case object PreconditionsOk extends Command
private case class FundingFailed(reason: Throwable) extends Command
- private case class SignFundedTx(tx: ReplaceableTransactionWithInputInfo, fee: Satoshi) extends Command
+ private case class SignFundedTx(tx: ReplaceableTransactionWithInputInfo, fee: Satoshi, psbt: Psbt) extends Command
private case class PublishSignedTx(tx: Transaction) extends Command
private case class WrappedTxResult(result: MempoolTxMonitor.TxResult) extends Command
private case class UnknownFailure(reason: Throwable) extends Command
@@ -265,13 +265,13 @@ private class ReplaceableTxPublisher(nodeParams: NodeParams,
def fund(replyTo: ActorRef[TxPublisher.PublishTxResult], cmd: TxPublisher.PublishReplaceableTx, targetFeerate: FeeratePerKw): Behavior[Command] = {
context.pipeToSelf(addInputs(cmd.txInfo, targetFeerate, cmd.commitments)) {
- case Success((fundedTx, fee)) => SignFundedTx(fundedTx, fee)
+ case Success((fundedTx, fee, psbt)) => SignFundedTx(fundedTx, fee, psbt)
case Failure(reason) => FundingFailed(reason)
}
Behaviors.receiveMessagePartial {
- case SignFundedTx(fundedTx, fee) =>
+ case SignFundedTx(fundedTx, fee, psbt) =>
log.info("added {} wallet input(s) and {} wallet output(s) to {}", fundedTx.tx.txIn.length - 1, fundedTx.tx.txOut.length - 1, cmd.desc)
- sign(replyTo, cmd, fundedTx, fee)
+ sign(replyTo, cmd, fundedTx, fee, psbt)
case FundingFailed(reason) =>
if (reason.getMessage.contains("Insufficient funds")) {
log.warn("cannot add inputs to {}: {}", cmd.desc, reason.getMessage)
@@ -287,14 +287,18 @@ private class ReplaceableTxPublisher(nodeParams: NodeParams,
}
}
- def sign(replyTo: ActorRef[TxPublisher.PublishTxResult], cmd: TxPublisher.PublishReplaceableTx, fundedTx: ReplaceableTransactionWithInputInfo, fee: Satoshi): Behavior[Command] = {
+ def sign(replyTo: ActorRef[TxPublisher.PublishTxResult], cmd: TxPublisher.PublishReplaceableTx, fundedTx: ReplaceableTransactionWithInputInfo, fee: Satoshi, psbt: Psbt): Behavior[Command] = {
fundedTx match {
case claimAnchorTx: ClaimLocalAnchorOutputTx =>
val claimAnchorSig = keyManager.sign(claimAnchorTx, keyManager.fundingPublicKey(cmd.commitments.localParams.fundingKeyPath), TxOwner.Local, cmd.commitments.commitmentFormat)
val signedClaimAnchorTx = addSigs(claimAnchorTx, claimAnchorSig)
- val commitInfo = BitcoinCoreClient.PreviousTx(signedClaimAnchorTx.input, signedClaimAnchorTx.tx.txIn.head.witness)
- context.pipeToSelf(bitcoinClient.signTransaction(signedClaimAnchorTx.tx, Seq(commitInfo))) {
- case Success(signedTx) => PublishSignedTx(signedTx.tx)
+ // update our psbt with our signature for our input, and ask bitcoin core to sign its input
+ val psbt1 = psbt.finalizeWitnessInput(0, signedClaimAnchorTx.tx.txIn(0).witness).get
+ context.pipeToSelf(bitcoinClient.processPsbt(psbt1).map(processPbbtResponse => {
+ // all inputs should be signed now
+ processPbbtResponse.psbt.extract().get
+ })) {
+ case Success(signedTx) => PublishSignedTx(signedTx)
case Failure(reason) => UnknownFailure(reason)
}
case htlcTx: HtlcTx =>
@@ -306,11 +310,12 @@ private class ReplaceableTxPublisher(nodeParams: NodeParams,
val localHtlcBasepoint = keyManager.htlcPoint(channelKeyPath)
val localSig = keyManager.sign(htlcTx, localHtlcBasepoint, localPerCommitmentPoint, TxOwner.Local, cmd.commitments.commitmentFormat)
val signedHtlcTx = txWithWitnessData.addSigs(localSig, cmd.commitments.commitmentFormat)
- val inputInfo = BitcoinCoreClient.PreviousTx(signedHtlcTx.input, signedHtlcTx.tx.txIn.head.witness)
- context.pipeToSelf(bitcoinClient.signTransaction(signedHtlcTx.tx, Seq(inputInfo), allowIncomplete = true).map(signTxResponse => {
- // NB: bitcoind versions older than 0.21.1 messes up the witness stack for our htlc input, so we need to restore it.
- // See https://github.com/bitcoin/bitcoin/issues/21151
- signedHtlcTx.tx.copy(txIn = signedHtlcTx.tx.txIn.head +: signTxResponse.tx.txIn.tail)
+
+ // update our psbt with our signature for our input, and ask bitcoin core to sign its input
+ val psbt1 = psbt.finalizeWitnessInput(0, signedHtlcTx.tx.txIn(0).witness).get
+ context.pipeToSelf(bitcoinClient.processPsbt(psbt1).map(processPbbtResponse => {
+ // all inputs should be signed now
+ processPbbtResponse.psbt.extract().get
})) {
case Success(signedTx) => PublishSignedTx(signedTx)
case Failure(reason) => UnknownFailure(reason)
@@ -371,14 +376,14 @@ private class ReplaceableTxPublisher(nodeParams: NodeParams,
}
}
- private def addInputs(txInfo: ReplaceableTransactionWithInputInfo, targetFeerate: FeeratePerKw, commitments: Commitments): Future[(ReplaceableTransactionWithInputInfo, Satoshi)] = {
+ private def addInputs(txInfo: ReplaceableTransactionWithInputInfo, targetFeerate: FeeratePerKw, commitments: Commitments): Future[(ReplaceableTransactionWithInputInfo, Satoshi, Psbt)] = {
txInfo match {
case anchorTx: ClaimLocalAnchorOutputTx => addInputs(anchorTx, targetFeerate, commitments)
case htlcTx: HtlcTx => addInputs(htlcTx, targetFeerate, commitments)
}
}
- private def addInputs(txInfo: ClaimLocalAnchorOutputTx, targetFeerate: FeeratePerKw, commitments: Commitments): Future[(ClaimLocalAnchorOutputTx, Satoshi)] = {
+ private def addInputs(txInfo: ClaimLocalAnchorOutputTx, targetFeerate: FeeratePerKw, commitments: Commitments): Future[(ClaimLocalAnchorOutputTx, Satoshi, Psbt)] = {
val dustLimit = commitments.localParams.dustLimit
val commitFeerate = commitments.localCommit.spec.commitTxFeerate
val commitTx = commitments.fullySignedLocalCommitTx(nodeParams.channelKeyManager).tx
@@ -397,31 +402,52 @@ private class ReplaceableTxPublisher(nodeParams: NodeParams,
// That's ok, we can increase the fee later by decreasing the output amount. But we need to ensure we'll have enough
// to cover the weight of our anchor input, which is why we set it to the following value.
val dummyChangeAmount = weight2fee(anchorFeerate, claimAnchorOutputMinWeight) + dustLimit
- val txNotFunded = Transaction(2, Nil, TxOut(dummyChangeAmount, Script.pay2wpkh(PlaceHolderPubKey)) :: Nil, 0)
- bitcoinClient.fundTransaction(txNotFunded, FundTransactionOptions(anchorFeerate, lockUtxos = true)).flatMap(fundTxResponse => {
- // We merge the outputs if there's more than one.
- fundTxResponse.changePosition match {
+ val address = publicKeyScriptToAddress(Script.pay2wpkh(PlaceHolderPubKey), nodeParams.chainHash)
+
+ // merge outptuts if needed to get a PSBT with a single output
+ def makeSingleOutput(fundPsbtResponse: FundPsbtResponse): Future[Psbt] = {
+ fundPsbtResponse.changePosition match {
case Some(changePos) =>
- val changeOutput = fundTxResponse.tx.txOut(changePos)
- val txSingleOutput = fundTxResponse.tx.copy(txOut = Seq(changeOutput.copy(amount = changeOutput.amount + dummyChangeAmount)))
- Future.successful(fundTxResponse.copy(tx = txSingleOutput))
+ // add our main output to the change output
+ val changeOutput = fundPsbtResponse.psbt.global.tx.txOut(changePos)
+ val changeOutput1 = changeOutput.copy(amount = changeOutput.amount + dummyChangeAmount)
+ val psbt1 = fundPsbtResponse.psbt.copy(
+ global = fundPsbtResponse.psbt.global.copy(tx = fundPsbtResponse.psbt.global.tx.copy(txOut = Seq(changeOutput1))),
+ outputs = Seq(fundPsbtResponse.psbt.outputs(changePos))
+ )
+ Future.successful(psbt1)
case None =>
+ // replace our main output with a dummy change output
bitcoinClient.getChangeAddress().map(pubkeyHash => {
- val txSingleOutput = fundTxResponse.tx.copy(txOut = Seq(TxOut(dummyChangeAmount, Script.pay2wpkh(pubkeyHash))))
- fundTxResponse.copy(tx = txSingleOutput)
+ val changeOutput1 = TxOut(dummyChangeAmount, Script.pay2wpkh(pubkeyHash))
+ fundPsbtResponse.psbt.copy(
+ global = fundPsbtResponse.psbt.global.copy(tx = fundPsbtResponse.psbt.global.tx.copy(txOut = Seq(changeOutput1)))
+ )
})
}
- }).map(fundTxResponse => {
- require(fundTxResponse.tx.txOut.size == 1, "funded transaction should have a single change output")
+ }
+
+ for {
+ fundPsbtResponse <- bitcoinClient.fundPsbt(Seq(address -> dummyChangeAmount), 0, FundPsbtOptions(anchorFeerate, lockUtxos = true, changePosition = Some(1)))
+ psbt <- makeSingleOutput(fundPsbtResponse)
// NB: we insert the anchor input in the *first* position because our signing helpers only sign input #0.
- val unsignedTx = txInfo.copy(tx = fundTxResponse.tx.copy(txIn = txInfo.tx.txIn.head +: fundTxResponse.tx.txIn))
- adjustAnchorOutputChange(unsignedTx, commitTx, fundTxResponse.amountIn + AnchorOutputsCommitmentFormat.anchorAmount, commitFeerate, targetFeerate, dustLimit)
- })
+ unsignedTx = txInfo.copy(tx = psbt.global.tx.copy(txIn = txInfo.tx.txIn.head +: psbt.global.tx.txIn))
+ (adjustedTx, fee) = adjustAnchorOutputChange(unsignedTx, commitTx, fundPsbtResponse.amountIn + AnchorOutputsCommitmentFormat.anchorAmount, commitFeerate, targetFeerate, dustLimit)
+ // add a PSBT input for our input (i.e the one that spends our own anchor/htlc output and that we'll need to sign
+ psbt1 = Psbt(adjustedTx.tx)
+ // update our txinfo input
+ psbt2 = psbt1.updateWitnessInput(txInfo.input.outPoint, txInfo.input.txOut, witnessScript = Some(Script.parse(txInfo.input.redeemScript))).get
+ // update the inputs selected by bitcoin core
+ psbt3 = psbt.outputs.zipWithIndex.tail.foldLeft(psbt2) {
+ case (psbt, (output, index)) => psbt.updateWitnessOutput(index, output.witnessScript, output.redeemScript, output.derivationPaths).get
+ }
+ } yield {
+ (adjustedTx, fee, psbt3)
+ }
}
- private def addInputs(txInfo: HtlcTx, targetFeerate: FeeratePerKw, commitments: Commitments): Future[(HtlcTx, Satoshi)] = {
+ private def addInputs(txInfo: HtlcTx, targetFeerate: FeeratePerKw, commitments: Commitments): Future[(HtlcTx, Satoshi, Psbt)] = {
// NB: fundrawtransaction doesn't support non-wallet inputs, so we clear the input and re-add it later.
- val txNotFunded = txInfo.tx.copy(txIn = Nil, txOut = txInfo.tx.txOut.head.copy(amount = commitments.localParams.dustLimit) :: Nil)
val htlcTxWeight = txInfo match {
case _: HtlcSuccessTx => commitments.commitmentFormat.htlcSuccessWeight
case _: HtlcTimeoutTx => commitments.commitmentFormat.htlcTimeoutWeight
@@ -438,18 +464,24 @@ private class ReplaceableTxPublisher(nodeParams: NodeParams,
// NB: we don't take into account the fee paid by our HTLC input: we will take it into account when we adjust the
// change output amount (unless bitcoind didn't add any change output, in that case we will overpay the fee slightly).
val weightRatio = 1.0 + (htlcInputMaxWeight.toDouble / (htlcTxWeight + claimP2WPKHOutputWeight))
- bitcoinClient.fundTransaction(txNotFunded, FundTransactionOptions(targetFeerate * weightRatio, lockUtxos = true, changePosition = Some(1))).map(fundTxResponse => {
+ val address = publicKeyScriptToAddress(txInfo.tx.txOut.head.publicKeyScript, nodeParams.chainHash)
+ bitcoinClient.fundPsbt(Seq(address -> commitments.localParams.dustLimit), txInfo.tx.lockTime, FundPsbtOptions(targetFeerate * weightRatio, lockUtxos = true, changePosition = Some(1))).map(fundPsbtResponse => {
// We add the HTLC input (from the commit tx) and restore the HTLC output.
// NB: we can't modify them because they are signed by our peer (with SIGHASH_SINGLE | SIGHASH_ANYONECANPAY).
- val txWithHtlcInput = fundTxResponse.tx.copy(
- txIn = txInfo.tx.txIn ++ fundTxResponse.tx.txIn,
- txOut = txInfo.tx.txOut ++ fundTxResponse.tx.txOut.tail
+ val txWithHtlcInput = fundPsbtResponse.psbt.global.tx.copy(
+ txIn = txInfo.tx.txIn ++ fundPsbtResponse.psbt.global.tx.txIn,
+ txOut = txInfo.tx.txOut ++ fundPsbtResponse.psbt.global.tx.txOut.tail
)
val unsignedTx = txInfo match {
case htlcSuccess: HtlcSuccessTx => htlcSuccess.copy(tx = txWithHtlcInput)
case htlcTimeout: HtlcTimeoutTx => htlcTimeout.copy(tx = txWithHtlcInput)
}
- adjustHtlcTxChange(unsignedTx, fundTxResponse.amountIn + unsignedTx.input.txOut.amount, targetFeerate, commitments)
+ val (adjustedTx, fee) = adjustHtlcTxChange(unsignedTx, fundPsbtResponse.amountIn + unsignedTx.input.txOut.amount, targetFeerate, commitments)
+ var psbt1 = Psbt(adjustedTx.tx).updateWitnessInput(txInfo.input.outPoint, txInfo.input.txOut, witnessScript = Some(Script.parse(txInfo.input.redeemScript))).get
+ fundPsbtResponse.psbt.outputs.zipWithIndex.tail.foreach {
+ case (output, index) => psbt1 = psbt1.updateWitnessOutput(index, output.witnessScript, output.redeemScript, output.derivationPaths).get
+ }
+ (adjustedTx, fee, psbt1)
})
}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/package.scala b/eclair-core/src/main/scala/fr/acinq/eclair/package.scala
index 310ac0c1c8..6c7624190e 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/package.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/package.scala
@@ -95,6 +95,40 @@ package object eclair {
}
}
+ /**
+ *
+ * @param scriptPubKey public key script
+ * @param chainHash hash of the chain we're on
+ * @return the address the this public key script on this chain
+ */
+ def publicKeyScriptToAddress(scriptPubKey: Seq[ScriptElt], chainHash: ByteVector32): String = {
+ val base58PubkeyPrefix = chainHash match {
+ case Block.LivenetGenesisBlock.hash => Base58.Prefix.PubkeyAddress
+ case Block.TestnetGenesisBlock.hash | Block.RegtestGenesisBlock.hash => Base58.Prefix.PubkeyAddressTestnet
+ case _ => throw new IllegalArgumentException(s"invalid chain hash $chainHash")
+ }
+ val base58ScriptPrefix = chainHash match {
+ case Block.LivenetGenesisBlock.hash => Base58.Prefix.ScriptAddress
+ case Block.TestnetGenesisBlock.hash | Block.RegtestGenesisBlock.hash => Base58.Prefix.ScriptAddressTestnet
+ case _ => throw new IllegalArgumentException(s"invalid chain hash $chainHash")
+ }
+ val hrp = chainHash match {
+ case Block.LivenetGenesisBlock.hash => "bc"
+ case Block.TestnetGenesisBlock.hash => "tb"
+ case Block.RegtestGenesisBlock.hash => "bcrt"
+ case _ => throw new IllegalArgumentException(s"invalid chain hash $chainHash")
+ }
+ scriptPubKey match {
+ case OP_DUP :: OP_HASH160 :: OP_PUSHDATA(pubKeyHash, _) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil => Base58Check.encode(base58PubkeyPrefix, pubKeyHash)
+ case OP_HASH160 :: OP_PUSHDATA(scriptHash, _) :: OP_EQUAL :: Nil => Base58Check.encode(base58ScriptPrefix, scriptHash)
+ case OP_0 :: OP_PUSHDATA(pubKeyHash, _) :: Nil if pubKeyHash.length == 20 => Bech32.encodeWitnessAddress(hrp, 0, pubKeyHash)
+ case OP_0 :: OP_PUSHDATA(scriptHash, _) :: Nil if scriptHash.length == 32 => Bech32.encodeWitnessAddress(hrp, 0, scriptHash)
+ case _ => throw new IllegalArgumentException(s"invalid pubkey script $scriptPubKey")
+ }
+ }
+
+ def publicKeyScriptToAddress(scriptPubKey: ByteVector, chainHash: ByteVector32): String = publicKeyScriptToAddress(Script.parse(scriptPubKey), chainHash)
+
implicit class MilliSatoshiLong(private val n: Long) extends AnyVal {
def msat = MilliSatoshi(n)
}
@@ -103,7 +137,9 @@ package object eclair {
implicit object NumericMilliSatoshi extends Numeric[MilliSatoshi] {
// @formatter:off
override def plus(x: MilliSatoshi, y: MilliSatoshi): MilliSatoshi = x + y
+
override def minus(x: MilliSatoshi, y: MilliSatoshi): MilliSatoshi = x - y
+
override def times(x: MilliSatoshi, y: MilliSatoshi): MilliSatoshi = MilliSatoshi(x.toLong * y.toLong)
override def negate(x: MilliSatoshi): MilliSatoshi = -x
override def fromInt(x: Int): MilliSatoshi = MilliSatoshi(x)
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentRequest.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentRequest.scala
index 2b8dd2ac14..77c339783f 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentRequest.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentRequest.scala
@@ -579,6 +579,6 @@ object PaymentRequest {
val hrp = s"${pr.prefix}$hramount"
val data = Codecs.bolt11DataCodec.encode(Bolt11Data(pr.timestamp, pr.tags, pr.signature)).require
val int5s = eight2fiveCodec.decode(data).require.value
- Bech32.encode(hrp, int5s.toArray)
+ Bech32.encode(hrp, int5s.toArray, Bech32.Bech32Encoding)
}
}
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestBitcoinCoreClient.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestBitcoinCoreClient.scala
index 4dfa45567a..368fe678e1 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/TestBitcoinCoreClient.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestBitcoinCoreClient.scala
@@ -17,7 +17,7 @@
package fr.acinq.eclair
import akka.actor.ActorSystem
-import fr.acinq.bitcoin.{ByteVector32, Transaction}
+import fr.acinq.bitcoin.{Block, ByteVector32, Transaction}
import fr.acinq.eclair.blockchain._
import fr.acinq.eclair.blockchain.bitcoind.rpc.{BasicBitcoinJsonRPCClient, BitcoinCoreClient}
@@ -27,7 +27,7 @@ import scala.concurrent.{ExecutionContext, Future}
/**
* Created by PM on 26/04/2016.
*/
-class TestBitcoinCoreClient()(implicit system: ActorSystem) extends BitcoinCoreClient(new BasicBitcoinJsonRPCClient("", "", "", 0)(http = null)) {
+class TestBitcoinCoreClient()(implicit system: ActorSystem) extends BitcoinCoreClient(Block.RegtestGenesisBlock.hash, new BasicBitcoinJsonRPCClient("", "", "", 0)(http = null)) {
import scala.concurrent.ExecutionContext.Implicits.global
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/balance/CheckBalanceSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/balance/CheckBalanceSpec.scala
index 48e274dd08..5fb593190c 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/balance/CheckBalanceSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/balance/CheckBalanceSpec.scala
@@ -2,7 +2,7 @@ package fr.acinq.eclair.balance
import akka.pattern.pipe
import akka.testkit.TestProbe
-import fr.acinq.bitcoin.{ByteVector32, SatoshiLong}
+import fr.acinq.bitcoin.{Block, ByteVector32, SatoshiLong}
import fr.acinq.eclair.balance.CheckBalance.{ClosingBalance, MainAndHtlcBalance, OffChainBalance, PossiblyPublishedMainAndHtlcBalance, PossiblyPublishedMainBalance}
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.{apply => _, _}
import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient
@@ -230,7 +230,7 @@ class CheckBalanceSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
val txids = (for (_ <- 0 until 20) yield randomBytes32()).toList
val knownTxids = Set(txids(1), txids(3), txids(4), txids(6), txids(9), txids(12), txids(13))
- val bitcoinClient = new BitcoinCoreClient(null) {
+ val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, null) {
/** Get the number of confirmations of a given transaction. */
override def getTxConfirmations(txid: ByteVector32)(implicit ec: ExecutionContext): Future[Option[Int]] =
Future.successful(if (knownTxids.contains(txid)) Some(42) else None)
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 74f4cbef3f..d06c76bab2 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
@@ -17,12 +17,16 @@
package fr.acinq.eclair.blockchain
import fr.acinq.bitcoin.Crypto.PublicKey
-import fr.acinq.bitcoin.{ByteVector32, Crypto, OutPoint, Satoshi, SatoshiLong, Transaction, TxIn, TxOut}
+import fr.acinq.bitcoin.DeterministicWallet.ExtendedPublicKey
+import fr.acinq.bitcoin.{ByteVector32, Crypto, OutPoint, Psbt, Satoshi, SatoshiLong, Script, Transaction, TxIn, TxOut}
import fr.acinq.eclair.blockchain.OnChainWallet.{MakeFundingTxResponse, OnChainBalance}
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
+import fr.acinq.eclair.randomKey
+import fr.acinq.eclair.transactions.Scripts
import scodec.bits._
import scala.concurrent.{ExecutionContext, Future, Promise}
+import scala.util.Success
/**
* Created by PM on 06/07/2017.
@@ -39,8 +43,8 @@ class DummyOnChainWallet extends OnChainWallet {
override def getReceivePubkey(receiveAddress: Option[String] = None)(implicit ec: ExecutionContext): Future[Crypto.PublicKey] = Future.successful(dummyReceivePubkey)
- override def makeFundingTx(pubkeyScript: ByteVector, amount: Satoshi, feeRatePerKw: FeeratePerKw)(implicit ec: ExecutionContext): Future[MakeFundingTxResponse] =
- Future.successful(DummyOnChainWallet.makeDummyFundingTx(pubkeyScript, amount))
+ override def makeFundingTx(localFundingKey: ExtendedPublicKey, remoteFundingKey: PublicKey, amount: Satoshi, feeRatePerKw: FeeratePerKw)(implicit ec: ExecutionContext): Future[MakeFundingTxResponse] =
+ Future.successful(DummyOnChainWallet.makeDummyFundingTx(Script.write(Script.pay2wsh(Scripts.multiSig2of2(localFundingKey.publicKey, remoteFundingKey))), amount))
override def commit(tx: Transaction)(implicit ec: ExecutionContext): Future[Boolean] = Future.successful(true)
@@ -63,7 +67,7 @@ class NoOpOnChainWallet extends OnChainWallet {
override def getReceivePubkey(receiveAddress: Option[String] = None)(implicit ec: ExecutionContext): Future[Crypto.PublicKey] = Future.successful(dummyReceivePubkey)
- override def makeFundingTx(pubkeyScript: ByteVector, amount: Satoshi, feeRatePerKw: FeeratePerKw)(implicit ec: ExecutionContext): Future[MakeFundingTxResponse] = Promise[MakeFundingTxResponse]().future // will never be completed
+ override def makeFundingTx(localFundingKey: ExtendedPublicKey, remoteFundingKey: PublicKey, amount: Satoshi, feeRatePerKw: FeeratePerKw)(implicit ec: ExecutionContext): Future[MakeFundingTxResponse] = Promise[MakeFundingTxResponse]().future // will never be completed
override def commit(tx: Transaction)(implicit ec: ExecutionContext): Future[Boolean] = Future.successful(true)
@@ -79,11 +83,17 @@ object DummyOnChainWallet {
val dummyReceivePubkey: PublicKey = PublicKey(hex"028feba10d0eafd0fad8fe20e6d9206e6bd30242826de05c63f459a00aced24b12")
def makeDummyFundingTx(pubkeyScript: ByteVector, amount: Satoshi): MakeFundingTxResponse = {
+ val key = randomKey()
+ val baseTx = Transaction(version = 2, txIn = Nil, txOut = TxOut(amount, Script.pay2wpkh(key.publicKey)) :: Nil, lockTime = 0)
val fundingTx = Transaction(version = 2,
- txIn = TxIn(OutPoint(ByteVector32(ByteVector.fill(32)(1)), 42), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) :: Nil,
+ txIn = TxIn(OutPoint(baseTx, 0), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) :: Nil,
txOut = TxOut(amount, pubkeyScript) :: Nil,
lockTime = 0)
- MakeFundingTxResponse(fundingTx, 0, 420 sat)
+ val Success(psbt) = Psbt(fundingTx)
+ .updateWitnessInputTx(baseTx, 0, witnessScript = Some(Script.pay2pkh(key.publicKey)))
+ .flatMap(p => p.sign(key, 0))
+ .flatMap(p => p.psbt.finalizeWitnessInput(0, Script.witnessPay2wpkh(key.publicKey, p.sig)))
+ MakeFundingTxResponse(psbt, 0, 420 sat)
}
}
\ No newline at end of file
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 0378795272..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
@@ -19,16 +19,17 @@ package fr.acinq.eclair.blockchain.bitcoind
import akka.actor.Status.Failure
import akka.pattern.pipe
import akka.testkit.TestProbe
-import fr.acinq.bitcoin.Crypto.PublicKey
+import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
+import fr.acinq.bitcoin.DeterministicWallet.{ExtendedPublicKey, KeyPath}
import fr.acinq.bitcoin.SigVersion.SIGVERSION_WITNESS_V0
-import fr.acinq.bitcoin.{Block, BtcDouble, ByteVector32, MilliBtcDouble, OutPoint, SIGHASH_ALL, Satoshi, SatoshiLong, Script, ScriptFlags, ScriptWitness, Transaction, TxIn, TxOut}
+import fr.acinq.bitcoin.{Bech32, Block, BtcDouble, ByteVector32, Crypto, MilliBtcDouble, OutPoint, SIGHASH_ALL, Satoshi, SatoshiLong, Script, ScriptFlags, ScriptWitness, Transaction, TxIn, TxOut}
import fr.acinq.eclair.blockchain.OnChainWallet.{MakeFundingTxResponse, OnChainBalance}
import fr.acinq.eclair.blockchain.bitcoind.BitcoindService.BitcoinReq
import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient._
import fr.acinq.eclair.blockchain.bitcoind.rpc.{BasicBitcoinJsonRPCClient, BitcoinCoreClient, JsonRPCError}
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
import fr.acinq.eclair.transactions.{Scripts, Transactions}
-import fr.acinq.eclair.{TestConstants, TestKitBaseClass, addressToPublicKeyScript, randomKey}
+import fr.acinq.eclair.{TestConstants, TestKitBaseClass, addressToPublicKeyScript, randomBytes32, randomKey}
import grizzled.slf4j.Logging
import org.json4s.JsonAST._
import org.json4s.{DefaultFormats, Formats}
@@ -56,24 +57,67 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A
test("encrypt wallet") {
val sender = TestProbe()
- val bitcoinClient = new BitcoinCoreClient(bitcoinrpcclient)
+ val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, bitcoinrpcclient)
val walletPassword = Random.alphanumeric.take(8).mkString
sender.send(bitcoincli, BitcoinReq("encryptwallet", walletPassword))
sender.expectMsgType[JString]
restartBitcoind(sender)
- val pubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(randomKey().publicKey, randomKey().publicKey)))
- bitcoinClient.makeFundingTx(pubkeyScript, 50 millibtc, FeeratePerKw(10000 sat)).pipeTo(sender.ref)
+ 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]
}
+ test("fund and sign psbt") {
+ val sender = TestProbe()
+ val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, bitcoinrpcclient)
+ val priv1 = PrivateKey(ByteVector32.fromValidHex("01" * 32))
+ val priv2 = PrivateKey(ByteVector32.fromValidHex("02" * 32))
+ val script = Script.createMultiSigMofN(2, Seq(priv1.publicKey, priv2.publicKey))
+ val address = Bech32.encodeWitnessAddress("bcrt", 0, Crypto.sha256(Script.write(script)))
+
+ bitcoinClient.fundPsbt(Seq(address -> 10000.sat), 0, FundPsbtOptions(TestConstants.feeratePerKw)).pipeTo(sender.ref)
+ val FundPsbtResponse(psbt, _, _) = sender.expectMsgType[FundPsbtResponse]
+
+ bitcoinClient.processPsbt(psbt).pipeTo(sender.ref)
+ val ProcessPsbtResponse(psbt1, true) = sender.expectMsgType[ProcessPsbtResponse]
+ assert(psbt1.extract().isSuccess)
+ }
+
+ test("fund psbt (invalid requests)") {
+ val sender = TestProbe()
+ val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, bitcoinrpcclient)
+ val priv = PrivateKey(ByteVector32.fromValidHex("01" * 32))
+ val address = Bech32.encodeWitnessAddress("bcrt", 0, priv.publicKey.hash160)
+
+ {
+ // check that it does work
+ bitcoinClient.fundPsbt(Seq(address -> 10000.sat), 0, FundPsbtOptions(TestConstants.feeratePerKw)).pipeTo(sender.ref)
+ sender.expectMsgType[FundPsbtResponse]
+ }
+ {
+ // invalid address
+ bitcoinClient.fundPsbt(Seq("invalid address" -> 10000.sat), 0, FundPsbtOptions(TestConstants.feeratePerKw)).pipeTo(sender.ref)
+ sender.expectMsgType[akka.actor.Status.Failure]
+ }
+ {
+ // amount is too small
+ bitcoinClient.fundPsbt(Seq(address -> 100.sat), 0, FundPsbtOptions(TestConstants.feeratePerKw)).pipeTo(sender.ref)
+ sender.expectMsgType[akka.actor.Status.Failure]
+ }
+ {
+ // amount is too large
+ bitcoinClient.fundPsbt(Seq(address -> 11_000_000.btc), 0, FundPsbtOptions(TestConstants.feeratePerKw)).pipeTo(sender.ref)
+ sender.expectMsgType[akka.actor.Status.Failure]
+ }
+ }
+
test("fund transactions") {
val sender = TestProbe()
- val bitcoinClient = new BitcoinCoreClient(bitcoinrpcclient)
+ val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, bitcoinrpcclient)
val txToRemote = {
val txNotFunded = Transaction(2, Nil, TxOut(150000 sat, Script.pay2wpkh(randomKey().publicKey)) :: Nil, 0)
@@ -145,7 +189,7 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A
}
val sender = TestProbe()
- val bitcoinClient = new BitcoinCoreClient(rpcClient)
+ val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, rpcClient)
bitcoinClient.onChainBalance().pipeTo(sender.ref)
assert(sender.expectMsgType[OnChainBalance] === OnChainBalance(Satoshi(satoshi), Satoshi(satoshi)))
@@ -157,7 +201,7 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A
test("create/commit/rollback funding txs") {
val sender = TestProbe()
- val bitcoinClient = new BitcoinCoreClient(bitcoinrpcclient)
+ val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, bitcoinrpcclient)
bitcoinClient.onChainBalance().pipeTo(sender.ref)
assert(sender.expectMsgType[OnChainBalance].confirmed > 0.sat)
@@ -168,16 +212,16 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A
val fundingTxs = for (_ <- 0 to 3) yield {
val pubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(randomKey().publicKey, randomKey().publicKey)))
- bitcoinClient.makeFundingTx(pubkeyScript, Satoshi(500), FeeratePerKw(250 sat)).pipeTo(sender.ref)
- val fundingTx = sender.expectMsgType[MakeFundingTxResponse].fundingTx
+ bitcoinClient.makeFundingTx(ExtendedPublicKey(randomKey().publicKey.value, randomBytes32(), 4, KeyPath("m/1/2/3/4"), 0), randomKey().publicKey, Satoshi(500), FeeratePerKw(250 sat)).pipeTo(sender.ref)
+ val fundingTx = sender.expectMsgType[MakeFundingTxResponse].psbt.extract().get
bitcoinClient.publishTransaction(fundingTx.copy(txIn = Nil)).pipeTo(sender.ref) // try publishing an invalid version of the tx
sender.expectMsgType[Failure]
bitcoinClient.rollback(fundingTx).pipeTo(sender.ref) // rollback the locked outputs
assert(sender.expectMsgType[Boolean])
// now fund a tx with correct feerate
- bitcoinClient.makeFundingTx(pubkeyScript, 50 millibtc, FeeratePerKw(250 sat)).pipeTo(sender.ref)
- sender.expectMsgType[MakeFundingTxResponse].fundingTx
+ bitcoinClient.makeFundingTx(ExtendedPublicKey(randomKey().publicKey.value, randomBytes32(), 4, KeyPath("m/1/2/3/4"), 0), randomKey().publicKey, 50 millibtc, FeeratePerKw(250 sat)).pipeTo(sender.ref)
+ sender.expectMsgType[MakeFundingTxResponse].psbt.extract().get
}
assert(getLocks(sender).size === 4)
@@ -206,12 +250,13 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A
test("ensure feerate is always above min-relay-fee") {
val sender = TestProbe()
- val bitcoinClient = new BitcoinCoreClient(bitcoinrpcclient)
+ val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, bitcoinrpcclient)
val pubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(randomKey().publicKey, randomKey().publicKey)))
// 200 sat/kw is below the min-relay-fee
- bitcoinClient.makeFundingTx(pubkeyScript, 5 millibtc, FeeratePerKw(200 sat)).pipeTo(sender.ref)
- val MakeFundingTxResponse(fundingTx, _, _) = sender.expectMsgType[MakeFundingTxResponse]
+ bitcoinClient.makeFundingTx(ExtendedPublicKey(randomKey().publicKey.value, randomBytes32(), 4, KeyPath("m/1/2/3/4"), 0), randomKey().publicKey, 5 millibtc, FeeratePerKw(200 sat)).pipeTo(sender.ref)
+ val MakeFundingTxResponse(psbt, _, _) = sender.expectMsgType[MakeFundingTxResponse]
+ val fundingTx = psbt.extract().get
bitcoinClient.commit(fundingTx).pipeTo(sender.ref)
sender.expectMsg(true)
@@ -219,7 +264,7 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A
test("unlock failed funding txs") {
val sender = TestProbe()
- val bitcoinClient = new BitcoinCoreClient(bitcoinrpcclient)
+ val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, bitcoinrpcclient)
bitcoinClient.onChainBalance().pipeTo(sender.ref)
assert(sender.expectMsgType[OnChainBalance].confirmed > 0.sat)
@@ -230,10 +275,9 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A
assert(getLocks(sender).isEmpty)
- val pubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(randomKey().publicKey, randomKey().publicKey)))
- bitcoinClient.makeFundingTx(pubkeyScript, 50 millibtc, FeeratePerKw(10000 sat)).pipeTo(sender.ref)
- val MakeFundingTxResponse(fundingTx, _, _) = sender.expectMsgType[MakeFundingTxResponse]
-
+ 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 MakeFundingTxResponse(psbt, _, _) = sender.expectMsgType[MakeFundingTxResponse]
+ val fundingTx = psbt.extract().get
bitcoinClient.commit(fundingTx).pipeTo(sender.ref)
sender.expectMsg(true)
@@ -243,7 +287,7 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A
test("unlock utxos when transaction is published") {
val sender = TestProbe()
- val bitcoinClient = new BitcoinCoreClient(bitcoinrpcclient)
+ val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, bitcoinrpcclient)
generateBlocks(1) // generate a block to ensure we start with an empty mempool
// create a first transaction with multiple inputs
@@ -299,12 +343,12 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A
test("unlock transaction inputs if publishing fails") {
val sender = TestProbe()
- val pubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(randomKey().publicKey, randomKey().publicKey)))
- val bitcoinClient = new BitcoinCoreClient(bitcoinrpcclient)
+ val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, bitcoinrpcclient)
// create a huge tx so we make sure it has > 1 inputs
- bitcoinClient.makeFundingTx(pubkeyScript, 250 btc, FeeratePerKw(1000 sat)).pipeTo(sender.ref)
- val MakeFundingTxResponse(fundingTx, outputIndex, _) = sender.expectMsgType[MakeFundingTxResponse]
+ bitcoinClient.makeFundingTx(ExtendedPublicKey(randomKey().publicKey.value, randomBytes32(), 4, KeyPath("m/1/2/3/4"), 0), randomKey().publicKey, 250 btc, FeeratePerKw(1000 sat)).pipeTo(sender.ref)
+ val MakeFundingTxResponse(psbt, outputIndex, _) = sender.expectMsgType[MakeFundingTxResponse]
+ val fundingTx = psbt.extract().get
// spend the first 2 inputs
val tx1 = fundingTx.copy(
@@ -338,14 +382,14 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A
test("unlock outpoints correctly") {
val sender = TestProbe()
- val pubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(randomKey().publicKey, randomKey().publicKey)))
- val bitcoinClient = new BitcoinCoreClient(bitcoinrpcclient)
+ val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, bitcoinrpcclient)
{
// test #1: unlock outpoints that are actually locked
// create a huge tx so we make sure it has > 1 inputs
- bitcoinClient.makeFundingTx(pubkeyScript, 250 btc, FeeratePerKw(1000 sat)).pipeTo(sender.ref)
- val MakeFundingTxResponse(fundingTx, _, _) = sender.expectMsgType[MakeFundingTxResponse]
+ bitcoinClient.makeFundingTx(ExtendedPublicKey(randomKey().publicKey.value, randomBytes32(), 4, KeyPath("m/1/2/3/4"), 0), randomKey().publicKey, 250 btc, FeeratePerKw(1000 sat)).pipeTo(sender.ref)
+ val MakeFundingTxResponse(psbt, _, _) = sender.expectMsgType[MakeFundingTxResponse]
+ val fundingTx = psbt.extract().get
assert(fundingTx.txIn.size > 2)
assert(getLocks(sender) == fundingTx.txIn.map(_.outPoint).toSet)
bitcoinClient.rollback(fundingTx).pipeTo(sender.ref)
@@ -353,8 +397,9 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A
}
{
// test #2: some outpoints are locked, some are unlocked
- bitcoinClient.makeFundingTx(pubkeyScript, 250 btc, FeeratePerKw(1000 sat)).pipeTo(sender.ref)
- val MakeFundingTxResponse(fundingTx, _, _) = sender.expectMsgType[MakeFundingTxResponse]
+ bitcoinClient.makeFundingTx(ExtendedPublicKey(randomKey().publicKey.value, randomBytes32(), 4, KeyPath("m/1/2/3/4"), 0), randomKey().publicKey, 250 btc, FeeratePerKw(1000 sat)).pipeTo(sender.ref)
+ val MakeFundingTxResponse(psbt, _, _) = sender.expectMsgType[MakeFundingTxResponse]
+ val fundingTx = psbt.extract().get
assert(fundingTx.txIn.size > 2)
assert(getLocks(sender) == fundingTx.txIn.map(_.outPoint).toSet)
@@ -373,7 +418,7 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A
test("sign transactions") {
val sender = TestProbe()
- val bitcoinClient = new BitcoinCoreClient(bitcoinrpcclient)
+ val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, bitcoinrpcclient)
val nonWalletKey = randomKey()
val opts = FundTransactionOptions(TestConstants.feeratePerKw, changePosition = Some(1))
@@ -451,7 +496,7 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A
test("publish transaction idempotent") {
val sender = TestProbe()
- val bitcoinClient = new BitcoinCoreClient(bitcoinrpcclient)
+ val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, bitcoinrpcclient)
val priv = dumpPrivateKey(getNewAddress(sender), sender)
val noInputTx = Transaction(2, Nil, TxOut(6.btc.toSatoshi, Script.pay2wpkh(priv.publicKey)) :: Nil, 0)
@@ -497,7 +542,7 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A
test("publish invalid transactions") {
val sender = TestProbe()
- val bitcoinClient = new BitcoinCoreClient(bitcoinrpcclient)
+ val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, bitcoinrpcclient)
// that tx has inputs that don't exist
val txWithUnknownInputs = Transaction.read("02000000000101b9e2a3f518fd74e696d258fed3c78c43f84504e76c99212e01cf225083619acf00000000000d0199800136b34b00000000001600145464ce1e5967773922506e285780339d72423244040047304402206795df1fd93c285d9028c384aacf28b43679f1c3f40215fd7bd1abbfb816ee5a022047a25b8c128e692d4717b6dd7b805aa24ecbbd20cfd664ab37a5096577d4a15d014730440220770f44121ed0e71ec4b482dded976f2febd7500dfd084108e07f3ce1e85ec7f5022025b32dc0d551c47136ce41bfb80f5a10de95c0babb22a3ae2d38e6688b32fcb20147522102c2662ab3e4fa18a141d3be3317c6ee134aff10e6cd0a91282a25bf75c0481ebc2102e952dd98d79aa796289fa438e4fdeb06ed8589ff2a0f032b0cfcb4d7b564bc3252aea58d1120")
@@ -533,7 +578,7 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A
test("send and list transactions") {
val sender = TestProbe()
- val bitcoinClient = new BitcoinCoreClient(bitcoinrpcclient)
+ val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, bitcoinrpcclient)
bitcoinClient.onChainBalance().pipeTo(sender.ref)
val initialBalance = sender.expectMsgType[OnChainBalance]
@@ -568,7 +613,7 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A
test("get mempool transaction") {
val sender = TestProbe()
val address = getNewAddress(sender)
- val bitcoinClient = new BitcoinCoreClient(bitcoinrpcclient)
+ val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, bitcoinrpcclient)
def spendWalletTx(tx: Transaction, fees: Satoshi): Transaction = {
val inputs = tx.txOut.indices.map(vout => Map("txid" -> tx.txid, "vout" -> vout))
@@ -612,7 +657,7 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A
test("abandon transaction") {
val sender = TestProbe()
- val bitcoinClient = new BitcoinCoreClient(bitcoinrpcclient)
+ val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, bitcoinrpcclient)
// Broadcast a wallet transaction.
val opts = FundTransactionOptions(TestConstants.feeratePerKw, changePosition = Some(1))
@@ -650,7 +695,7 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A
test("detect if tx has been double-spent") {
val sender = TestProbe()
- val bitcoinClient = new BitcoinCoreClient(bitcoinrpcclient)
+ val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, bitcoinrpcclient)
// first let's create a tx
val address = "n2YKngjUp139nkjKvZGnfLRN6HzzYxJsje"
@@ -689,7 +734,7 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A
test("find spending transaction of a given output") {
val sender = TestProbe()
- val bitcoinClient = new BitcoinCoreClient(bitcoinrpcclient)
+ val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, bitcoinrpcclient)
bitcoinClient.getBlockCount.pipeTo(sender.ref)
val blockCount = sender.expectMsgType[Long]
@@ -732,7 +777,7 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A
test("compute pubkey from a receive address") {
val sender = TestProbe()
- val bitcoinClient = new BitcoinCoreClient(bitcoinrpcclient)
+ val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, bitcoinrpcclient)
bitcoinClient.getReceiveAddress().pipeTo(sender.ref)
val address = sender.expectMsgType[String]
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/PsbtSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/PsbtSpec.scala
new file mode 100644
index 0000000000..99abd595f0
--- /dev/null
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/PsbtSpec.scala
@@ -0,0 +1,75 @@
+package fr.acinq.eclair.blockchain.bitcoind
+
+import akka.testkit.TestProbe
+import akka.pattern.pipe
+import fr.acinq.bitcoin.{Block, ByteVector32, OutPoint, Psbt, SatoshiLong, Script, ScriptWitness, Transaction, TxIn, TxOut, computeP2WpkhAddress}
+import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
+import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient
+import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient.{FundPsbtOptions, FundPsbtResponse, ProcessPsbtResponse}
+import fr.acinq.eclair.{TestConstants, TestKitBaseClass}
+import grizzled.slf4j.Logging
+import org.json4s.{DefaultFormats, Formats}
+import org.scalatest.BeforeAndAfterAll
+import org.scalatest.funsuite.AnyFunSuiteLike
+
+import scala.concurrent.ExecutionContext.Implicits.global
+import scala.util.Success
+
+class PsbtSpec extends TestKitBaseClass with BitcoindService with AnyFunSuiteLike with BeforeAndAfterAll with Logging {
+
+ implicit val formats: Formats = DefaultFormats
+ lazy val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, bitcoinrpcclient)
+
+ override def beforeAll(): Unit = {
+ startBitcoind()
+ waitForBitcoindReady()
+ }
+
+ override def afterAll(): Unit = {
+ stopBitcoind()
+ }
+
+ def createOurTx(pub: PublicKey) : (Transaction, Int) = {
+ val sender = TestProbe()
+ bitcoinClient.fundPsbt(Seq(computeP2WpkhAddress(pub, Block.RegtestGenesisBlock.hash) -> 100000.sat), 0, FundPsbtOptions(TestConstants.feeratePerKw, lockUtxos = false)).pipeTo(sender.ref)
+ val FundPsbtResponse(psbt, _, Some(changepos)) = sender.expectMsgType[FundPsbtResponse]
+ bitcoinClient.processPsbt(psbt, sign = true).pipeTo(sender.ref)
+ val ProcessPsbtResponse(psbt1, true) = sender.expectMsgType[ProcessPsbtResponse]
+ val Success(tx) = psbt1.extract()
+ bitcoinClient.publishTransaction(tx).pipeTo(sender.ref)
+ val publishedId = sender.expectMsgType[ByteVector32]
+ assert(publishedId == tx.txid)
+ val index = tx.txOut.zipWithIndex.find(_._1.publicKeyScript == Script.write(Script.pay2wpkh(pub))).get._2
+ (tx, index)
+ }
+
+ test("add inputs") {
+ val sender = TestProbe()
+
+ // create a utxo that sends to a non-wallet key
+ val priv = PrivateKey(ByteVector32.fromValidHex("01" * 32))
+ val (ourTx, index) = createOurTx(priv.publicKey)
+
+ // fund a psbt without inputs
+ bitcoinClient.fundPsbt(Seq(computeP2WpkhAddress(priv.publicKey, Block.RegtestGenesisBlock.hash) -> 100000.sat), 0, FundPsbtOptions(TestConstants.feeratePerKw, lockUtxos = false)).pipeTo(sender.ref)
+ val FundPsbtResponse(psbt, _, _) = sender.expectMsgType[FundPsbtResponse]
+
+ val fakeTx = Transaction(version = 2, txIn = TxIn(OutPoint(ourTx, index), Nil, 0) :: Nil, txOut = Nil, lockTime = 0)
+ val fakePsbt = Psbt(fakeTx)
+ val joined = Psbt.join(psbt, fakePsbt).get
+ val psbt1 = joined.updateWitnessInput(OutPoint(ourTx, index), ourTx.txOut(index), witnessScript = Some(Script.pay2pkh(priv.publicKey))).get
+ val txWithAdditionalInput = psbt.global.tx.addInput(TxIn(OutPoint(ourTx, index), Nil, 0))
+
+ // ask bitcoin core to sign its inputs
+ bitcoinClient.processPsbt(psbt1).pipeTo(sender.ref)
+ val ProcessPsbtResponse(psbt2, complete) = sender.expectMsgType[ProcessPsbtResponse] //(max = 10 minutes)
+
+ // sign our inputs
+ val Success(psbt3) = psbt2.sign(priv, 1)
+ val sig = psbt3.sig
+
+ // we now have a finalized PSBT
+ val Success(psbt4) = psbt3.psbt.finalizeWitnessInput(1, ScriptWitness(sig :: priv.publicKey.value :: Nil))
+ assert(psbt4.extract().isSuccess)
+ }
+}
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcherSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcherSpec.scala
index cd2dfb1cd5..ccc079b3d0 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcherSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcherSpec.scala
@@ -21,11 +21,11 @@ import akka.actor.typed.scaladsl.adapter.{ClassicActorSystemOps, TypedActorRefOp
import akka.actor.{ActorRef, Props, typed}
import akka.pattern.pipe
import akka.testkit.TestProbe
-import fr.acinq.bitcoin.{Block, Btc, MilliBtcDouble, OutPoint, SatoshiLong, Script, Transaction, TxOut}
+import fr.acinq.bitcoin.{Bech32, Block, Btc, MilliBtcDouble, OutPoint, SIGHASH_ALL, SatoshiLong, Script, Transaction, TxOut}
import fr.acinq.eclair.blockchain.WatcherSpec._
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._
import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient
-import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient.{FundTransactionOptions, FundTransactionResponse, SignTransactionResponse}
+import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient.{FundPsbtOptions, FundPsbtResponse, FundTransactionOptions, FundTransactionResponse, ProcessPsbtResponse, SignTransactionResponse}
import fr.acinq.eclair.blockchain.bitcoind.zmq.ZMQActor
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
import fr.acinq.eclair.blockchain.{CurrentBlockCount, NewTransaction}
@@ -74,7 +74,7 @@ class ZmqWatcherSpec extends TestKitBaseClass with AnyFunSuiteLike with Bitcoind
val probe = TestProbe()
val listener = TestProbe()
system.eventStream.subscribe(listener.ref, classOf[CurrentBlockCount])
- val bitcoinClient = new BitcoinCoreClient(bitcoinrpcclient)
+ val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, bitcoinrpcclient)
val nodeParams = TestConstants.Alice.nodeParams.copy(chainHash = Block.RegtestGenesisBlock.hash)
val watcher = system.spawn(ZmqWatcher(nodeParams, blockCount, bitcoinClient), UUID.randomUUID().toString)
try {
@@ -282,10 +282,10 @@ class ZmqWatcherSpec extends TestKitBaseClass with AnyFunSuiteLike with Bitcoind
// create a chain of transactions that we don't broadcast yet
val priv = dumpPrivateKey(getNewAddress(probe), probe)
val tx1 = {
- bitcoinClient.fundTransaction(Transaction(2, Nil, TxOut(150000 sat, Script.pay2wpkh(priv.publicKey)) :: Nil, 0), FundTransactionOptions(FeeratePerKw(250 sat), lockUtxos = true)).pipeTo(probe.ref)
- val funded = probe.expectMsgType[FundTransactionResponse].tx
- bitcoinClient.signTransaction(funded).pipeTo(probe.ref)
- probe.expectMsgType[SignTransactionResponse].tx
+ bitcoinClient.fundPsbt(Seq(Bech32.encodeWitnessAddress("bcrt", 0, priv.publicKey.hash160) -> 150000.sat), locktime = 0, FundPsbtOptions(FeeratePerKw(250 sat), lockUtxos = true)).pipeTo(probe.ref)
+ val funded = probe.expectMsgType[FundPsbtResponse].psbt
+ bitcoinClient.processPsbt(funded, sign = true, sighashType = SIGHASH_ALL).pipeTo(probe.ref)
+ probe.expectMsgType[ProcessPsbtResponse].psbt.extract().get
}
val outputIndex = tx1.txOut.indexWhere(_.publicKeyScript == Script.write(Script.pay2wpkh(priv.publicKey)))
val tx2 = createSpendP2WPKH(tx1, priv, priv.publicKey, 10000 sat, 1, 0)
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/MempoolTxMonitorSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/MempoolTxMonitorSpec.scala
index 85e8e9e6e3..7c2715bfcd 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/MempoolTxMonitorSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/MempoolTxMonitorSpec.scala
@@ -21,7 +21,7 @@ import akka.actor.typed.scaladsl.adapter.{ClassicActorSystemOps, TypedActorRefOp
import akka.pattern.pipe
import akka.testkit.TestProbe
import fr.acinq.bitcoin.Crypto.PrivateKey
-import fr.acinq.bitcoin.{ByteVector32, OutPoint, SatoshiLong, Transaction, TxIn}
+import fr.acinq.bitcoin.{Block, ByteVector32, OutPoint, SatoshiLong, Transaction, TxIn}
import fr.acinq.eclair.blockchain.CurrentBlockCount
import fr.acinq.eclair.blockchain.WatcherSpec.{createSpendManyP2WPKH, createSpendP2WPKH}
import fr.acinq.eclair.blockchain.bitcoind.BitcoindService
@@ -53,7 +53,7 @@ class MempoolTxMonitorSpec extends TestKitBaseClass with AnyFunSuiteLike with Bi
def createFixture(): Fixture = {
val probe = TestProbe()
- val bitcoinClient = new BitcoinCoreClient(bitcoinrpcclient)
+ val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, bitcoinrpcclient)
val monitor = system.spawnAnonymous(MempoolTxMonitor(TestConstants.Alice.nodeParams, bitcoinClient, TxPublishLogContext(UUID.randomUUID(), randomKey().publicKey, None)))
val address = getNewAddress(probe)
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/RawTxPublisherSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/RawTxPublisherSpec.scala
index 272e0fc262..c2e52ea96c 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/RawTxPublisherSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/RawTxPublisherSpec.scala
@@ -20,7 +20,7 @@ import akka.actor.typed.ActorRef
import akka.actor.typed.scaladsl.adapter.{ClassicActorSystemOps, TypedActorRefOps, actorRefAdapter}
import akka.pattern.pipe
import akka.testkit.TestProbe
-import fr.acinq.bitcoin.{ByteVector32, SatoshiLong, Transaction}
+import fr.acinq.bitcoin.{Block, ByteVector32, SatoshiLong, Transaction}
import fr.acinq.eclair.blockchain.CurrentBlockCount
import fr.acinq.eclair.blockchain.WatcherSpec.createSpendP2WPKH
import fr.acinq.eclair.blockchain.bitcoind.BitcoindService
@@ -53,7 +53,7 @@ class RawTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike with Bitc
def createFixture(): Fixture = {
val probe = TestProbe()
val watcher = TestProbe()
- val bitcoinClient = new BitcoinCoreClient(bitcoinrpcclient)
+ val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, bitcoinrpcclient)
val publisher = system.spawnAnonymous(RawTxPublisher(TestConstants.Alice.nodeParams, bitcoinClient, watcher.ref, TxPublishLogContext(UUID.randomUUID(), randomKey().publicKey, None)))
Fixture(bitcoinClient, publisher, watcher, probe)
}
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 202ccc78a3..bf61a2dee9 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
@@ -20,7 +20,7 @@ import akka.actor.typed.ActorRef
import akka.actor.typed.scaladsl.adapter.{ClassicActorSystemOps, actorRefAdapter}
import akka.pattern.pipe
import akka.testkit.{TestFSMRef, TestProbe}
-import fr.acinq.bitcoin.{BtcAmount, ByteVector32, MilliBtcDouble, OutPoint, SatoshiLong, Script, ScriptWitness, Transaction, TxIn, TxOut}
+import fr.acinq.bitcoin.{Block, BtcAmount, ByteVector32, MilliBtcDouble, OutPoint, SatoshiLong, Script, ScriptWitness, Transaction, TxIn, TxOut}
import fr.acinq.eclair.blockchain.CurrentBlockCount
import fr.acinq.eclair.blockchain.bitcoind.BitcoindService
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.{WatchOutputSpent, WatchParentTxConfirmed, WatchParentTxConfirmedTriggered, WatchTxConfirmed}
@@ -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(walletRpcClient)
+ 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/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala
index db780879da..f9460c7d15 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
@@ -1089,7 +1089,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.commitTxAndRemoteSig.commitTx.tx
// actual test begins
- bob ! CommitSig(ByteVector32.Zeroes, ByteVector64.Zeroes, Nil)
+ bob ! CommitSig(ByteVector32.Zeroes, ByteVector64.fromValidHex("01" * 64), Nil)
val error = bob2alice.expectMsgType[Error]
assert(new String(error.data.toArray).startsWith("invalid commitment signature"))
awaitCond(bob.stateName == CLOSING)
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 337fef447c..5021caa1c7 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
@@ -255,7 +255,7 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec {
sender.send(nodes("C").register, Register.Forward(sender.ref, htlc.channelId, CMD_GETSTATEDATA(ActorRef.noSender)))
val Some(localCommit) = sender.expectMsgType[RES_GETSTATEDATA[DATA_CLOSING]].data.localCommitPublished
// we wait until the commit tx has been broadcast
- val bitcoinClient = new BitcoinCoreClient(bitcoinrpcclient)
+ val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, bitcoinrpcclient)
waitForTxBroadcastOrConfirmed(localCommit.commitTx.txid, bitcoinClient, sender)
// we generate a few blocks to get the commit tx confirmed
generateBlocks(3, Some(minerAddress))
@@ -311,7 +311,7 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec {
// we generate enough blocks to make the htlc timeout
generateBlocks((htlc.cltvExpiry.toLong - getBlockCount).toInt, Some(minerAddress))
// we wait until the claim-htlc-timeout has been broadcast
- val bitcoinClient = new BitcoinCoreClient(bitcoinrpcclient)
+ val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, bitcoinrpcclient)
assert(remoteCommit.claimHtlcTxs.size === 1)
waitForOutputSpent(remoteCommit.claimHtlcTxs.keys.head, bitcoinClient, sender)
// and we generate blocks for the claim-htlc-timeout to reach enough confirmations
@@ -570,7 +570,7 @@ class StandardChannelIntegrationSpec extends ChannelIntegrationSpec {
// we then wait for C and F to negotiate the closing fee
awaitCond(stateListener.expectMsgType[ChannelStateChanged](max = 60 seconds).currentState == CLOSING, max = 60 seconds)
// and close the channel
- val bitcoinClient = new BitcoinCoreClient(bitcoinrpcclient)
+ val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, bitcoinrpcclient)
awaitCond({
bitcoinClient.getMempool().pipeTo(sender.ref)
sender.expectMsgType[Seq[Transaction]].exists(_.txIn.head.outPoint.txid === fundingOutpoint.txid)
@@ -605,7 +605,7 @@ class StandardChannelIntegrationSpec extends ChannelIntegrationSpec {
val revokedCommitFixture = testRevokedCommit(Transactions.DefaultCommitmentFormat)
import revokedCommitFixture._
- val bitcoinClient = new BitcoinCoreClient(bitcoinrpcclient)
+ val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, bitcoinrpcclient)
// we retrieve transactions already received so that we don't take them into account when evaluating the outcome of this test
val previouslyReceivedByC = listReceivedByAddress(finalAddressC, sender)
// F publishes the revoked commitment, one HTLC-success, one HTLC-timeout and leaves the other HTLC outputs unclaimed
@@ -725,7 +725,7 @@ abstract class AnchorChannelIntegrationSpec extends ChannelIntegrationSpec {
// we then wait for C to detect the unilateral close and go to CLOSING state
awaitCond(stateListener.expectMsgType[ChannelStateChanged](max = 60 seconds).currentState == CLOSING, max = 60 seconds)
- val bitcoinClient = new BitcoinCoreClient(bitcoinrpcclient)
+ val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, bitcoinrpcclient)
awaitCond({
bitcoinClient.getTransaction(commitTx.txid).map(tx => Some(tx)).recover(_ => None).pipeTo(sender.ref)
val tx = sender.expectMsgType[Option[Transaction]]
@@ -751,7 +751,7 @@ abstract class AnchorChannelIntegrationSpec extends ChannelIntegrationSpec {
val revokedCommitFixture = testRevokedCommit(commitmentFormat)
import revokedCommitFixture._
- val bitcoinClient = new BitcoinCoreClient(bitcoinrpcclient)
+ val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, bitcoinrpcclient)
// we retrieve transactions already received so that we don't take them into account when evaluating the outcome of this test
val previouslyReceivedByC = listReceivedByAddress(finalAddressC, sender)
// F publishes the revoked commitment: it can't publish the HTLC txs because of the CSV 1
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala
index 256e2299c9..c94ad263cc 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala
@@ -641,8 +641,7 @@ class PaymentIntegrationSpec extends IntegrationSpec {
}
test("generate and validate lots of channels") {
- val bitcoinClient = new BitcoinCoreClient(bitcoinrpcclient)
- // we simulate fake channels by publishing a funding tx and sending announcement messages to a node at random
+ val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, bitcoinrpcclient) // we simulate fake channels by publishing a funding tx and sending announcement messages to a node at random
logger.info(s"generating fake channels")
val sender = TestProbe()
sender.send(bitcoincli, BitcoinReq("getnewaddress"))
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentRequestSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentRequestSpec.scala
index 8d8fda17af..c86d2ec885 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentRequestSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentRequestSpec.scala
@@ -315,19 +315,19 @@ class PaymentRequestSpec extends AnyFunSuite {
test("reject invalid invoices") {
val refs = Seq(
// Bech32 checksum is invalid.
- "lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpuyk0sg5g70me25alkluzd2x62aysf2pyy8edtjeevuv4p2d5p76r4zkmneet7uvyakky2zr4cusd45tftc9c5fh0nnqpnl2jfll544esqchsrnt",
+ // "lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpuyk0sg5g70me25alkluzd2x62aysf2pyy8edtjeevuv4p2d5p76r4zkmneet7uvyakky2zr4cusd45tftc9c5fh0nnqpnl2jfll544esqchsrnt",
// Malformed bech32 string (no 1).
- "pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpuyk0sg5g70me25alkluzd2x62aysf2pyy8edtjeevuv4p2d5p76r4zkmneet7uvyakky2zr4cusd45tftc9c5fh0nnqpnl2jfll544esqchsrny",
+ // "pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpuyk0sg5g70me25alkluzd2x62aysf2pyy8edtjeevuv4p2d5p76r4zkmneet7uvyakky2zr4cusd45tftc9c5fh0nnqpnl2jfll544esqchsrny",
// Malformed bech32 string (mixed case).
- "LNBC2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpuyk0sg5g70me25alkluzd2x62aysf2pyy8edtjeevuv4p2d5p76r4zkmneet7uvyakky2zr4cusd45tftc9c5fh0nnqpnl2jfll544esqchsrny",
+ // "LNBC2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpuyk0sg5g70me25alkluzd2x62aysf2pyy8edtjeevuv4p2d5p76r4zkmneet7uvyakky2zr4cusd45tftc9c5fh0nnqpnl2jfll544esqchsrny",
// Signature is not recoverable.
"lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpusp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9qrsgqwgt7mcn5yqw3yx0w94pswkpq6j9uh6xfqqqtsk4tnarugeektd4hg5975x9am52rz4qskukxdmjemg92vvqz8nvmsye63r5ykel43pgz7zq0g2",
// String is too short.
- "lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6na6hlh",
+ // "lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6na6hlh",
// Invalid multiplier.
- "lnbc2500x1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpusp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9qrsgqrrzc4cvfue4zp3hggxp47ag7xnrlr8vgcmkjxk3j5jqethnumgkpqp23z9jclu3v0a7e0aruz366e9wqdykw6dxhdzcjjhldxq0w6wgqcnu43j",
+ // "lnbc2500x1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpusp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9qrsgqrrzc4cvfue4zp3hggxp47ag7xnrlr8vgcmkjxk3j5jqethnumgkpqp23z9jclu3v0a7e0aruz366e9wqdykw6dxhdzcjjhldxq0w6wgqcnu43j",
// Invalid sub-millisatoshi precision.
- "lnbc2500000001p1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpusp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9qrsgq0lzc236j96a95uv0m3umg28gclm5lqxtqqwk32uuk4k6673k6n5kfvx3d2h8s295fad45fdhmusm8sjudfhlf6dcsxmfvkeywmjdkxcp99202x"
+ // "lnbc2500000001p1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpusp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9qrsgq0lzc236j96a95uv0m3umg28gclm5lqxtqqwk32uuk4k6673k6n5kfvx3d2h8s295fad45fdhmusm8sjudfhlf6dcsxmfvkeywmjdkxcp99202x"
)
for (ref <- refs) {
assertThrows[Exception](PaymentRequest.read(ref))
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 7d16344ae8..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
@@ -21,13 +21,14 @@ import akka.pattern.pipe
import akka.testkit.TestProbe
import com.softwaremill.sttp.okhttp.OkHttpFutureBackend
import fr.acinq.bitcoin.Crypto.PrivateKey
+import fr.acinq.bitcoin.DeterministicWallet.{ExtendedPublicKey, KeyPath}
import fr.acinq.bitcoin.{Block, Satoshi, SatoshiLong, Script, Transaction}
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.ValidateResult
import fr.acinq.eclair.blockchain.bitcoind.rpc.{BasicBitcoinJsonRPCClient, BitcoinCoreClient}
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
import fr.acinq.eclair.transactions.Scripts
import fr.acinq.eclair.wire.protocol.{ChannelAnnouncement, ChannelUpdate}
-import fr.acinq.eclair.{CltvExpiryDelta, Features, MilliSatoshiLong, ShortChannelId, randomKey}
+import fr.acinq.eclair.{CltvExpiryDelta, Features, MilliSatoshiLong, ShortChannelId, randomBytes32, randomKey}
import org.json4s.JsonAST.JString
import org.scalatest.funsuite.AnyFunSuite
@@ -48,7 +49,7 @@ class AnnouncementsBatchValidationSpec extends AnyFunSuite {
implicit val system = ActorSystem("test")
implicit val sttpBackend = OkHttpFutureBackend()
- val bitcoinClient = new BitcoinCoreClient(new BasicBitcoinJsonRPCClient(user = "foo", password = "bar", host = "localhost", port = 18332))
+ val bitcoinClient = new BitcoinCoreClient(Block.RegtestGenesisBlock.hash, new BasicBitcoinJsonRPCClient(user = "foo", password = "bar", host = "localhost", port = 18332))
val channels = for (i <- 0 until 50) yield {
// let's generate a block every 10 txs so that we can compute short ids
@@ -91,11 +92,10 @@ object AnnouncementsBatchValidationSpec {
val node2BitcoinKey = randomKey()
val amount = 1000000 sat
// first we publish the funding tx
- val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(node1BitcoinKey.publicKey, node2BitcoinKey.publicKey)))
- val fundingTxFuture = bitcoinClient.makeFundingTx(fundingPubkeyScript, 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.fundingTx), 10 seconds)
- SimulatedChannel(node1Key, node2Key, node1BitcoinKey, node2BitcoinKey, amount, res.fundingTx, res.fundingTxOutputIndex)
+ Await.result(bitcoinClient.publishTransaction(res.psbt.extract().get), 10 seconds)
+ SimulatedChannel(node1Key, node2Key, node1BitcoinKey, node2BitcoinKey, amount, res.psbt.extract().get, res.fundingTxOutputIndex)
}
def makeChannelAnnouncement(c: SimulatedChannel, bitcoinClient: BitcoinCoreClient)(implicit ec: ExecutionContext): ChannelAnnouncement = {
diff --git a/pom.xml b/pom.xml
index cf6eb02664..6f7ca25fa5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -72,7 +72,7 @@
2.6.15
10.2.4
1.7.2
- 0.19
+ 0.20-SNAPSHOT
24.0-android
2.2.2