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:
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.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.
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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]]
|
||||
|
|
Loading…
Add table
Reference in a new issue