mirror of
https://github.com/ACINQ/eclair.git
synced 2025-03-12 19:01:39 +01:00
Add advanced api control methods (#3024)
Those are advanced, unsafe api methods useful for debugging only, and are left purposefully undocumented.
This commit is contained in:
parent
c7a288b91f
commit
4729876cac
7 changed files with 164 additions and 10 deletions
|
@ -24,11 +24,12 @@ import akka.pattern._
|
||||||
import akka.util.Timeout
|
import akka.util.Timeout
|
||||||
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.{BlockHash, ByteVector32, ByteVector64, Crypto, OutPoint, Satoshi, Script, TxId, addressToPublicKeyScript}
|
import fr.acinq.bitcoin.scalacompat.{BlockHash, ByteVector32, ByteVector64, Crypto, DeterministicWallet, OutPoint, Satoshi, Script, Transaction, TxId, addressToPublicKeyScript}
|
||||||
import fr.acinq.eclair.ApiTypes.ChannelNotFound
|
import fr.acinq.eclair.ApiTypes.ChannelNotFound
|
||||||
import fr.acinq.eclair.balance.CheckBalance.GlobalBalance
|
import fr.acinq.eclair.balance.CheckBalance.GlobalBalance
|
||||||
import fr.acinq.eclair.balance.{BalanceActor, ChannelsListener}
|
import fr.acinq.eclair.balance.{BalanceActor, ChannelsListener}
|
||||||
import fr.acinq.eclair.blockchain.OnChainWallet.OnChainBalance
|
import fr.acinq.eclair.blockchain.OnChainWallet.OnChainBalance
|
||||||
|
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.WatchFundingSpentTriggered
|
||||||
import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient
|
import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient
|
||||||
import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient.{Descriptors, WalletTx}
|
import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient.{Descriptors, WalletTx}
|
||||||
import fr.acinq.eclair.blockchain.fee.{ConfirmationTarget, FeeratePerByte, FeeratePerKw}
|
import fr.acinq.eclair.blockchain.fee.{ConfirmationTarget, FeeratePerByte, FeeratePerKw}
|
||||||
|
@ -67,6 +68,9 @@ case class VerifiedMessage(valid: Boolean, publicKey: PublicKey)
|
||||||
|
|
||||||
case class SendOnionMessageResponsePayload(tlvs: TlvStream[OnionMessagePayloadTlv])
|
case class SendOnionMessageResponsePayload(tlvs: TlvStream[OnionMessagePayloadTlv])
|
||||||
case class SendOnionMessageResponse(sent: Boolean, failureMessage: Option[String], response: Option[SendOnionMessageResponsePayload])
|
case class SendOnionMessageResponse(sent: Boolean, failureMessage: Option[String], response: Option[SendOnionMessageResponsePayload])
|
||||||
|
|
||||||
|
case class SpendFromChannelPrep(fundingTxIndex: Long, localFundingPubkey: PublicKey, inputAmount: Satoshi, unsignedTx: Transaction)
|
||||||
|
case class SpendFromChannelResult(signedTx: Transaction)
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
|
|
||||||
case class EnableFromFutureHtlcResponse(enabled: Boolean, failureMessage: Option[String])
|
case class EnableFromFutureHtlcResponse(enabled: Boolean, failureMessage: Option[String])
|
||||||
|
@ -102,6 +106,8 @@ trait Eclair {
|
||||||
|
|
||||||
def forceClose(channels: List[ApiTypes.ChannelIdentifier])(implicit timeout: Timeout): Future[Map[ApiTypes.ChannelIdentifier, Either[Throwable, CommandResponse[CMD_FORCECLOSE]]]]
|
def forceClose(channels: List[ApiTypes.ChannelIdentifier])(implicit timeout: Timeout): Future[Map[ApiTypes.ChannelIdentifier, Either[Throwable, CommandResponse[CMD_FORCECLOSE]]]]
|
||||||
|
|
||||||
|
def forceCloseResetFundingIndex(channel: ApiTypes.ChannelIdentifier, resetFundingTxIndex: Int)(implicit timeout: Timeout): Future[CommandResponse[CMD_FORCECLOSE]]
|
||||||
|
|
||||||
def bumpForceCloseFee(channels: List[ApiTypes.ChannelIdentifier], confirmationTarget: ConfirmationTarget)(implicit timeout: Timeout): Future[Map[ApiTypes.ChannelIdentifier, Either[Throwable, CommandResponse[CMD_BUMP_FORCE_CLOSE_FEE]]]]
|
def bumpForceCloseFee(channels: List[ApiTypes.ChannelIdentifier], confirmationTarget: ConfirmationTarget)(implicit timeout: Timeout): Future[Map[ApiTypes.ChannelIdentifier, Either[Throwable, CommandResponse[CMD_BUMP_FORCE_CLOSE_FEE]]]]
|
||||||
|
|
||||||
def updateRelayFee(nodes: List[PublicKey], feeBase: MilliSatoshi, feeProportionalMillionths: Long)(implicit timeout: Timeout): Future[Map[ApiTypes.ChannelIdentifier, Either[Throwable, CommandResponse[CMD_UPDATE_RELAY_FEE]]]]
|
def updateRelayFee(nodes: List[PublicKey], feeBase: MilliSatoshi, feeProportionalMillionths: Long)(implicit timeout: Timeout): Future[Map[ApiTypes.ChannelIdentifier, Either[Throwable, CommandResponse[CMD_UPDATE_RELAY_FEE]]]]
|
||||||
|
@ -174,6 +180,8 @@ trait Eclair {
|
||||||
|
|
||||||
def globalBalance()(implicit timeout: Timeout): Future[GlobalBalance]
|
def globalBalance()(implicit timeout: Timeout): Future[GlobalBalance]
|
||||||
|
|
||||||
|
def resetBalance()(implicit timeout: Timeout): Future[Option[GlobalBalance]]
|
||||||
|
|
||||||
def signMessage(message: ByteVector): SignedMessage
|
def signMessage(message: ByteVector): SignedMessage
|
||||||
|
|
||||||
def verifyMessage(message: ByteVector, recoverableSignature: ByteVector): VerifiedMessage
|
def verifyMessage(message: ByteVector, recoverableSignature: ByteVector): VerifiedMessage
|
||||||
|
@ -193,9 +201,15 @@ trait Eclair {
|
||||||
def enableFromFutureHtlc(): Future[EnableFromFutureHtlcResponse]
|
def enableFromFutureHtlc(): Future[EnableFromFutureHtlcResponse]
|
||||||
|
|
||||||
def stop(): Future[Unit]
|
def stop(): Future[Unit]
|
||||||
|
|
||||||
|
def manualWatchFundingSpent(channelId: ByteVector32, tx: Transaction): TxId
|
||||||
|
|
||||||
|
def spendFromChannelAddressPrep(outPoint: OutPoint, fundingKeyPath: DeterministicWallet.KeyPath, fundingTxIndex: Long, address: String, feerate: FeeratePerKw): Future[SpendFromChannelPrep]
|
||||||
|
|
||||||
|
def spendFromChannelAddress(fundingKeyPath: DeterministicWallet.KeyPath, fundingTxIndex: Long, remoteFundingPubkey: PublicKey, remoteSig: ByteVector64, unsignedTx: Transaction): Future[SpendFromChannelResult]
|
||||||
}
|
}
|
||||||
|
|
||||||
class EclairImpl(appKit: Kit) extends Eclair with Logging {
|
class EclairImpl(val appKit: Kit) extends Eclair with Logging with SpendFromChannelAddress {
|
||||||
|
|
||||||
implicit val ec: ExecutionContext = appKit.system.dispatcher
|
implicit val ec: ExecutionContext = appKit.system.dispatcher
|
||||||
implicit val scheduler: Scheduler = appKit.system.scheduler.toTyped
|
implicit val scheduler: Scheduler = appKit.system.scheduler.toTyped
|
||||||
|
@ -278,6 +292,10 @@ class EclairImpl(appKit: Kit) extends Eclair with Logging {
|
||||||
sendToChannels(channels, CMD_FORCECLOSE(ActorRef.noSender))
|
sendToChannels(channels, CMD_FORCECLOSE(ActorRef.noSender))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override def forceCloseResetFundingIndex(channel: ApiTypes.ChannelIdentifier, resetFundingTxIndex: Int)(implicit timeout: Timeout): Future[CommandResponse[CMD_FORCECLOSE]] = {
|
||||||
|
sendToChannel[CMD_FORCECLOSE, CommandResponse[CMD_FORCECLOSE]](channel, CMD_FORCECLOSE(ActorRef.noSender, resetFundingTxIndex_opt = Some(resetFundingTxIndex)))
|
||||||
|
}
|
||||||
|
|
||||||
override def bumpForceCloseFee(channels: List[ApiTypes.ChannelIdentifier], confirmationTarget: ConfirmationTarget)(implicit timeout: Timeout): Future[Map[ApiTypes.ChannelIdentifier, Either[Throwable, CommandResponse[CMD_BUMP_FORCE_CLOSE_FEE]]]] = {
|
override def bumpForceCloseFee(channels: List[ApiTypes.ChannelIdentifier], confirmationTarget: ConfirmationTarget)(implicit timeout: Timeout): Future[Map[ApiTypes.ChannelIdentifier, Either[Throwable, CommandResponse[CMD_BUMP_FORCE_CLOSE_FEE]]]] = {
|
||||||
sendToChannelsTyped(channels, cmdBuilder = CMD_BUMP_FORCE_CLOSE_FEE(_, confirmationTarget))
|
sendToChannelsTyped(channels, cmdBuilder = CMD_BUMP_FORCE_CLOSE_FEE(_, confirmationTarget))
|
||||||
}
|
}
|
||||||
|
@ -658,6 +676,10 @@ class EclairImpl(appKit: Kit) extends Eclair with Logging {
|
||||||
} yield globalBalance
|
} yield globalBalance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override def resetBalance()(implicit timeout: Timeout): Future[Option[GlobalBalance]] = {
|
||||||
|
appKit.balanceActor.ask(res => BalanceActor.ResetBalance(res))
|
||||||
|
}
|
||||||
|
|
||||||
override def signMessage(message: ByteVector): SignedMessage = {
|
override def signMessage(message: ByteVector): SignedMessage = {
|
||||||
val bytesToSign = SignedMessage.signedBytes(message)
|
val bytesToSign = SignedMessage.signedBytes(message)
|
||||||
val (signature, recoveryId) = appKit.nodeParams.nodeKeyManager.signDigest(bytesToSign)
|
val (signature, recoveryId) = appKit.nodeParams.nodeKeyManager.signDigest(bytesToSign)
|
||||||
|
@ -808,4 +830,10 @@ class EclairImpl(appKit: Kit) extends Eclair with Logging {
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
Future.successful(())
|
Future.successful(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override def manualWatchFundingSpent(channelId: ByteVector32, tx: Transaction): TxId = {
|
||||||
|
appKit.register ! Register.Forward(null, channelId, WatchFundingSpentTriggered(tx))
|
||||||
|
tx.txid
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
package fr.acinq.eclair
|
||||||
|
|
||||||
|
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
|
||||||
|
import fr.acinq.bitcoin.scalacompat.{ByteVector64, DeterministicWallet, OutPoint, Satoshi, SatoshiLong, Script, ScriptWitness, Transaction, TxIn, TxOut, addressToPublicKeyScript}
|
||||||
|
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
|
||||||
|
import fr.acinq.eclair.transactions.Scripts.multiSig2of2
|
||||||
|
import fr.acinq.eclair.transactions.{Scripts, Transactions}
|
||||||
|
import fr.acinq.eclair.transactions.Transactions.{DefaultCommitmentFormat, InputInfo, PlaceHolderPubKey, PlaceHolderSig, TxOwner}
|
||||||
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
|
import scala.concurrent.Future
|
||||||
|
|
||||||
|
trait SpendFromChannelAddress {
|
||||||
|
|
||||||
|
this: EclairImpl =>
|
||||||
|
|
||||||
|
/** these dummy witnesses are used as a placeholder to accurately compute the weight */
|
||||||
|
private val dummy2of2Witness = Scripts.witness2of2(PlaceHolderSig, PlaceHolderSig, PlaceHolderPubKey, PlaceHolderPubKey)
|
||||||
|
|
||||||
|
private def buildTx(outPoint: OutPoint, outputAmount: Satoshi, pubKeyScript: ByteVector, witness: ScriptWitness) = Transaction(2,
|
||||||
|
txIn = Seq(TxIn(outPoint, ByteVector.empty, 0, witness)),
|
||||||
|
txOut = Seq(TxOut(outputAmount, pubKeyScript)),
|
||||||
|
lockTime = 0)
|
||||||
|
|
||||||
|
override def spendFromChannelAddressPrep(outPoint: OutPoint, fundingKeyPath: DeterministicWallet.KeyPath, fundingTxIndex: Long, address: String, feerate: FeeratePerKw): Future[SpendFromChannelPrep] = {
|
||||||
|
for {
|
||||||
|
inputTx <- appKit.wallet.getTransaction(outPoint.txid)
|
||||||
|
inputAmount = inputTx.txOut(outPoint.index.toInt).amount
|
||||||
|
Right(pubKeyScript) = addressToPublicKeyScript(appKit.nodeParams.chainHash, address).map(Script.write)
|
||||||
|
// build the tx a first time with a zero amount to compute the weight
|
||||||
|
fee = Transactions.weight2fee(feerate, buildTx(outPoint, 0.sat, pubKeyScript, dummy2of2Witness).weight())
|
||||||
|
_ = assert(inputAmount - fee > Transactions.dustLimit(pubKeyScript), s"amount insufficient (fee=$fee)")
|
||||||
|
unsignedTx = buildTx(outPoint, inputAmount - fee, pubKeyScript, dummy2of2Witness)
|
||||||
|
// the following are not used, but need to be sent to the counterparty
|
||||||
|
localFundingPubkey = appKit.nodeParams.channelKeyManager.fundingPublicKey(fundingKeyPath, fundingTxIndex).publicKey
|
||||||
|
} yield SpendFromChannelPrep(fundingTxIndex, localFundingPubkey, inputAmount, unsignedTx)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def spendFromChannelAddress(fundingKeyPath: DeterministicWallet.KeyPath, fundingTxIndex: Long, remoteFundingPubkey: PublicKey, remoteSig: ByteVector64, unsignedTx: Transaction): Future[SpendFromChannelResult] = {
|
||||||
|
for {
|
||||||
|
_ <- Future.successful(())
|
||||||
|
outPoint = unsignedTx.txIn.head.outPoint
|
||||||
|
inputTx <- appKit.wallet.getTransaction(outPoint.txid)
|
||||||
|
localFundingPubkey = appKit.nodeParams.channelKeyManager.fundingPublicKey(fundingKeyPath, fundingTxIndex)
|
||||||
|
fundingRedeemScript = multiSig2of2(localFundingPubkey.publicKey, remoteFundingPubkey)
|
||||||
|
inputInfo = InputInfo(outPoint, inputTx.txOut(outPoint.index.toInt), fundingRedeemScript)
|
||||||
|
localSig = appKit.nodeParams.channelKeyManager.sign(
|
||||||
|
Transactions.SpliceTx(inputInfo, unsignedTx), // classify as splice, doesn't really matter
|
||||||
|
localFundingPubkey,
|
||||||
|
TxOwner.Local, // unused
|
||||||
|
DefaultCommitmentFormat // unused
|
||||||
|
)
|
||||||
|
witness = Scripts.witness2of2(localSig, remoteSig, localFundingPubkey.publicKey, remoteFundingPubkey)
|
||||||
|
signedTx = unsignedTx.updateWitness(0, witness)
|
||||||
|
} yield SpendFromChannelResult(signedTx)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ object BalanceActor {
|
||||||
|
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
sealed trait Command
|
sealed trait Command
|
||||||
|
final case class ResetBalance(replyTo: ActorRef[Option[GlobalBalance]]) extends Command
|
||||||
private final case object TickBalance extends Command
|
private final case object TickBalance extends Command
|
||||||
final case class GetGlobalBalance(replyTo: ActorRef[Try[GlobalBalance]], channels: Map[ByteVector32, PersistentChannelData]) extends Command
|
final case class GetGlobalBalance(replyTo: ActorRef[Try[GlobalBalance]], channels: Map[ByteVector32, PersistentChannelData]) extends Command
|
||||||
private final case class WrappedChannels(wrapped: ChannelsListener.GetChannelsResponse) extends Command
|
private final case class WrappedChannels(wrapped: ChannelsListener.GetChannelsResponse) extends Command
|
||||||
|
@ -52,6 +53,12 @@ private class BalanceActor(context: ActorContext[Command],
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
def apply(refBalance_opt: Option[GlobalBalance], previousBalance_opt: Option[GlobalBalance]): Behavior[Command] = Behaviors.receiveMessage {
|
def apply(refBalance_opt: Option[GlobalBalance], previousBalance_opt: Option[GlobalBalance]): Behavior[Command] = Behaviors.receiveMessage {
|
||||||
|
case ResetBalance(replyTo) =>
|
||||||
|
log.info("resetting balance")
|
||||||
|
// we use the last balance as new reference
|
||||||
|
val newRefBalance_opt = previousBalance_opt
|
||||||
|
replyTo ! previousBalance_opt
|
||||||
|
apply(refBalance_opt = newRefBalance_opt, previousBalance_opt = previousBalance_opt)
|
||||||
case TickBalance =>
|
case TickBalance =>
|
||||||
log.debug("checking balance...")
|
log.debug("checking balance...")
|
||||||
channelsListener ! ChannelsListener.GetChannels(context.messageAdapter[ChannelsListener.GetChannelsResponse](WrappedChannels))
|
channelsListener ! ChannelsListener.GetChannels(context.messageAdapter[ChannelsListener.GetChannelsResponse](WrappedChannels))
|
||||||
|
|
|
@ -227,7 +227,7 @@ final case class ClosingFeerates(preferred: FeeratePerKw, min: FeeratePerKw, max
|
||||||
|
|
||||||
sealed trait CloseCommand extends HasReplyToCommand
|
sealed trait CloseCommand extends HasReplyToCommand
|
||||||
final case class CMD_CLOSE(replyTo: ActorRef, scriptPubKey: Option[ByteVector], feerates: Option[ClosingFeerates]) extends CloseCommand with ForbiddenCommandDuringQuiescenceNegotiation with ForbiddenCommandWhenQuiescent
|
final case class CMD_CLOSE(replyTo: ActorRef, scriptPubKey: Option[ByteVector], feerates: Option[ClosingFeerates]) extends CloseCommand with ForbiddenCommandDuringQuiescenceNegotiation with ForbiddenCommandWhenQuiescent
|
||||||
final case class CMD_FORCECLOSE(replyTo: ActorRef) extends CloseCommand
|
final case class CMD_FORCECLOSE(replyTo: ActorRef, resetFundingTxIndex_opt: Option[Int] = None) extends CloseCommand
|
||||||
final case class CMD_BUMP_FORCE_CLOSE_FEE(replyTo: akka.actor.typed.ActorRef[CommandResponse[CMD_BUMP_FORCE_CLOSE_FEE]], confirmationTarget: ConfirmationTarget) extends Command
|
final case class CMD_BUMP_FORCE_CLOSE_FEE(replyTo: akka.actor.typed.ActorRef[CommandResponse[CMD_BUMP_FORCE_CLOSE_FEE]], confirmationTarget: ConfirmationTarget) extends Command
|
||||||
|
|
||||||
sealed trait ChannelFundingCommand extends Command {
|
sealed trait ChannelFundingCommand extends Command {
|
||||||
|
|
|
@ -20,6 +20,7 @@ import akka.actor.typed.scaladsl.Behaviors
|
||||||
import akka.actor.typed.scaladsl.adapter.{ClassicActorContextOps, actorRefAdapter}
|
import akka.actor.typed.scaladsl.adapter.{ClassicActorContextOps, actorRefAdapter}
|
||||||
import akka.actor.{Actor, ActorContext, ActorRef, FSM, OneForOneStrategy, PossiblyHarmful, Props, SupervisorStrategy, typed}
|
import akka.actor.{Actor, ActorContext, ActorRef, FSM, OneForOneStrategy, PossiblyHarmful, Props, SupervisorStrategy, typed}
|
||||||
import akka.event.Logging.MDC
|
import akka.event.Logging.MDC
|
||||||
|
import com.softwaremill.quicklens.ModifyPimp
|
||||||
import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey}
|
import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey}
|
||||||
import fr.acinq.bitcoin.scalacompat.{ByteVector32, Satoshi, SatoshiLong, Transaction, TxId}
|
import fr.acinq.bitcoin.scalacompat.{ByteVector32, Satoshi, SatoshiLong, Transaction, TxId}
|
||||||
import fr.acinq.eclair.Logs.LogCategory
|
import fr.acinq.eclair.Logs.LogCategory
|
||||||
|
@ -2626,12 +2627,31 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
|
||||||
|
|
||||||
case Event(c: CMD_FORCECLOSE, d) =>
|
case Event(c: CMD_FORCECLOSE, d) =>
|
||||||
d match {
|
d match {
|
||||||
case data: PersistentChannelData =>
|
case data: ChannelDataWithCommitments =>
|
||||||
val replyTo = if (c.replyTo == ActorRef.noSender) sender() else c.replyTo
|
val replyTo = if (c.replyTo == ActorRef.noSender) sender() else c.replyTo
|
||||||
replyTo ! RES_SUCCESS(c, data.channelId)
|
val failure = ForcedLocalCommit(d.channelId)
|
||||||
val failure = ForcedLocalCommit(data.channelId)
|
c.resetFundingTxIndex_opt match {
|
||||||
handleLocalError(failure, data, Some(c))
|
case Some(resetFundingTxIndex) =>
|
||||||
case _: TransientChannelData =>
|
val isActive = data.commitments.active.exists(_.fundingTxIndex == resetFundingTxIndex)
|
||||||
|
val nextFundingUnconfirmed = data.commitments.active.filter(_.fundingTxIndex > resetFundingTxIndex).forall(_.localFundingStatus.isInstanceOf[LocalFundingStatus.UnconfirmedFundingTx])
|
||||||
|
if (isActive && nextFundingUnconfirmed) {
|
||||||
|
// The commitment hasn't been deactivated yet and more recent funding transactions are unconfirmed, so
|
||||||
|
// we may try force-closing using this commitment index. Note however that if a more recent funding
|
||||||
|
// transaction confirms first, our closing attempt will permanently fail, we will have lost data about
|
||||||
|
// the latest confirmed funding transaction and may not be able to get our funds back. Use with extreme
|
||||||
|
// caution!
|
||||||
|
log.warning("force-closing with fundingTxIndex reset to {} (concurrent funding transactions: {})", resetFundingTxIndex, data.commitments.active.filter(_.fundingTxIndex > resetFundingTxIndex).map(_.fundingTxId).mkString(", "))
|
||||||
|
replyTo ! RES_SUCCESS(c, data.channelId)
|
||||||
|
val resetData = data.modify(_.commitments.active).using(_.filter(_.fundingTxIndex <= resetFundingTxIndex))
|
||||||
|
handleLocalError(failure, resetData, Some(c))
|
||||||
|
} else {
|
||||||
|
handleCommandError(CommandUnavailableInThisState(d.channelId, "forcecloseresetfundingindex", stateName), c)
|
||||||
|
}
|
||||||
|
case None =>
|
||||||
|
replyTo ! RES_SUCCESS(c, data.channelId)
|
||||||
|
handleLocalError(failure, data, Some(c))
|
||||||
|
}
|
||||||
|
case _ =>
|
||||||
handleCommandError(CommandUnavailableInThisState(d.channelId, "forceclose", stateName), c)
|
handleCommandError(CommandUnavailableInThisState(d.channelId, "forceclose", stateName), c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,13 @@
|
||||||
package fr.acinq.eclair.api.handlers
|
package fr.acinq.eclair.api.handlers
|
||||||
|
|
||||||
import akka.http.scaladsl.server.Route
|
import akka.http.scaladsl.server.Route
|
||||||
|
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
|
||||||
|
import fr.acinq.bitcoin.scalacompat.DeterministicWallet.KeyPath
|
||||||
|
import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, OutPoint, Transaction, TxId}
|
||||||
import fr.acinq.eclair.api.Service
|
import fr.acinq.eclair.api.Service
|
||||||
import fr.acinq.eclair.api.directives.EclairDirectives
|
import fr.acinq.eclair.api.directives.EclairDirectives
|
||||||
|
import fr.acinq.eclair.api.serde.FormParamExtractors._
|
||||||
|
import fr.acinq.eclair.blockchain.fee.{FeeratePerByte, FeeratePerKw}
|
||||||
|
|
||||||
trait Control {
|
trait Control {
|
||||||
this: Service with EclairDirectives =>
|
this: Service with EclairDirectives =>
|
||||||
|
@ -29,6 +34,40 @@ trait Control {
|
||||||
complete(eclairApi.enableFromFutureHtlc())
|
complete(eclairApi.enableFromFutureHtlc())
|
||||||
}
|
}
|
||||||
|
|
||||||
val controlRoutes: Route = enableFromFutureHtlc
|
val resetBalance: Route = postRequest("resetbalance") { implicit t =>
|
||||||
|
complete(eclairApi.resetBalance())
|
||||||
|
}
|
||||||
|
|
||||||
|
val forceCloseResetFundingIndex: Route = postRequest("forcecloseresetfundingindex") { implicit t =>
|
||||||
|
withChannelIdentifier { channel =>
|
||||||
|
formFields("resetFundingIndex".as[Int]) {
|
||||||
|
resetFundingIndex =>
|
||||||
|
complete(eclairApi.forceCloseResetFundingIndex(channel, resetFundingIndex))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val manualWatchFundingSpent: Route = postRequest("manualwatchfundingspent") { implicit t =>
|
||||||
|
formFields(channelIdFormParam, "tx") {
|
||||||
|
(channelId, tx) =>
|
||||||
|
complete(eclairApi.manualWatchFundingSpent(channelId, Transaction.read(tx)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val spendFromChannelAddressPrep: Route = postRequest("spendfromchanneladdressprep") { implicit t =>
|
||||||
|
formFields("t".as[ByteVector32], "o".as[Int], "kp", "fi".as[Int], "address", "f".as[FeeratePerByte]) {
|
||||||
|
(txId, outputIndex, keyPath, fundingTxIndex, address, feerate) =>
|
||||||
|
complete(eclairApi.spendFromChannelAddressPrep(OutPoint(TxId(txId), outputIndex), KeyPath(keyPath), fundingTxIndex, address, FeeratePerKw(feerate)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val spendFromChannelAddress: Route = postRequest("spendfromchanneladdress") { implicit t =>
|
||||||
|
formFields("kp", "fi".as[Int], "p".as[PublicKey], "s".as[ByteVector64], "tx") {
|
||||||
|
(keyPath, fundingTxIndex, remoteFundingPubkey, remoteSig, unsignedTx) =>
|
||||||
|
complete(eclairApi.spendFromChannelAddress(KeyPath(keyPath), fundingTxIndex, remoteFundingPubkey, remoteSig, Transaction.read(unsignedTx)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val controlRoutes: Route = enableFromFutureHtlc ~ resetBalance ~ forceCloseResetFundingIndex ~ manualWatchFundingSpent ~ spendFromChannelAddressPrep ~ spendFromChannelAddress
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ package fr.acinq.eclair.api.serde
|
||||||
import akka.http.scaladsl.unmarshalling.Unmarshaller
|
import akka.http.scaladsl.unmarshalling.Unmarshaller
|
||||||
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.{ByteVector32, OutPoint, Satoshi, TxId}
|
import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, OutPoint, Satoshi, TxId}
|
||||||
import fr.acinq.eclair.api.directives.RouteFormat
|
import fr.acinq.eclair.api.directives.RouteFormat
|
||||||
import fr.acinq.eclair.api.serde.JsonSupport._
|
import fr.acinq.eclair.api.serde.JsonSupport._
|
||||||
import fr.acinq.eclair.blockchain.fee.{ConfirmationPriority, FeeratePerByte}
|
import fr.acinq.eclair.blockchain.fee.{ConfirmationPriority, FeeratePerByte}
|
||||||
|
@ -45,6 +45,8 @@ object FormParamExtractors {
|
||||||
|
|
||||||
implicit val bytes32ListUnmarshaller: Unmarshaller[String, List[ByteVector32]] = listUnmarshaller(bin => ByteVector32.fromValidHex(bin))
|
implicit val bytes32ListUnmarshaller: Unmarshaller[String, List[ByteVector32]] = listUnmarshaller(bin => ByteVector32.fromValidHex(bin))
|
||||||
|
|
||||||
|
implicit val bytes64Unmarshaller: Unmarshaller[String, ByteVector64] = Unmarshaller.strict { bin => ByteVector64.fromValidHex(bin) }
|
||||||
|
|
||||||
implicit val bolt11Unmarshaller: Unmarshaller[String, Bolt11Invoice] = Unmarshaller.strict { rawRequest => Bolt11Invoice.fromString(rawRequest).get }
|
implicit val bolt11Unmarshaller: Unmarshaller[String, Bolt11Invoice] = Unmarshaller.strict { rawRequest => Bolt11Invoice.fromString(rawRequest).get }
|
||||||
|
|
||||||
implicit val shortChannelIdUnmarshaller: Unmarshaller[String, ShortChannelId] = Unmarshaller.strict { str => ShortChannelId.fromCoordinates(str).get }
|
implicit val shortChannelIdUnmarshaller: Unmarshaller[String, ShortChannelId] = Unmarshaller.strict { str => ShortChannelId.fromCoordinates(str).get }
|
||||||
|
|
Loading…
Add table
Reference in a new issue