Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set anchor output feerates when force-closing #1702

Merged
merged 5 commits into from
Feb 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ package fr.acinq.eclair.blockchain

import akka.actor.ActorRef
import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.bitcoin.{ByteVector32, Script, ScriptWitness, Transaction}
import fr.acinq.bitcoin.{ByteVector32, Satoshi, Script, ScriptWitness, Transaction}
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
import fr.acinq.eclair.channel.BitcoinEvent
import fr.acinq.eclair.transactions.Transactions.TransactionSigningKit
import fr.acinq.eclair.wire.ChannelAnnouncement
import scodec.bits.ByteVector

Expand Down Expand Up @@ -136,8 +138,16 @@ final case class WatchEventSpentBasic(event: BitcoinEvent) extends WatchEvent
// TODO: not implemented yet.
final case class WatchEventLost(event: BitcoinEvent) extends WatchEvent

/** Publish the provided tx as soon as possible depending on locktime and csv */
final case class PublishAsap(tx: Transaction)
sealed trait PublishStrategy
object PublishStrategy {
case object JustPublish extends PublishStrategy
case class SetFeerate(currentFeerate: FeeratePerKw, targetFeerate: FeeratePerKw, dustLimit: Satoshi, signingKit: TransactionSigningKit) extends PublishStrategy {
override def toString = s"SetFeerate(target=$targetFeerate)"
}
}

/** Publish the provided tx as soon as possible depending on lock time, csv and publishing strategy. */
final case class PublishAsap(tx: Transaction, strategy: PublishStrategy)

sealed trait UtxoStatus
object UtxoStatus {
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,20 @@ class ExtendedBitcoinClient(val rpcClient: BitcoinJsonRPCClient) {
}
}

