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

Use typed TxId #2742

Merged
merged 3 commits into from
Nov 23, 2023
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
12 changes: 6 additions & 6 deletions eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import akka.pattern._
import akka.util.Timeout
import com.softwaremill.quicklens.ModifyPimp
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Crypto, OutPoint, Satoshi, Script, addressToPublicKeyScript}
import fr.acinq.bitcoin.scalacompat.{BlockHash, ByteVector32, ByteVector64, Crypto, OutPoint, Satoshi, Script, TxId, addressToPublicKeyScript}
import fr.acinq.eclair.ApiTypes.ChannelNotFound
import fr.acinq.eclair.balance.CheckBalance.GlobalBalance
import fr.acinq.eclair.balance.{BalanceActor, ChannelsListener}
Expand Down Expand Up @@ -58,7 +58,7 @@ import java.util.UUID
import scala.concurrent.duration._
import scala.concurrent.{ExecutionContext, Future, Promise}

case class GetInfoResponse(version: String, nodeId: PublicKey, alias: String, color: String, features: Features[Feature], chainHash: ByteVector32, network: String, blockHeight: Int, publicAddresses: Seq[NodeAddress], onionAddress: Option[NodeAddress], instanceId: String)
case class GetInfoResponse(version: String, nodeId: PublicKey, alias: String, color: String, features: Features[Feature], chainHash: BlockHash, network: String, blockHeight: Int, publicAddresses: Seq[NodeAddress], onionAddress: Option[NodeAddress], instanceId: String)

case class AuditResponse(sent: Seq[PaymentSent], received: Seq[PaymentReceived], relayed: Seq[PaymentRelayed])

Expand Down Expand Up @@ -131,9 +131,9 @@ trait Eclair {

def sentInfo(id: PaymentIdentifier)(implicit timeout: Timeout): Future[Seq[OutgoingPayment]]

def sendOnChain(address: String, amount: Satoshi, confirmationTargetOrFeerate: Either[Long, FeeratePerByte]): Future[ByteVector32]
def sendOnChain(address: String, amount: Satoshi, confirmationTargetOrFeerate: Either[Long, FeeratePerByte]): Future[TxId]

def cpfpBumpFees(targetFeeratePerByte: FeeratePerByte, outpoints: Set[OutPoint]): Future[ByteVector32]
def cpfpBumpFees(targetFeeratePerByte: FeeratePerByte, outpoints: Set[OutPoint]): Future[TxId]

def findRoute(targetNodeId: PublicKey, amount: MilliSatoshi, pathFindingExperimentName_opt: Option[String], extraEdges: Seq[Invoice.ExtraEdge] = Seq.empty, includeLocalChannelCost: Boolean = false, ignoreNodeIds: Seq[PublicKey] = Seq.empty, ignoreShortChannelIds: Seq[ShortChannelId] = Seq.empty, maxFee_opt: Option[MilliSatoshi] = None)(implicit timeout: Timeout): Future[RouteResponse]

Expand Down Expand Up @@ -357,7 +357,7 @@ class EclairImpl(appKit: Kit) extends Eclair with Logging {
}
}

