mirror of
https://github.com/ACINQ/eclair.git
synced 2025-03-13 11:35:47 +01:00
Cache both onchain public key and public key scripts
This commit is contained in:
parent
2db6229065
commit
da7950b836
11 changed files with 120 additions and 46 deletions
|
@ -23,7 +23,7 @@ import akka.actor.{ActorRef, ActorSystem, Props, SupervisorStrategy, typed}
|
||||||
import akka.pattern.after
|
import akka.pattern.after
|
||||||
import akka.util.Timeout
|
import akka.util.Timeout
|
||||||
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
|
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.NodeParams.hashFromChain
|
||||||
import fr.acinq.eclair.Setup.Seeds
|
import fr.acinq.eclair.Setup.Seeds
|
||||||
import fr.acinq.eclair.balance.{BalanceActor, ChannelsListener}
|
import fr.acinq.eclair.balance.{BalanceActor, ChannelsListener}
|
||||||
|
@ -264,6 +264,7 @@ class Setup(val datadir: File,
|
||||||
_ <- feeratesRetrieved.future
|
_ <- feeratesRetrieved.future
|
||||||
|
|
||||||
finalPubkey = new AtomicReference[PublicKey](null)
|
finalPubkey = new AtomicReference[PublicKey](null)
|
||||||
|
finalPubkeyScript = new AtomicReference[Seq[ScriptElt]](null)
|
||||||
pubkeyRefreshDelay = FiniteDuration(config.getDuration("bitcoind.final-pubkey-refresh-delay").getSeconds, TimeUnit.SECONDS)
|
pubkeyRefreshDelay = FiniteDuration(config.getDuration("bitcoind.final-pubkey-refresh-delay").getSeconds, TimeUnit.SECONDS)
|
||||||
// there are 3 possibilities regarding onchain key management:
|
// 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.
|
// 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`.
|
// 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.
|
// 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 {
|
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 = {
|
override def getP2wpkhPubkey(renew: Boolean): PublicKey = {
|
||||||
val key = finalPubkey.get()
|
val key = finalPubkey.get()
|
||||||
if (renew) refresher ! OnchainPubkeyRefresher.Renew
|
if (renew) refresher ! OnchainPubkeyRefresher.Renew
|
||||||
key
|
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")
|
_ = if (bitcoinClient.useEclairSigner) logger.info("using eclair to sign bitcoin core transactions")
|
||||||
initialPubkey <- bitcoinClient.getP2wpkhPubkey()
|
initialPubkey <- bitcoinClient.getP2wpkhPubkey()
|
||||||
_ = finalPubkey.set(initialPubkey)
|
_ = 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.
|
// 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.
|
// We want to do something about it: we can unlock them automatically, or let the node operator decide what to do.
|
||||||
|
|
|
@ -18,7 +18,7 @@ package fr.acinq.eclair.blockchain
|
||||||
|
|
||||||
import fr.acinq.bitcoin.psbt.Psbt
|
import fr.acinq.bitcoin.psbt.Psbt
|
||||||
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
|
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.bitcoind.rpc.BitcoinCoreClient.AddressType
|
||||||
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
|
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
|
||||||
import scodec.bits.ByteVector
|
import scodec.bits.ByteVector
|
||||||
|
@ -117,10 +117,7 @@ trait OnChainChannelFunder {
|
||||||
/** This trait lets users generate on-chain addresses and public keys. */
|
/** This trait lets users generate on-chain addresses and public keys. */
|
||||||
trait OnChainAddressGenerator {
|
trait OnChainAddressGenerator {
|
||||||
|
|
||||||
/**
|
def getReceivePublicKeyScript(addressType: Option[AddressType] = None)(implicit ec: ExecutionContext): Future[Seq[ScriptElt]]
|
||||||
* @param label used if implemented with bitcoin core, can be ignored by implementation
|
|
||||||
*/
|
|
||||||
def getReceiveAddress(addressType: Option[AddressType] = None)(implicit ec: ExecutionContext): Future[String]
|
|
||||||
|
|
||||||
/** Generate a p2wpkh wallet address and return the corresponding public key. */
|
/** Generate a p2wpkh wallet address and return the corresponding public key. */
|
||||||
def getP2wpkhPubkey()(implicit ec: ExecutionContext): Future[PublicKey]
|
def getP2wpkhPubkey()(implicit ec: ExecutionContext): Future[PublicKey]
|
||||||
|
@ -133,6 +130,8 @@ trait OnchainPubkeyCache {
|
||||||
* @param renew applies after requesting the current pubkey, and is asynchronous
|
* @param renew applies after requesting the current pubkey, and is asynchronous
|
||||||
*/
|
*/
|
||||||
def getP2wpkhPubkey(renew: Boolean = true): PublicKey
|
def getP2wpkhPubkey(renew: Boolean = true): PublicKey
|
||||||
|
|
||||||
|
def getReceivePubkeyScript(renew: Boolean = true): Seq[ScriptElt]
|
||||||
}
|
}
|
||||||
|
|
||||||
/** This trait lets users check the wallet's on-chain balance. */
|
/** This trait lets users check the wallet's on-chain balance. */
|
||||||
|
|
|
@ -4,6 +4,7 @@ package fr.acinq.eclair.blockchain.bitcoind
|
||||||
import akka.actor.typed.Behavior
|
import akka.actor.typed.Behavior
|
||||||
import akka.actor.typed.scaladsl.{ActorContext, Behaviors, TimerScheduler}
|
import akka.actor.typed.scaladsl.{ActorContext, Behaviors, TimerScheduler}
|
||||||
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
|
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
|
||||||
|
import fr.acinq.bitcoin.scalacompat.{BlockHash, ScriptElt, addressToPublicKeyScript}
|
||||||
import fr.acinq.eclair.blockchain.OnChainAddressGenerator
|
import fr.acinq.eclair.blockchain.OnChainAddressGenerator
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
@ -19,27 +20,29 @@ object OnchainPubkeyRefresher {
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
sealed trait Command
|
sealed trait Command
|
||||||
case object Renew extends Command
|
case object Renew extends Command
|
||||||
|
case object RenewPubkeyScript extends Command
|
||||||
private case class Set(pubkey: PublicKey) 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 class Error(reason: Throwable) extends Command
|
||||||
private case object Done extends Command
|
private case object Done extends Command
|
||||||
// @formatter:on
|
// @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.setup { context =>
|
||||||
Behaviors.withTimers { timers =>
|
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._
|
import OnchainPubkeyRefresher._
|
||||||
|
|
||||||
def idle(): Behavior[Command] = Behaviors.receiveMessagePartial {
|
def idle(): Behavior[Command] = Behaviors.receiveMessagePartial {
|
||||||
case Renew =>
|
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()) {
|
context.pipeToSelf(generator.getP2wpkhPubkey()) {
|
||||||
case Success(pubkey) => Set(pubkey)
|
case Success(pubkey) => Set(pubkey)
|
||||||
case Failure(reason) => Error(reason)
|
case Failure(reason) => Error(reason)
|
||||||
|
@ -52,12 +55,33 @@ private class OnchainPubkeyRefresher(generator: OnChainAddressGenerator, finalPu
|
||||||
context.log.error("cannot generate new onchain address", reason)
|
context.log.error("cannot generate new onchain address", reason)
|
||||||
Behaviors.same
|
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 =>
|
case Done =>
|
||||||
context.log.info(s"setting final onchain script to $script")
|
context.log.info(s"setting final onchain script to $script")
|
||||||
finalPubkey.set(script)
|
finalPubkeyScript.set(script)
|
||||||
idle()
|
idle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
def getChangeAddress(addressType: Option[AddressType] = None)(implicit ec: ExecutionContext): Future[String] = for {
|
||||||
JString(address) <- rpcClient.invoke("getrawchangeaddress", addressType.map(_.bitcoinCoreName).toList: _*)
|
JString(address) <- rpcClient.invoke("getrawchangeaddress", addressType.map(_.bitcoinCoreName).toList: _*)
|
||||||
verifiedAddress <- verifyAddress(address)
|
verifiedAddress <- verifyAddress(address)
|
||||||
|
|
|
@ -23,6 +23,7 @@ import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey, sha256}
|
||||||
import fr.acinq.bitcoin.scalacompat.Script._
|
import fr.acinq.bitcoin.scalacompat.Script._
|
||||||
import fr.acinq.bitcoin.scalacompat._
|
import fr.acinq.bitcoin.scalacompat._
|
||||||
import fr.acinq.eclair._
|
import fr.acinq.eclair._
|
||||||
|
import fr.acinq.eclair.blockchain.OnchainPubkeyCache
|
||||||
import fr.acinq.eclair.blockchain.fee._
|
import fr.acinq.eclair.blockchain.fee._
|
||||||
import fr.acinq.eclair.channel.fsm.Channel
|
import fr.acinq.eclair.channel.fsm.Channel
|
||||||
import fr.acinq.eclair.channel.fsm.Channel.REFRESH_CHANNEL_UPDATE_INTERVAL
|
import fr.acinq.eclair.channel.fsm.Channel.REFRESH_CHANNEL_UPDATE_INTERVAL
|
||||||
|
@ -628,6 +629,16 @@ object Helpers {
|
||||||
|
|
||||||
object MutualClose {
|
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 = {
|
def isValidFinalScriptPubkey(scriptPubKey: ByteVector, allowAnySegwit: Boolean, allowOpReturn: Boolean): Boolean = {
|
||||||
Try(Script.parse(scriptPubKey)) match {
|
Try(Script.parse(scriptPubKey)) match {
|
||||||
case Success(OP_DUP :: OP_HASH160 :: OP_PUSHDATA(pubkeyHash, _) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) if pubkeyHash.size == 20 => true
|
case Success(OP_DUP :: OP_HASH160 :: OP_PUSHDATA(pubkeyHash, _) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) if pubkeyHash.size == 20 => true
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
package fr.acinq.eclair.channel.fsm
|
package fr.acinq.eclair.channel.fsm
|
||||||
|
|
||||||
import akka.actor.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.Features
|
||||||
import fr.acinq.eclair.channel.Helpers.Closing.MutualClose
|
import fr.acinq.eclair.channel.Helpers.Closing.MutualClose
|
||||||
import fr.acinq.eclair.channel._
|
import fr.acinq.eclair.channel._
|
||||||
|
@ -110,6 +110,7 @@ trait CommonHandlers {
|
||||||
case d: DATA_NEGOTIATING_SIMPLE => d.localScriptPubKey
|
case d: DATA_NEGOTIATING_SIMPLE => d.localScriptPubKey
|
||||||
case d: DATA_CLOSING => d.finalScriptPubKey
|
case d: DATA_CLOSING => d.finalScriptPubKey
|
||||||
case d =>
|
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 {
|
d.commitments.params.localParams.upfrontShutdownScript_opt match {
|
||||||
case Some(upfrontShutdownScript) =>
|
case Some(upfrontShutdownScript) =>
|
||||||
if (data.commitments.params.channelFeatures.hasFeature(Features.UpfrontShutdownScript)) {
|
if (data.commitments.params.channelFeatures.hasFeature(Features.UpfrontShutdownScript)) {
|
||||||
|
@ -117,21 +118,18 @@ trait CommonHandlers {
|
||||||
upfrontShutdownScript
|
upfrontShutdownScript
|
||||||
} else {
|
} else {
|
||||||
log.info("ignoring pre-generated shutdown script, because option_upfront_shutdown_script is disabled")
|
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 =>
|
case None =>
|
||||||
// normal case: we don't pre-generate shutdown scripts
|
// 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]) = {
|
def startSimpleClose(commitments: Commitments, localShutdown: Shutdown, remoteShutdown: Shutdown, closingFeerates: Option[ClosingFeerates]): (DATA_NEGOTIATING_SIMPLE, Option[ClosingComplete]) = {
|
||||||
val localScript = localShutdown.scriptPubKey
|
val localScript = localShutdown.scriptPubKey
|
||||||
val remoteScript = remoteShutdown.scriptPubKey
|
val remoteScript = remoteShutdown.scriptPubKey
|
||||||
|
|
|
@ -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 = {
|
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(
|
makeChannelParams(
|
||||||
nodeParams, initFeatures,
|
nodeParams, initFeatures,
|
||||||
if (upfrontShutdownScript) Some(Script.write(Script.pay2wpkh(pubkey_opt.get))) else None,
|
if (upfrontShutdownScript) Some(Script.write(wallet.getReceivePubkeyScript())) else None,
|
||||||
if (channelType.paysDirectlyToWallet) Some(pubkey_opt.get) else None,
|
if (channelType.paysDirectlyToWallet) Some(wallet.getP2wpkhPubkey()) else None,
|
||||||
isChannelOpener = isChannelOpener,
|
isChannelOpener = isChannelOpener,
|
||||||
paysCommitTxFees = paysCommitTxFees,
|
paysCommitTxFees = paysCommitTxFees,
|
||||||
dualFunded = dualFunded,
|
dualFunded = dualFunded,
|
||||||
|
|
|
@ -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 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)
|
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 doubleSpent(tx: Transaction)(implicit ec: ExecutionContext): Future[Boolean] = Future.successful(false)
|
||||||
|
|
||||||
override def getP2wpkhPubkey(renew: Boolean): PublicKey = dummyReceivePubkey
|
override def getP2wpkhPubkey(renew: Boolean): PublicKey = dummyReceivePubkey
|
||||||
|
|
||||||
|
override def getReceivePubkeyScript(renew: Boolean): Seq[ScriptElt] = Script.pay2tr(dummyReceivePubkey.xOnly)
|
||||||
}
|
}
|
||||||
|
|
||||||
class NoOpOnChainWallet extends OnChainWallet with OnchainPubkeyCache {
|
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 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)
|
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 doubleSpent(tx: Transaction)(implicit ec: ExecutionContext): Future[Boolean] = Future.successful(doubleSpent.contains(tx.txid))
|
||||||
|
|
||||||
override def getP2wpkhPubkey(renew: Boolean): PublicKey = dummyReceivePubkey
|
override def getP2wpkhPubkey(renew: Boolean): PublicKey = dummyReceivePubkey
|
||||||
|
|
||||||
|
override def getReceivePubkeyScript(renew: Boolean): Seq[ScriptElt] = Script.pay2tr(dummyReceivePubkey.xOnly)
|
||||||
}
|
}
|
||||||
|
|
||||||
class SingleKeyOnChainWallet extends OnChainWallet with OnchainPubkeyCache {
|
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 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 {
|
override def getReceivePublicKeyScript(addressType: Option[AddressType] = None)(implicit ec: ExecutionContext): Future[Seq[ScriptElt]] = Future.successful(addressType match {
|
||||||
case Some(AddressType.P2tr) => Future.successful(pubkey.xOnly.pub.p2trAddress(fr.acinq.bitcoin.Block.RegtestGenesisBlock.hash))
|
case Some(AddressType.P2wpkh) => script84
|
||||||
case _ => Future.successful(Bech32.encodeWitnessAddress("bcrt", 0, pubkey.hash160.toArray))
|
case _ => script86
|
||||||
}
|
})
|
||||||
|
|
||||||
override def getP2wpkhPubkey()(implicit ec: ExecutionContext): Future[Crypto.PublicKey] = Future.successful(pubkey)
|
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 doubleSpent(tx: Transaction)(implicit ec: ExecutionContext): Future[Boolean] = Future.successful(doubleSpent.contains(tx.txid))
|
||||||
|
|
||||||
override def getP2wpkhPubkey(renew: Boolean): PublicKey = pubkey
|
override def getP2wpkhPubkey(renew: Boolean): PublicKey = pubkey
|
||||||
|
|
||||||
|
override def getReceivePubkeyScript(renew: Boolean): Seq[ScriptElt] = script86
|
||||||
}
|
}
|
||||||
|
|
||||||
object DummyOnChainWallet {
|
object DummyOnChainWallet {
|
||||||
|
|
||||||
val dummyReceiveAddress: String = "bcrt1qwcv8naajwn8fjhu8z59q9e6ucrqr068rlcenux"
|
|
||||||
val dummyReceivePubkey: PublicKey = PublicKey(hex"028feba10d0eafd0fad8fe20e6d9206e6bd30242826de05c63f459a00aced24b12")
|
val dummyReceivePubkey: PublicKey = PublicKey(hex"028feba10d0eafd0fad8fe20e6d9206e6bd30242826de05c63f459a00aced24b12")
|
||||||
|
|
||||||
def makeDummyFundingTx(pubkeyScript: ByteVector, amount: Satoshi): MakeFundingTxResponse = {
|
def makeDummyFundingTx(pubkeyScript: ByteVector, amount: Satoshi): MakeFundingTxResponse = {
|
||||||
|
|
|
@ -2,7 +2,7 @@ package fr.acinq.eclair.blockchain.bitcoind
|
||||||
|
|
||||||
import akka.actor.typed.scaladsl.adapter.ClassicActorSystemOps
|
import akka.actor.typed.scaladsl.adapter.ClassicActorSystemOps
|
||||||
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
|
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.OnChainAddressGenerator
|
||||||
import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient.AddressType
|
import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient.AddressType
|
||||||
import fr.acinq.eclair.{TestKitBaseClass, randomKey}
|
import fr.acinq.eclair.{TestKitBaseClass, randomKey}
|
||||||
|
@ -15,16 +15,24 @@ import scala.concurrent.{ExecutionContext, Future}
|
||||||
class OnchainPubkeyRefresherSpec extends TestKitBaseClass with AnyFunSuiteLike {
|
class OnchainPubkeyRefresherSpec extends TestKitBaseClass with AnyFunSuiteLike {
|
||||||
test("renew onchain scripts") {
|
test("renew onchain scripts") {
|
||||||
val finalPubkey = new AtomicReference[PublicKey](randomKey().publicKey)
|
val finalPubkey = new AtomicReference[PublicKey](randomKey().publicKey)
|
||||||
|
val finalPubkeyScript = new AtomicReference[Seq[ScriptElt]](Script.pay2tr(randomKey().xOnlyPublicKey()))
|
||||||
val generator = new OnChainAddressGenerator {
|
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)
|
override def getP2wpkhPubkey()(implicit ec: ExecutionContext): Future[Crypto.PublicKey] = Future.successful(randomKey().publicKey)
|
||||||
}
|
}
|
||||||
val manager = system.spawnAnonymous(OnchainPubkeyRefresher(generator, finalPubkey, 3 seconds))
|
val manager = system.spawnAnonymous(OnchainPubkeyRefresher(generator, finalPubkey, finalPubkeyScript, 3 seconds))
|
||||||
|
// renew pubkey explicitly
|
||||||
// renew script explicitly
|
|
||||||
val currentPubkey = finalPubkey.get()
|
val currentPubkey = finalPubkey.get()
|
||||||
manager ! OnchainPubkeyRefresher.Renew
|
manager ! OnchainPubkeyRefresher.Renew
|
||||||
awaitCond(finalPubkey.get() != currentPubkey)
|
awaitCond(finalPubkey.get() != currentPubkey)
|
||||||
|
|
||||||
|
// renew pubkey explicitly
|
||||||
|
val currentPubkeyScript = finalPubkeyScript.get()
|
||||||
|
manager ! OnchainPubkeyRefresher.RenewPubkeyScript
|
||||||
|
awaitCond(finalPubkeyScript.get() != currentPubkeyScript)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ import akka.pattern.pipe
|
||||||
import akka.testkit.{TestFSMRef, TestProbe}
|
import akka.testkit.{TestFSMRef, TestProbe}
|
||||||
import com.softwaremill.quicklens.ModifyPimp
|
import com.softwaremill.quicklens.ModifyPimp
|
||||||
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
|
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.NotificationsLogger.NotifyNodeOperator
|
||||||
import fr.acinq.eclair.blockchain.bitcoind.BitcoindService
|
import fr.acinq.eclair.blockchain.bitcoind.BitcoindService
|
||||||
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._
|
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._
|
||||||
|
@ -129,8 +129,14 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w
|
||||||
getP2wpkhPubkey().pipeTo(probe.ref)
|
getP2wpkhPubkey().pipeTo(probe.ref)
|
||||||
probe.expectMsgType[PublicKey]
|
probe.expectMsgType[PublicKey]
|
||||||
}
|
}
|
||||||
|
val pubkeyScript = {
|
||||||
|
getReceivePublicKeyScript(None).pipeTo(probe.ref)
|
||||||
|
probe.expectMsgType[Seq[ScriptElt]]
|
||||||
|
}
|
||||||
|
|
||||||
override def getP2wpkhPubkey(renew: Boolean): PublicKey = pubkey
|
override def getP2wpkhPubkey(renew: Boolean): PublicKey = pubkey
|
||||||
|
|
||||||
|
override def getReceivePubkeyScript(renew: Boolean): Seq[ScriptElt] = pubkeyScript
|
||||||
}
|
}
|
||||||
|
|
||||||
(walletRpcClient, walletClient)
|
(walletRpcClient, walletClient)
|
||||||
|
@ -1848,8 +1854,14 @@ class ReplaceableTxPublisherWithEclairSignerSpec extends ReplaceableTxPublisherS
|
||||||
getP2wpkhPubkey().pipeTo(probe.ref)
|
getP2wpkhPubkey().pipeTo(probe.ref)
|
||||||
probe.expectMsgType[PublicKey]
|
probe.expectMsgType[PublicKey]
|
||||||
}
|
}
|
||||||
|
lazy val pubkeyScript = {
|
||||||
|
getReceivePublicKeyScript(None).pipeTo(probe.ref)
|
||||||
|
probe.expectMsgType[Seq[ScriptElt]]
|
||||||
|
}
|
||||||
|
|
||||||
override def getP2wpkhPubkey(renew: Boolean): PublicKey = pubkey
|
override def getP2wpkhPubkey(renew: Boolean): PublicKey = pubkey
|
||||||
|
|
||||||
|
override def getReceivePubkeyScript(renew: Boolean): Seq[ScriptElt] = pubkeyScript
|
||||||
}
|
}
|
||||||
createEclairBackedWallet(walletRpcClient, keyManager)
|
createEclairBackedWallet(walletRpcClient, keyManager)
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ import akka.testkit.TestProbe
|
||||||
import com.typesafe.config.ConfigFactory
|
import com.typesafe.config.ConfigFactory
|
||||||
import fr.acinq.bitcoin.ScriptFlags
|
import fr.acinq.bitcoin.ScriptFlags
|
||||||
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
|
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.BitcoindService.BitcoinReq
|
||||||
import fr.acinq.eclair.blockchain.bitcoind.rpc.{BitcoinCoreClient, JsonRPCError}
|
import fr.acinq.eclair.blockchain.bitcoind.rpc.{BitcoinCoreClient, JsonRPCError}
|
||||||
import fr.acinq.eclair.channel._
|
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)))
|
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
|
val dataC = sender.expectMsgType[RES_GET_CHANNEL_DATA[DATA_NORMAL]].data
|
||||||
assert(dataC.commitments.params.commitmentFormat == commitmentFormat)
|
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)))
|
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
|
val dataF = sender.expectMsgType[RES_GET_CHANNEL_DATA[DATA_NORMAL]].data
|
||||||
assert(dataF.commitments.params.commitmentFormat == commitmentFormat)
|
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)
|
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
|
// 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.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]]
|
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
|
// we prepare the revoked transactions F will publish
|
||||||
val keyManagerF = nodes("F").nodeParams.channelKeyManager
|
val keyManagerF = nodes("F").nodeParams.channelKeyManager
|
||||||
val channelKeyPathF = keyManagerF.keyPath(commitmentsF.params.localParams, commitmentsF.params.channelConfig)
|
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)))
|
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 commitmentsC = sender.expectMsgType[RES_GET_CHANNEL_DATA[DATA_NORMAL]].data.commitments
|
||||||
val fundingOutpoint = commitmentsC.latest.commitInput.outPoint
|
val fundingOutpoint = commitmentsC.latest.commitInput.outPoint
|
||||||
val finalPubKeyScriptC = Script.write(Script.pay2wpkh(nodes("C").wallet.getP2wpkhPubkey(false)))
|
val finalPubKeyScriptC = Helpers.Closing.MutualClose.generateFinalScriptPubKey(nodes("C").wallet, true, false)
|
||||||
val finalPubKeyScriptF = Script.write(Script.pay2wpkh(nodes("F").wallet.getP2wpkhPubkey(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))
|
fundee.register ! Register.Forward(sender.ref.toTyped[Any], channelId, CMD_CLOSE(sender.ref, None, None))
|
||||||
sender.expectMsgType[RES_SUCCESS[CMD_CLOSE]]
|
sender.expectMsgType[RES_SUCCESS[CMD_CLOSE]]
|
||||||
|
|
Loading…
Add table
Reference in a new issue