def signTransaction(tx: Transaction, previousTxs: Seq[PreviousTx])(implicit ec: ExecutionContext): Future[SignTransactionResponse] = {
def signTransaction(tx: Transaction, previousTxs: Seq[PreviousTx], allowIncomplete: Boolean = false)(implicit ec: ExecutionContext): Future[SignTransactionResponse] = {
rpcClient.invoke("signrawtransactionwithwallet", tx.toString(), previousTxs).map(json => {
val JString(hex) = json \ "hex"
val JBool(complete) = json \ "complete"
if (!complete) {
val message = (json \ "errors" \\ classOf[JString]).mkString(",")
// TODO: remove allowIncomplete once /~https://github.com/bitcoin/bitcoin/issues/21151 is fixed
if (!complete && !allowIncomplete) {
val JArray(errors) = json \ "errors"
val message = errors.map(error => {
val JString(txid) = error \ "txid"
val JInt(vout) = error \ "vout"
val JString(scriptSig) = error \ "scriptSig"
val JString(message) = error \ "error"
s"txid=$txid vout=$vout scriptSig=$scriptSig error=$message"
}).mkString(", ")
throw JsonRPCError(Error(-1, message))
}
SignTransactionResponse(Transaction.read(hex), complete)
Expand Down Expand Up @@ -212,7 +220,7 @@ class ExtendedBitcoinClient(val rpcClient: BitcoinJsonRPCClient) {
val JDecimal(descendantFees) = json \ "fees" \ "descendant"
val JBool(replaceable) = json \ "bip125-replaceable"
// NB: bitcoind counts the transaction itself as its own ancestor and descendant, which is confusing: we fix that by decrementing these counters.
MempoolTx(vsize.toLong, weight.toLong, replaceable, toSatoshi(fees), ancestorCount.toInt - 1, toSatoshi(ancestorFees), descendantCount.toInt - 1, toSatoshi(descendantFees))
MempoolTx(txid, vsize.toLong, weight.toLong, replaceable, toSatoshi(fees), ancestorCount.toInt - 1, toSatoshi(ancestorFees), descendantCount.toInt - 1, toSatoshi(descendantFees))
})
}

Expand Down Expand Up @@ -276,6 +284,7 @@ object ExtendedBitcoinClient {
/**
* Information about a transaction currently in the mempool.
*
* @param txid transaction id.
* @param vsize virtual transaction size as defined in BIP 141.
* @param weight transaction weight as defined in BIP 141.
* @param replaceable Whether this transaction could be replaced with RBF (BIP125).
Expand All @@ -285,7 +294,7 @@ object ExtendedBitcoinClient {
* @param descendantCount number of unconfirmed child transactions.
* @param descendantFees transactions fees for the package consisting of this transaction and its unconfirmed children (without its unconfirmed parents).
*/
case class MempoolTx(vsize: Long, weight: Long, replaceable: Boolean, fees: Satoshi, ancestorCount: Int, ancestorFees: Satoshi, descendantCount: Int, descendantFees: Satoshi)
case class MempoolTx(txid: ByteVector32, vsize: Long, weight: Long, replaceable: Boolean, fees: Satoshi, ancestorCount: Int, ancestorFees: Satoshi, descendantCount: Int, descendantFees: Satoshi)

def toSatoshi(btcAmount: BigDecimal): Satoshi = Satoshi(btcAmount.bigDecimal.scaleByPowerOfTen(8).longValue)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ class ElectrumWatcher(blockCount: AtomicLong, client: ActorRef) extends Actor wi

case ElectrumClient.ServerError(ElectrumClient.GetTransaction(txid, Some(origin: ActorRef)), _) => origin ! GetTxWithMetaResponse(txid, None, tip.time)

case PublishAsap(tx) =>
case PublishAsap(tx, _) =>
val blockCount = this.blockCount.get()
val cltvTimeout = Scripts.cltvTimeout(tx)
val csvTimeouts = Scripts.csvTimeouts(tx)
Expand All @@ -180,7 +180,7 @@ class ElectrumWatcher(blockCount: AtomicLong, client: ActorRef) extends Actor wi
csvTimeouts.foreach { case (parentTxId, csvTimeout) =>
log.info(s"txid=${tx.txid} has a relative timeout of $csvTimeout blocks, watching parentTxId=$parentTxId tx={}", tx)
val parentPublicKeyScript = WatchConfirmed.extractPublicKeyScript(tx.txIn.find(_.outPoint.txid == parentTxId).get.witness)
self ! WatchConfirmed(self, parentTxId, parentPublicKeyScript, minDepth = csvTimeout, BITCOIN_PARENT_TX_CONFIRMED(tx))
self ! WatchConfirmed(self, parentTxId, parentPublicKeyScript, minDepth = csvTimeout, BITCOIN_PARENT_TX_CONFIRMED(PublishAsap(tx, PublishStrategy.JustPublish)))
}
} else if (cltvTimeout > blockCount) {
log.info(s"delaying publication of txid=${tx.txid} until block=$cltvTimeout (curblock=$blockCount)")
Expand All @@ -191,7 +191,7 @@ class ElectrumWatcher(blockCount: AtomicLong, client: ActorRef) extends Actor wi
context become running(height, tip, watches, scriptHashStatus, block2tx, sent :+ tx)
}

case WatchEventConfirmed(BITCOIN_PARENT_TX_CONFIRMED(tx), _, _, _) =>
case WatchEventConfirmed(BITCOIN_PARENT_TX_CONFIRMED(PublishAsap(tx, _)), _, _, _) =>
log.info(s"parent tx of txid=${tx.txid} has been confirmed")
val blockCount = this.blockCount.get()
val cltvTimeout = Scripts.cltvTimeout(tx)
Expand All @@ -214,8 +214,8 @@ class ElectrumWatcher(blockCount: AtomicLong, client: ActorRef) extends Actor wi

case ElectrumClient.ElectrumDisconnected =>
// we remember watches and keep track of tx that have not yet been published
// we also re-send the txes that we previously sent but hadn't yet received the confirmation
context become disconnected(watches, sent.map(PublishAsap), block2tx, Queue.empty)
// we also re-send the txs that we previously sent but hadn't yet received the confirmation
context become disconnected(watches, sent.map(tx => PublishAsap(tx, PublishStrategy.JustPublish)), block2tx, Queue.empty)
}

def publish(tx: Transaction): Unit = {
Expand Down
Loading