override def sendOnChain(address: String, amount: Satoshi, confirmationTargetOrFeerate: Either[Long, FeeratePerByte]): Future[ByteVector32] = {
override def sendOnChain(address: String, amount: Satoshi, confirmationTargetOrFeerate: Either[Long, FeeratePerByte]): Future[TxId] = {
val feeRate = confirmationTargetOrFeerate match {
case Left(blocks) =>
if (blocks < 3) appKit.nodeParams.currentFeerates.fast
Expand All @@ -375,7 +375,7 @@ class EclairImpl(appKit: Kit) extends Eclair with Logging {
}
}

override def cpfpBumpFees(targetFeeratePerByte: FeeratePerByte, outpoints: Set[OutPoint]): Future[ByteVector32] = {
override def cpfpBumpFees(targetFeeratePerByte: FeeratePerByte, outpoints: Set[OutPoint]): Future[TxId] = {
appKit.wallet match {
case w: BitcoinCoreClient => w.cpfp(outpoints, FeeratePerKw(targetFeeratePerByte)).map(_.txid)
case _ => Future.failed(new IllegalArgumentException("this call is only available with a bitcoin core backend"))
Expand Down
10 changes: 5 additions & 5 deletions eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package fr.acinq.eclair

import com.typesafe.config.{Config, ConfigFactory, ConfigValueType}
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, Crypto, Satoshi}
import fr.acinq.bitcoin.scalacompat.{Block, BlockHash, Crypto, Satoshi}
import fr.acinq.eclair.Setup.Seeds
import fr.acinq.eclair.blockchain.fee._
import fr.acinq.eclair.channel.ChannelFlags
Expand Down Expand Up @@ -73,7 +73,7 @@ case class NodeParams(nodeKeyManager: NodeKeyManager,
autoReconnect: Boolean,
initialRandomReconnectDelay: FiniteDuration,
maxReconnectInterval: FiniteDuration,
chainHash: ByteVector32,
chainHash: BlockHash,
invoiceExpiry: FiniteDuration,
multiPartPaymentExpiry: FiniteDuration,
peerConnectionConf: PeerConnection.Conf,
Expand Down Expand Up @@ -184,16 +184,16 @@ object NodeParams extends Logging {
Seeds(nodeSeed, channelSeed)
}

private val chain2Hash: Map[String, ByteVector32] = Map(
private val chain2Hash: Map[String, BlockHash] = Map(
t-bast marked this conversation as resolved.
Show resolved Hide resolved
"regtest" -> Block.RegtestGenesisBlock.hash,
"testnet" -> Block.TestnetGenesisBlock.hash,
"signet" -> Block.SignetGenesisBlock.hash,
"mainnet" -> Block.LivenetGenesisBlock.hash
)

def hashFromChain(chain: String): ByteVector32 = chain2Hash.getOrElse(chain, throw new RuntimeException(s"invalid chain '$chain'"))
def hashFromChain(chain: String): BlockHash = chain2Hash.getOrElse(chain, throw new RuntimeException(s"invalid chain '$chain'"))

def chainFromHash(chainHash: ByteVector32): String = chain2Hash.map(_.swap).getOrElse(chainHash, throw new RuntimeException(s"invalid chainHash '$chainHash'"))
def chainFromHash(chainHash: BlockHash): String = chain2Hash.map(_.swap).getOrElse(chainHash, throw new RuntimeException(s"invalid chainHash '$chainHash'"))

def parseSocks5ProxyParams(config: Config): Option[Socks5ProxyParams] = {
if (config.getBoolean("socks5.enabled")) {
Expand Down
7 changes: 4 additions & 3 deletions eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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, ByteVector32, Satoshi}
import fr.acinq.bitcoin.scalacompat.{Block, BlockHash, BlockId, ByteVector32, Satoshi}
import fr.acinq.eclair.Setup.Seeds
import fr.acinq.eclair.balance.{BalanceActor, ChannelsListener}
import fr.acinq.eclair.blockchain._
Expand Down Expand Up @@ -134,7 +134,7 @@ class Setup(val datadir: File,
case "password" => BitcoinJsonRPCAuthMethod.UserPassword(config.getString("bitcoind.rpcuser"), config.getString("bitcoind.rpcpassword"))
}

case class BitcoinStatus(version: Int, chainHash: ByteVector32, initialBlockDownload: Boolean, verificationProgress: Double, blockCount: Long, headerCount: Long, unspentAddresses: List[String])
case class BitcoinStatus(version: Int, chainHash: BlockHash, initialBlockDownload: Boolean, verificationProgress: Double, blockCount: Long, headerCount: Long, unspentAddresses: List[String])

def getBitcoinStatus(bitcoinClient: BasicBitcoinJsonRPCClient): Future[BitcoinStatus] = for {
json <- bitcoinClient.invoke("getblockchaininfo").recover { case e => throw BitcoinRPCConnectionException(e) }
Expand All @@ -147,7 +147,8 @@ class Setup(val datadir: File,
ibd = (json \ "initialblockdownload").extract[Boolean]
blocks = (json \ "blocks").extract[Long]
headers = (json \ "headers").extract[Long]
chainHash <- bitcoinClient.invoke("getblockhash", 0).map(_.extract[String]).map(s => ByteVector32.fromValidHex(s)).map(_.reverse)
// NB: bitcoind confusingly returns the blockId instead of the blockHash.
chainHash <- bitcoinClient.invoke("getblockhash", 0).map(_.extract[String]).map(s => BlockId(ByteVector32.fromValidHex(s))).map(BlockHash(_))
bitcoinVersion <- bitcoinClient.invoke("getnetworkinfo").map(json => json \ "version").map(_.extract[Int])
unspentAddresses <- bitcoinClient.invoke("listunspent").recover { _ => if (wallet.isEmpty && wallets.length > 1) throw BitcoinDefaultWalletException(wallets) else throw BitcoinWalletNotLoadedException(wallet.getOrElse(""), wallets) }
.collect { case JArray(values) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package fr.acinq.eclair.balance
import akka.actor.typed.eventstream.EventStream
import akka.actor.typed.scaladsl.{ActorContext, Behaviors}
import akka.actor.typed.{ActorRef, Behavior}
import fr.acinq.bitcoin.scalacompat.{ByteVector32, SatoshiLong}
import fr.acinq.bitcoin.scalacompat.{ByteVector32, SatoshiLong, TxId}
import fr.acinq.eclair.NotificationsLogger
import fr.acinq.eclair.NotificationsLogger.NotifyNodeOperator
import fr.acinq.eclair.balance.BalanceActor._
Expand Down Expand Up @@ -40,11 +40,11 @@ object BalanceActor {
}
}

final case class UtxoInfo(utxos: Seq[Utxo], ancestorCount: Map[ByteVector32, Long])
final case class UtxoInfo(utxos: Seq[Utxo], ancestorCount: Map[TxId, Long])

def checkUtxos(bitcoinClient: BitcoinCoreClient)(implicit ec: ExecutionContext): Future[UtxoInfo] = {

def getUnconfirmedAncestorCount(utxo: Utxo): Future[(ByteVector32, Long)] = bitcoinClient.rpcClient.invoke("getmempoolentry", utxo.txid).map(json => {
def getUnconfirmedAncestorCount(utxo: Utxo): Future[(TxId, Long)] = bitcoinClient.rpcClient.invoke("getmempoolentry", utxo.txid).map(json => {
val JInt(ancestorCount) = json \ "ancestorcount"
(utxo.txid, ancestorCount.toLong)
}).recover {
Expand All @@ -55,7 +55,7 @@ object BalanceActor {
(utxo.txid, 0)
}

def getUnconfirmedAncestorCountMap(utxos: Seq[Utxo]): Future[Map[ByteVector32, Long]] = Future.sequence(utxos.filter(_.confirmations == 0).map(getUnconfirmedAncestorCount)).map(_.toMap)
def getUnconfirmedAncestorCountMap(utxos: Seq[Utxo]): Future[Map[TxId, Long]] = Future.sequence(utxos.filter(_.confirmations == 0).map(getUnconfirmedAncestorCount)).map(_.toMap)

for {
utxos <- bitcoinClient.listUnspent()
Expand Down Expand Up @@ -134,7 +134,7 @@ private class BalanceActor(context: ActorContext[Command],
Behaviors.same
case WrappedUtxoInfo(res) =>
res match {
case Success(UtxoInfo(utxos: Seq[Utxo], ancestorCount: Map[ByteVector32, Long])) =>
case Success(UtxoInfo(utxos, ancestorCount)) =>
val filteredByStatus: Map[String, Seq[Utxo]] = Map(
Monitoring.Tags.UtxoStatuses.Confirmed -> utxos.filter(utxo => utxo.confirmations > 0),
// We cannot create chains of unconfirmed transactions with more than 25 elements, so we ignore such utxos.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package fr.acinq.eclair.balance

import com.softwaremill.quicklens._
import fr.acinq.bitcoin.scalacompat.{Btc, ByteVector32, Satoshi, SatoshiLong, Script}
import fr.acinq.bitcoin.scalacompat.{Btc, ByteVector32, Satoshi, SatoshiLong, Script, TxId}
import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient
import fr.acinq.eclair.channel.Helpers.Closing
import fr.acinq.eclair.channel.Helpers.Closing.{CurrentRemoteClose, LocalClose, NextRemoteClose, RemoteClose}
Expand Down Expand Up @@ -51,11 +51,11 @@ object CheckBalance {
* That's why we keep track of the id of each transaction that pays us any amount. It allows us to double check from
* bitcoin core and remove any published transaction.
*/
case class PossiblyPublishedMainBalance(toLocal: Map[ByteVector32, Btc] = Map.empty) {
case class PossiblyPublishedMainBalance(toLocal: Map[TxId, Btc] = Map.empty) {
val total: Btc = toLocal.values.map(_.toSatoshi).sum
}

case class PossiblyPublishedMainAndHtlcBalance(toLocal: Map[ByteVector32, Btc] = Map.empty, htlcs: Map[ByteVector32, Btc] = Map.empty, htlcsUnpublished: Btc = 0.sat) {
case class PossiblyPublishedMainAndHtlcBalance(toLocal: Map[TxId, Btc] = Map.empty, htlcs: Map[TxId, Btc] = Map.empty, htlcsUnpublished: Btc = 0.sat) {
val totalToLocal: Btc = toLocal.values.map(_.toSatoshi).sum
val totalHtlcs: Btc = htlcs.values.map(_.toSatoshi).sum
val total: Btc = totalToLocal + totalHtlcs + htlcsUnpublished
Expand Down Expand Up @@ -153,7 +153,7 @@ object CheckBalance {
val finalScriptPubKey = Script.write(Script.pay2wpkh(c.params.localParams.walletStaticPaymentBasepoint.get))
Transactions.findPubKeyScriptIndex(remoteCommitPublished.commitTx, finalScriptPubKey) match {
case Right(outputIndex) => Map(remoteCommitPublished.commitTx.txid -> remoteCommitPublished.commitTx.txOut(outputIndex).amount.toBtc)
case _ => Map.empty[ByteVector32, Btc] // either we don't have an output (below dust), or we have used a non-default pubkey script
case _ => Map.empty[TxId, Btc] // either we don't have an output (below dust), or we have used a non-default pubkey script
}
} else {
remoteCommitPublished.claimMainOutputTx.toSeq.map(c => c.tx.txid -> c.tx.txOut.head.amount.toBtc).toMap
Expand Down Expand Up @@ -258,13 +258,13 @@ object CheckBalance {
*/
def prunePublishedTransactions(br: OffChainBalance, bitcoinClient: BitcoinCoreClient)(implicit ec: ExecutionContext): Future[OffChainBalance] = {
for {
txs: Iterable[Option[(ByteVector32, Int)]] <- Future.sequence((br.closing.localCloseBalance.toLocal.keys ++
txs: Iterable[Option[(TxId, Int)]] <- Future.sequence((br.closing.localCloseBalance.toLocal.keys ++
br.closing.localCloseBalance.htlcs.keys ++
br.closing.remoteCloseBalance.toLocal.keys ++
br.closing.remoteCloseBalance.htlcs.keys ++
br.closing.mutualCloseBalance.toLocal.keys)
.map(txid => bitcoinClient.getTxConfirmations(txid).map(_ map { confirmations => txid -> confirmations })))
txMap: Map[ByteVector32, Int] = txs.flatten.toMap
txMap: Map[TxId, Int] = txs.flatten.toMap
} yield {
br
.modifyAll(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

package fr.acinq.eclair.blockchain

import fr.acinq.bitcoin.scalacompat.{ByteVector32, Transaction}
import fr.acinq.bitcoin.scalacompat.{BlockId, Transaction}
import fr.acinq.eclair.BlockHeight
import fr.acinq.eclair.blockchain.fee.FeeratesPerKw

Expand All @@ -26,7 +26,7 @@ import fr.acinq.eclair.blockchain.fee.FeeratesPerKw

sealed trait BlockchainEvent

case class NewBlock(blockHash: ByteVector32) extends BlockchainEvent
case class NewBlock(blockId: BlockId) extends BlockchainEvent

case class NewTransaction(tx: Transaction) extends BlockchainEvent

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.{ByteVector32, OutPoint, Satoshi, Transaction}
import fr.acinq.bitcoin.scalacompat.{OutPoint, Satoshi, Transaction, TxId}
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
import scodec.bits.ByteVector

Expand Down Expand Up @@ -52,7 +52,7 @@ trait OnChainChannelFunder {
* Publish a transaction on the bitcoin network.
* This method must be idempotent: if the tx was already published, it must return a success.
*/
def publishTransaction(tx: Transaction)(implicit ec: ExecutionContext): Future[ByteVector32]
def publishTransaction(tx: Transaction)(implicit ec: ExecutionContext): Future[TxId]

/** Create a fully signed channel funding transaction with the provided pubkeyScript. */
def makeFundingTx(pubkeyScript: ByteVector, amount: Satoshi, feeRate: FeeratePerKw)(implicit ec: ExecutionContext): Future[MakeFundingTxResponse]
Expand All @@ -70,10 +70,10 @@ trait OnChainChannelFunder {
def commit(tx: Transaction)(implicit ec: ExecutionContext): Future[Boolean]

/** Return the transaction if it exists, either in the blockchain or in the mempool. */
def getTransaction(txId: ByteVector32)(implicit ec: ExecutionContext): Future[Transaction]
def getTransaction(txId: TxId)(implicit ec: ExecutionContext): Future[Transaction]

/** Get the number of confirmations of a given transaction. */
def getTxConfirmations(txid: ByteVector32)(implicit ec: ExecutionContext): Future[Option[Int]]
def getTxConfirmations(txId: TxId)(implicit ec: ExecutionContext): Future[Option[Int]]

/** Rollback a transaction that we failed to commit: this probably translates to "release locks on utxos". */
def rollback(tx: Transaction)(implicit ec: ExecutionContext): Future[Boolean]
Expand Down Expand Up @@ -139,9 +139,9 @@ object OnChainWallet {

/** Transaction with all available witnesses. */
val partiallySignedTx: Transaction = {
var tx = psbt.getGlobal.getTx
for (i <- 0 until psbt.getInputs.size()) {
Option(psbt.getInputs.get(i).getScriptWitness).foreach { witness =>
var tx = psbt.global.tx
for (i <- 0 until psbt.inputs.size()) {
Option(psbt.inputs.get(i).getScriptWitness).foreach { witness =>
tx = tx.updateWitness(i, witness)
}
}
Expand Down
Loading