1
0
Fork 0
mirror of https://github.com/ACINQ/eclair.git synced 2025-03-12 19:01:39 +01:00

Cache both onchain public key and public key scripts

This commit is contained in:
sstone 2025-03-03 16:09:54 +01:00
parent 2db6229065
commit da7950b836
No known key found for this signature in database
GPG key ID: E04E48E72C205463
11 changed files with 120 additions and 46 deletions

View file

@ -23,7 +23,7 @@ import akka.actor.{ActorRef, ActorSystem, Props, SupervisorStrategy, typed}
import akka.pattern.after
import akka.util.Timeout
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
import fr.acinq.bitcoin.scalacompat.{Block, BlockHash, BlockId, ByteVector32, Satoshi, Script, addressToPublicKeyScript}
import fr.acinq.bitcoin.scalacompat.{Block, BlockHash, BlockId, ByteVector32, Satoshi, Script, ScriptElt, addressToPublicKeyScript}
import fr.acinq.eclair.NodeParams.hashFromChain
import fr.acinq.eclair.Setup.Seeds
import fr.acinq.eclair.balance.{BalanceActor, ChannelsListener}
@ -264,6 +264,7 @@ class Setup(val datadir: File,
_ <- feeratesRetrieved.future
finalPubkey = new AtomicReference[PublicKey](null)
finalPubkeyScript = new AtomicReference[Seq[ScriptElt]](null)
pubkeyRefreshDelay = FiniteDuration(config.getDuration("bitcoind.final-pubkey-refresh-delay").getSeconds, TimeUnit.SECONDS)
// there are 3 possibilities regarding onchain key management:
// 1) there is no `eclair-signer.conf` file in Eclair's data directory, Eclair will not manage Bitcoin core keys, and Eclair's API will not return bitcoin core descriptors. This is the default mode.
@ -273,17 +274,25 @@ class Setup(val datadir: File,
// 3) there is an `eclair-signer.conf` file in Eclair's data directory, and the name of the wallet set in `eclair-signer.conf` matches the `eclair.bitcoind.wallet` setting in `eclair.conf`.
// Eclair will assume that this is a watch-only bitcoin wallet that has been created from descriptors generated by Eclair, and will manage its private keys, and here we pass the onchain key manager to our bitcoin client.
bitcoinClient = new BitcoinCoreClient(bitcoin, nodeParams.liquidityAdsConfig.lockUtxos, if (bitcoin.wallet == onChainKeyManager_opt.map(_.walletName)) onChainKeyManager_opt else None) with OnchainPubkeyCache {
val refresher: typed.ActorRef[OnchainPubkeyRefresher.Command] = system.spawn(Behaviors.supervise(OnchainPubkeyRefresher(this, finalPubkey, pubkeyRefreshDelay)).onFailure(typed.SupervisorStrategy.restart), name = "onchain-address-manager")
val refresher: typed.ActorRef[OnchainPubkeyRefresher.Command] = system.spawn(Behaviors.supervise(OnchainPubkeyRefresher(this, finalPubkey, finalPubkeyScript, pubkeyRefreshDelay)).onFailure(typed.SupervisorStrategy.restart), name = "onchain-address-manager")
override def getP2wpkhPubkey(renew: Boolean): PublicKey = {
val key = finalPubkey.get()
if (renew) refresher ! OnchainPubkeyRefresher.Renew
key
}
override def getReceivePubkeyScript(renew: Boolean): Seq[ScriptElt] = {
val script = finalPubkeyScript.get()
if (renew) refresher ! OnchainPubkeyRefresher.RenewPubkeyScript
script
}
}
_ = if (bitcoinClient.useEclairSigner) logger.info("using eclair to sign bitcoin core transactions")
initialPubkey <- bitcoinClient.getP2wpkhPubkey()
_ = finalPubkey.set(initialPubkey)
initialPubkeyScript <- bitcoinClient.getReceivePublicKeyScript()
_ = finalPubkeyScript.set(initialPubkeyScript)
// If we started funding a transaction and restarted before signing it, we may have utxos that stay locked forever.
// We want to do something about it: we can unlock them automatically, or let the node operator decide what to do.

View file

@ -18,7 +18,7 @@ package fr.acinq.eclair.blockchain
import fr.acinq.bitcoin.psbt.Psbt
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
import fr.acinq.bitcoin.scalacompat.{OutPoint, Satoshi, Transaction, TxId}
import fr.acinq.bitcoin.scalacompat.{OutPoint, Satoshi, ScriptElt, Transaction, TxId}
import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient.AddressType
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
import scodec.bits.ByteVector
@ -117,10 +117,7 @@ trait OnChainChannelFunder {
/** This trait lets users generate on-chain addresses and public keys. */
trait OnChainAddressGenerator {
/**
* @param label used if implemented with bitcoin core, can be ignored by implementation
*/
def getReceiveAddress(addressType: Option[AddressType] = None)(implicit ec: ExecutionContext): Future[String]
def getReceivePublicKeyScript(addressType: Option[AddressType] = None)(implicit ec: ExecutionContext): Future[Seq[ScriptElt]]
/** Generate a p2wpkh wallet address and return the corresponding public key. */
def getP2wpkhPubkey()(implicit ec: ExecutionContext): Future[PublicKey]
@ -133,6 +130,8 @@ trait OnchainPubkeyCache {
* @param renew applies after requesting the current pubkey, and is asynchronous
*/
def getP2wpkhPubkey(renew: Boolean = true): PublicKey
def getReceivePubkeyScript(renew: Boolean = true): Seq[ScriptElt]
}
/** This trait lets users check the wallet's on-chain balance. */

View file

@ -4,6 +4,7 @@ package fr.acinq.eclair.blockchain.bitcoind
import akka.actor.typed.Behavior
import akka.actor.typed.scaladsl.{ActorContext, Behaviors, TimerScheduler}
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
import fr.acinq.bitcoin.scalacompat.{BlockHash, ScriptElt, addressToPublicKeyScript}
import fr.acinq.eclair.blockchain.OnChainAddressGenerator
import java.util.concurrent.atomic.AtomicReference
@ -19,27 +20,29 @@ object OnchainPubkeyRefresher {
// @formatter:off
sealed trait Command
case object Renew extends Command
case object RenewPubkeyScript extends Command
private case class Set(pubkey: PublicKey) extends Command
private case class SetPubkeyScript(script: Seq[ScriptElt]) extends Command
private case class Error(reason: Throwable) extends Command
private case object Done extends Command
// @formatter:on
def apply(generator: OnChainAddressGenerator, finalPubkey: AtomicReference[PublicKey], delay: FiniteDuration): Behavior[Command] = {
def apply(generator: OnChainAddressGenerator, finalPubkey: AtomicReference[PublicKey], finalPubkeyScript: AtomicReference[Seq[ScriptElt]], delay: FiniteDuration): Behavior[Command] = {
Behaviors.setup { context =>
Behaviors.withTimers { timers =>
new OnchainPubkeyRefresher(generator, finalPubkey, context, timers, delay).idle()
new OnchainPubkeyRefresher(generator, finalPubkey, finalPubkeyScript, context, timers, delay).idle()
}
}
}
}
private class OnchainPubkeyRefresher(generator: OnChainAddressGenerator, finalPubkey: AtomicReference[PublicKey], context: ActorContext[OnchainPubkeyRefresher.Command], timers: TimerScheduler[OnchainPubkeyRefresher.Command], delay: FiniteDuration) {
private class OnchainPubkeyRefresher(generator: OnChainAddressGenerator, finalPubkey: AtomicReference[PublicKey], finalPubkeyScript: AtomicReference[Seq[ScriptElt]], context: ActorContext[OnchainPubkeyRefresher.Command], timers: TimerScheduler[OnchainPubkeyRefresher.Command], delay: FiniteDuration) {
import OnchainPubkeyRefresher._
def idle(): Behavior[Command] = Behaviors.receiveMessagePartial {
case Renew =>
context.log.debug(s"received Renew current script is ${finalPubkey.get()}")
context.log.debug(s"received Renew current pubkey is ${finalPubkey.get()}")
context.pipeToSelf(generator.getP2wpkhPubkey()) {
case Success(pubkey) => Set(pubkey)
case Failure(reason) => Error(reason)
@ -52,12 +55,33 @@ private class OnchainPubkeyRefresher(generator: OnChainAddressGenerator, finalPu
context.log.error("cannot generate new onchain address", reason)
Behaviors.same
}
case RenewPubkeyScript =>
context.log.debug(s"received Renew current script is ${finalPubkeyScript.get()}")
context.pipeToSelf(generator.getReceivePublicKeyScript()) {
case Success(script) => SetPubkeyScript(script)
case Failure(reason) => Error(reason)
}
Behaviors.receiveMessagePartial {
case SetPubkeyScript(script) =>
timers.startSingleTimer(Done, delay) // wait a bit to avoid generating too many addresses in case of mass channel force-close
waiting(script)
case Error(reason) =>
context.log.error("cannot generate new onchain address", reason)
Behaviors.same
}
}
def waiting(script: PublicKey): Behavior[Command] = Behaviors.receiveMessagePartial {
def waiting(pubkey: PublicKey): Behavior[Command] = Behaviors.receiveMessagePartial {
case Done =>
context.log.info(s"setting final onchain pubkey to $pubkey")
finalPubkey.set(pubkey)
idle()
}
def waiting(script: Seq[ScriptElt]): Behavior[Command] = Behaviors.receiveMessagePartial {
case Done =>
context.log.info(s"setting final onchain script to $script")
finalPubkey.set(script)
finalPubkeyScript.set(script)
idle()
}
}

View file

@ -592,6 +592,10 @@ class BitcoinCoreClient(val rpcClient: BitcoinJsonRPCClient, val lockUtxos: Bool
}
}
def getReceivePublicKeyScript(addressType: Option[AddressType] = None)(implicit ec: ExecutionContext): Future[Seq[ScriptElt]] = getReceiveAddress(addressType).map { address =>
addressToPublicKeyScript(this.rpcClient.chainHash, address).getOrElse(throw new RuntimeException(s"cannot convert $address to a public key script"))
}
def getChangeAddress(addressType: Option[AddressType] = None)(implicit ec: ExecutionContext): Future[String] = for {
JString(address) <- rpcClient.invoke("getrawchangeaddress", addressType.map(_.bitcoinCoreName).toList: _*)
verifiedAddress <- verifyAddress(address)

View file

@ -23,6 +23,7 @@ import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey, sha256}
import fr.acinq.bitcoin.scalacompat.Script._
import fr.acinq.bitcoin.scalacompat._
import fr.acinq.eclair._
import fr.acinq.eclair.blockchain.OnchainPubkeyCache
import fr.acinq.eclair.blockchain.fee._
import fr.acinq.eclair.channel.fsm.Channel
import fr.acinq.eclair.channel.fsm.Channel.REFRESH_CHANNEL_UPDATE_INTERVAL
@ -628,6 +629,16 @@ object Helpers {
object MutualClose {
def generateFinalScriptPubKey(wallet: OnchainPubkeyCache, allowAnySegwit: Boolean, renew: Boolean = true): ByteVector = {
val finalScriptPubkey = if (!allowAnySegwit) {
val finalPubKey = wallet.getP2wpkhPubkey(renew)
Script.write(Script.pay2wpkh(finalPubKey))
} else {
Script.write(wallet.getReceivePubkeyScript(renew))
}
finalScriptPubkey
}
def isValidFinalScriptPubkey(scriptPubKey: ByteVector, allowAnySegwit: Boolean, allowOpReturn: Boolean): Boolean = {
Try(Script.parse(scriptPubKey)) match {
case Success(OP_DUP :: OP_HASH160 :: OP_PUSHDATA(pubkeyHash, _) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) if pubkeyHash.size == 20 => true

View file

@ -17,7 +17,7 @@
package fr.acinq.eclair.channel.fsm
import akka.actor.FSM
import fr.acinq.bitcoin.scalacompat.{ByteVector32, Script}
import fr.acinq.bitcoin.scalacompat.ByteVector32
import fr.acinq.eclair.Features
import fr.acinq.eclair.channel.Helpers.Closing.MutualClose
import fr.acinq.eclair.channel._
@ -110,6 +110,7 @@ trait CommonHandlers {
case d: DATA_NEGOTIATING_SIMPLE => d.localScriptPubKey
case d: DATA_CLOSING => d.finalScriptPubKey
case d =>
val allowAnySegwit = Features.canUseFeature(data.commitments.params.localParams.initFeatures, data.commitments.params.remoteParams.initFeatures, Features.ShutdownAnySegwit)
d.commitments.params.localParams.upfrontShutdownScript_opt match {
case Some(upfrontShutdownScript) =>
if (data.commitments.params.channelFeatures.hasFeature(Features.UpfrontShutdownScript)) {
@ -117,21 +118,18 @@ trait CommonHandlers {
upfrontShutdownScript
} else {
log.info("ignoring pre-generated shutdown script, because option_upfront_shutdown_script is disabled")
generateFinalScriptPubKey()
val finalScriptPubkey = Helpers.Closing.MutualClose.generateFinalScriptPubKey(wallet, allowAnySegwit)
log.info(s"using finalScriptPubkey=$finalScriptPubkey")
finalScriptPubkey
}
case None =>
// normal case: we don't pre-generate shutdown scripts
generateFinalScriptPubKey()
val finalScriptPubkey = Helpers.Closing.MutualClose.generateFinalScriptPubKey(wallet, allowAnySegwit)
log.info(s"using finalScriptPubkey=$finalScriptPubkey")
finalScriptPubkey
}
}
private def generateFinalScriptPubKey(): ByteVector = {
val finalPubKey = wallet.getP2wpkhPubkey()
val finalScriptPubKey = Script.write(Script.pay2wpkh(finalPubKey))
log.info(s"using finalScriptPubkey=$finalScriptPubKey")
finalScriptPubKey
}
def startSimpleClose(commitments: Commitments, localShutdown: Shutdown, remoteShutdown: Shutdown, closingFeerates: Option[ClosingFeerates]): (DATA_NEGOTIATING_SIMPLE, Option[ClosingComplete]) = {
val localScript = localShutdown.scriptPubKey
val remoteScript = remoteShutdown.scriptPubKey

View file

@ -324,11 +324,10 @@ private class OpenChannelInterceptor(peer: ActorRef[Any],
}
private def createLocalParams(nodeParams: NodeParams, initFeatures: Features[InitFeature], upfrontShutdownScript: Boolean, channelType: SupportedChannelType, isChannelOpener: Boolean, paysCommitTxFees: Boolean, dualFunded: Boolean, fundingAmount: Satoshi, disableMaxHtlcValueInFlight: Boolean): LocalParams = {
val pubkey_opt = if (upfrontShutdownScript || channelType.paysDirectlyToWallet) Some(wallet.getP2wpkhPubkey()) else None
makeChannelParams(
nodeParams, initFeatures,
if (upfrontShutdownScript) Some(Script.write(Script.pay2wpkh(pubkey_opt.get))) else None,
if (channelType.paysDirectlyToWallet) Some(pubkey_opt.get) else None,
if (upfrontShutdownScript) Some(Script.write(wallet.getReceivePubkeyScript())) else None,
if (channelType.paysDirectlyToWallet) Some(wallet.getP2wpkhPubkey()) else None,
isChannelOpener = isChannelOpener,
paysCommitTxFees = paysCommitTxFees,
dualFunded = dualFunded,

View file

@ -49,7 +49,10 @@ class DummyOnChainWallet extends OnChainWallet with OnchainPubkeyCache {
override def onChainBalance()(implicit ec: ExecutionContext): Future[OnChainBalance] = Future.successful(OnChainBalance(1105 sat, 561 sat))
override def getReceiveAddress(addressTYpe: Option[AddressType] = None)(implicit ec: ExecutionContext): Future[String] = Future.successful(dummyReceiveAddress)
override def getReceivePublicKeyScript(addressType: Option[AddressType] = None)(implicit ec: ExecutionContext): Future[Seq[ScriptElt]] = Future.successful(addressType match {
case Some(AddressType.P2tr) => Script.pay2tr(dummyReceivePubkey.xOnly)
case _ => Script.pay2wpkh(dummyReceivePubkey)
})
override def getP2wpkhPubkey()(implicit ec: ExecutionContext): Future[Crypto.PublicKey] = Future.successful(dummyReceivePubkey)
@ -92,6 +95,8 @@ class DummyOnChainWallet extends OnChainWallet with OnchainPubkeyCache {
override def doubleSpent(tx: Transaction)(implicit ec: ExecutionContext): Future[Boolean] = Future.successful(false)
override def getP2wpkhPubkey(renew: Boolean): PublicKey = dummyReceivePubkey
override def getReceivePubkeyScript(renew: Boolean): Seq[ScriptElt] = Script.pay2tr(dummyReceivePubkey.xOnly)
}
class NoOpOnChainWallet extends OnChainWallet with OnchainPubkeyCache {
@ -104,7 +109,10 @@ class NoOpOnChainWallet extends OnChainWallet with OnchainPubkeyCache {
override def onChainBalance()(implicit ec: ExecutionContext): Future[OnChainBalance] = Future.successful(OnChainBalance(1105 sat, 561 sat))
override def getReceiveAddress(addressType: Option[AddressType] = None)(implicit ec: ExecutionContext): Future[String] = Future.successful(dummyReceiveAddress)
override def getReceivePublicKeyScript(addressType: Option[AddressType] = None)(implicit ec: ExecutionContext): Future[Seq[ScriptElt]] = Future.successful(addressType match {
case Some(AddressType.P2tr) => Script.pay2tr(dummyReceivePubkey.xOnly)
case _ => Script.pay2wpkh(dummyReceivePubkey)
})
override def getP2wpkhPubkey()(implicit ec: ExecutionContext): Future[Crypto.PublicKey] = Future.successful(dummyReceivePubkey)
@ -137,6 +145,8 @@ class NoOpOnChainWallet extends OnChainWallet with OnchainPubkeyCache {
override def doubleSpent(tx: Transaction)(implicit ec: ExecutionContext): Future[Boolean] = Future.successful(doubleSpent.contains(tx.txid))
override def getP2wpkhPubkey(renew: Boolean): PublicKey = dummyReceivePubkey
override def getReceivePubkeyScript(renew: Boolean): Seq[ScriptElt] = Script.pay2tr(dummyReceivePubkey.xOnly)
}
class SingleKeyOnChainWallet extends OnChainWallet with OnchainPubkeyCache {
@ -164,10 +174,10 @@ class SingleKeyOnChainWallet extends OnChainWallet with OnchainPubkeyCache {
override def onChainBalance()(implicit ec: ExecutionContext): Future[OnChainBalance] = Future.successful(OnChainBalance(1105 sat, 561 sat))
override def getReceiveAddress(addressType: Option[AddressType] = None)(implicit ec: ExecutionContext): Future[String] = addressType match {
case Some(AddressType.P2tr) => Future.successful(pubkey.xOnly.pub.p2trAddress(fr.acinq.bitcoin.Block.RegtestGenesisBlock.hash))
case _ => Future.successful(Bech32.encodeWitnessAddress("bcrt", 0, pubkey.hash160.toArray))
}
override def getReceivePublicKeyScript(addressType: Option[AddressType] = None)(implicit ec: ExecutionContext): Future[Seq[ScriptElt]] = Future.successful(addressType match {
case Some(AddressType.P2wpkh) => script84
case _ => script86
})
override def getP2wpkhPubkey()(implicit ec: ExecutionContext): Future[Crypto.PublicKey] = Future.successful(pubkey)
@ -280,11 +290,11 @@ class SingleKeyOnChainWallet extends OnChainWallet with OnchainPubkeyCache {
override def doubleSpent(tx: Transaction)(implicit ec: ExecutionContext): Future[Boolean] = Future.successful(doubleSpent.contains(tx.txid))
override def getP2wpkhPubkey(renew: Boolean): PublicKey = pubkey
override def getReceivePubkeyScript(renew: Boolean): Seq[ScriptElt] = script86
}
object DummyOnChainWallet {
val dummyReceiveAddress: String = "bcrt1qwcv8naajwn8fjhu8z59q9e6ucrqr068rlcenux"
val dummyReceivePubkey: PublicKey = PublicKey(hex"028feba10d0eafd0fad8fe20e6d9206e6bd30242826de05c63f459a00aced24b12")
def makeDummyFundingTx(pubkeyScript: ByteVector, amount: Satoshi): MakeFundingTxResponse = {

View file

@ -2,7 +2,7 @@ package fr.acinq.eclair.blockchain.bitcoind
import akka.actor.typed.scaladsl.adapter.ClassicActorSystemOps
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
import fr.acinq.bitcoin.scalacompat.{Block, Crypto, computeBIP84Address}
import fr.acinq.bitcoin.scalacompat.{Block, Crypto, Script, ScriptElt, computeBIP84Address}
import fr.acinq.eclair.blockchain.OnChainAddressGenerator
import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient.AddressType
import fr.acinq.eclair.{TestKitBaseClass, randomKey}
@ -15,16 +15,24 @@ import scala.concurrent.{ExecutionContext, Future}
class OnchainPubkeyRefresherSpec extends TestKitBaseClass with AnyFunSuiteLike {
test("renew onchain scripts") {
val finalPubkey = new AtomicReference[PublicKey](randomKey().publicKey)
val finalPubkeyScript = new AtomicReference[Seq[ScriptElt]](Script.pay2tr(randomKey().xOnlyPublicKey()))
val generator = new OnChainAddressGenerator {
override def getReceiveAddress(addressType: Option[AddressType] = None)(implicit ec: ExecutionContext): Future[String] = Future.successful(computeBIP84Address(randomKey().publicKey, Block.RegtestGenesisBlock.hash))
override def getReceivePublicKeyScript(addressType: Option[AddressType] = None)(implicit ec: ExecutionContext): Future[Seq[ScriptElt]] = Future.successful(addressType match {
case Some(AddressType.P2tr) => Script.pay2tr(randomKey().xOnlyPublicKey())
case _ => Script.pay2wpkh(randomKey().publicKey)
})
override def getP2wpkhPubkey()(implicit ec: ExecutionContext): Future[Crypto.PublicKey] = Future.successful(randomKey().publicKey)
}
val manager = system.spawnAnonymous(OnchainPubkeyRefresher(generator, finalPubkey, 3 seconds))
// renew script explicitly
val manager = system.spawnAnonymous(OnchainPubkeyRefresher(generator, finalPubkey, finalPubkeyScript, 3 seconds))
// renew pubkey explicitly
val currentPubkey = finalPubkey.get()
manager ! OnchainPubkeyRefresher.Renew
awaitCond(finalPubkey.get() != currentPubkey)
// renew pubkey explicitly
val currentPubkeyScript = finalPubkeyScript.get()
manager ! OnchainPubkeyRefresher.RenewPubkeyScript
awaitCond(finalPubkeyScript.get() != currentPubkeyScript)
}
}

View file

@ -22,7 +22,7 @@ import akka.pattern.pipe
import akka.testkit.{TestFSMRef, TestProbe}
import com.softwaremill.quicklens.ModifyPimp
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
import fr.acinq.bitcoin.scalacompat.{Block, BtcAmount, MilliBtcDouble, MnemonicCode, OutPoint, SatoshiLong, Transaction, TxId}
import fr.acinq.bitcoin.scalacompat.{Block, BtcAmount, MilliBtcDouble, MnemonicCode, OutPoint, SatoshiLong, ScriptElt, Transaction, TxId}
import fr.acinq.eclair.NotificationsLogger.NotifyNodeOperator
import fr.acinq.eclair.blockchain.bitcoind.BitcoindService
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._
@ -129,8 +129,14 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w
getP2wpkhPubkey().pipeTo(probe.ref)
probe.expectMsgType[PublicKey]
}
val pubkeyScript = {
getReceivePublicKeyScript(None).pipeTo(probe.ref)
probe.expectMsgType[Seq[ScriptElt]]
}
override def getP2wpkhPubkey(renew: Boolean): PublicKey = pubkey
override def getReceivePubkeyScript(renew: Boolean): Seq[ScriptElt] = pubkeyScript
}
(walletRpcClient, walletClient)
@ -1848,8 +1854,14 @@ class ReplaceableTxPublisherWithEclairSignerSpec extends ReplaceableTxPublisherS
getP2wpkhPubkey().pipeTo(probe.ref)
probe.expectMsgType[PublicKey]
}
lazy val pubkeyScript = {
getReceivePublicKeyScript(None).pipeTo(probe.ref)
probe.expectMsgType[Seq[ScriptElt]]
}
override def getP2wpkhPubkey(renew: Boolean): PublicKey = pubkey
override def getReceivePubkeyScript(renew: Boolean): Seq[ScriptElt] = pubkeyScript
}
createEclairBackedWallet(walletRpcClient, keyManager)

View file

@ -24,7 +24,7 @@ import akka.testkit.TestProbe
import com.typesafe.config.ConfigFactory
import fr.acinq.bitcoin.ScriptFlags
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
import fr.acinq.bitcoin.scalacompat.{Block, BtcDouble, ByteVector32, Crypto, OutPoint, SatoshiLong, Script, Transaction, TxId, computeBIP84Address}
import fr.acinq.bitcoin.scalacompat.{Block, BtcDouble, ByteVector32, Crypto, OutPoint, SatoshiLong, Script, Transaction, TxId, addressFromPublicKeyScript, computeBIP84Address}
import fr.acinq.eclair.blockchain.bitcoind.BitcoindService.BitcoinReq
import fr.acinq.eclair.blockchain.bitcoind.rpc.{BitcoinCoreClient, JsonRPCError}
import fr.acinq.eclair.channel._
@ -148,11 +148,11 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec {
sender.send(nodes("C").register, Register.Forward(sender.ref.toTyped[Any], htlc.channelId, CMD_GET_CHANNEL_DATA(ActorRef.noSender)))
val dataC = sender.expectMsgType[RES_GET_CHANNEL_DATA[DATA_NORMAL]].data
assert(dataC.commitments.params.commitmentFormat == commitmentFormat)
val finalAddressC = computeBIP84Address(nodes("C").wallet.getP2wpkhPubkey(false), Block.RegtestGenesisBlock.hash)
val Right(finalAddressC) = addressFromPublicKeyScript(Block.RegtestGenesisBlock.hash, nodes("C").wallet.getReceivePubkeyScript(false))
sender.send(nodes("F").register, Register.Forward(sender.ref.toTyped[Any], htlc.channelId, CMD_GET_CHANNEL_DATA(ActorRef.noSender)))
val dataF = sender.expectMsgType[RES_GET_CHANNEL_DATA[DATA_NORMAL]].data
assert(dataF.commitments.params.commitmentFormat == commitmentFormat)
val finalAddressF = computeBIP84Address(nodes("F").wallet.getP2wpkhPubkey(false), Block.RegtestGenesisBlock.hash)
val Right(finalAddressF) = addressFromPublicKeyScript(Block.RegtestGenesisBlock.hash, nodes("F").wallet.getReceivePubkeyScript(false))
ForceCloseFixture(sender, paymentSender, stateListenerC, stateListenerF, paymentId, htlc, preimage, minerAddress, finalAddressC, finalAddressF)
}
@ -434,7 +434,7 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec {
// we retrieve C's default final address
sender.send(nodes("C").register, Register.Forward(sender.ref.toTyped[Any], commitmentsF.channelId, CMD_GET_CHANNEL_DATA(ActorRef.noSender)))
sender.expectMsgType[RES_GET_CHANNEL_DATA[DATA_NORMAL]]
val finalAddressC = computeBIP84Address(nodes("C").wallet.getP2wpkhPubkey(false), Block.RegtestGenesisBlock.hash)
val Right(finalAddressC) = addressFromPublicKeyScript(Block.RegtestGenesisBlock.hash, nodes("C").wallet.getReceivePubkeyScript(false))
// we prepare the revoked transactions F will publish
val keyManagerF = nodes("F").nodeParams.channelKeyManager
val channelKeyPathF = keyManagerF.keyPath(commitmentsF.params.localParams, commitmentsF.params.channelConfig)
@ -566,8 +566,8 @@ class StandardChannelIntegrationSpec extends ChannelIntegrationSpec {
sender.send(funder.register, Register.Forward(sender.ref.toTyped[Any], channelId, CMD_GET_CHANNEL_DATA(ActorRef.noSender)))
val commitmentsC = sender.expectMsgType[RES_GET_CHANNEL_DATA[DATA_NORMAL]].data.commitments
val fundingOutpoint = commitmentsC.latest.commitInput.outPoint
val finalPubKeyScriptC = Script.write(Script.pay2wpkh(nodes("C").wallet.getP2wpkhPubkey(false)))
val finalPubKeyScriptF = Script.write(Script.pay2wpkh(nodes("F").wallet.getP2wpkhPubkey(false)))
val finalPubKeyScriptC = Helpers.Closing.MutualClose.generateFinalScriptPubKey(nodes("C").wallet, true, false)
val finalPubKeyScriptF = Helpers.Closing.MutualClose.generateFinalScriptPubKey(nodes("F").wallet, true, false)
fundee.register ! Register.Forward(sender.ref.toTyped[Any], channelId, CMD_CLOSE(sender.ref, None, None))
sender.expectMsgType[RES_SUCCESS[CMD_CLOSE]]