Skip to content

Commit e73c1cf

Browse files
authored
Use typed TxId (#2742)
And explicitly encode/decode as a `tx_hash` for lightning messages.
1 parent e20b736 commit e73c1cf

File tree

113 files changed

+757
-705
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

113 files changed

+757
-705
lines changed

eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import akka.pattern._
2424
import akka.util.Timeout
2525
import com.softwaremill.quicklens.ModifyPimp
2626
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
27-
import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Crypto, OutPoint, Satoshi, Script, addressToPublicKeyScript}
27+
import fr.acinq.bitcoin.scalacompat.{BlockHash, ByteVector32, ByteVector64, Crypto, OutPoint, Satoshi, Script, TxId, addressToPublicKeyScript}
2828
import fr.acinq.eclair.ApiTypes.ChannelNotFound
2929
import fr.acinq.eclair.balance.CheckBalance.GlobalBalance
3030
import fr.acinq.eclair.balance.{BalanceActor, ChannelsListener}
@@ -58,7 +58,7 @@ import java.util.UUID
5858
import scala.concurrent.duration._
5959
import scala.concurrent.{ExecutionContext, Future, Promise}
6060

61-
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)
61+
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)
6262

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

@@ -131,9 +131,9 @@ trait Eclair {
131131

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

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

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

138138
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]
139139

@@ -357,7 +357,7 @@ class EclairImpl(appKit: Kit) extends Eclair with Logging {
357357
}
358358
}
359359

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

378-
override def cpfpBumpFees(targetFeeratePerByte: FeeratePerByte, outpoints: Set[OutPoint]): Future[ByteVector32] = {
378+
override def cpfpBumpFees(targetFeeratePerByte: FeeratePerByte, outpoints: Set[OutPoint]): Future[TxId] = {
379379
appKit.wallet match {
380380
case w: BitcoinCoreClient => w.cpfp(outpoints, FeeratePerKw(targetFeeratePerByte)).map(_.txid)
381381
case _ => Future.failed(new IllegalArgumentException("this call is only available with a bitcoin core backend"))

eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ package fr.acinq.eclair
1818

1919
import com.typesafe.config.{Config, ConfigFactory, ConfigValueType}
2020
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
21-
import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, Crypto, Satoshi}
21+
import fr.acinq.bitcoin.scalacompat.{Block, BlockHash, Crypto, Satoshi}
2222
import fr.acinq.eclair.Setup.Seeds
2323
import fr.acinq.eclair.blockchain.fee._
2424
import fr.acinq.eclair.channel.ChannelFlags
@@ -73,7 +73,7 @@ case class NodeParams(nodeKeyManager: NodeKeyManager,
7373
autoReconnect: Boolean,
7474
initialRandomReconnectDelay: FiniteDuration,
7575
maxReconnectInterval: FiniteDuration,
76-
chainHash: ByteVector32,
76+
chainHash: BlockHash,
7777
invoiceExpiry: FiniteDuration,
7878
multiPartPaymentExpiry: FiniteDuration,
7979
peerConnectionConf: PeerConnection.Conf,
@@ -184,16 +184,16 @@ object NodeParams extends Logging {
184184
Seeds(nodeSeed, channelSeed)
185185
}
186186

187-
private val chain2Hash: Map[String, ByteVector32] = Map(
187+
private val chain2Hash: Map[String, BlockHash] = Map(
188188
"regtest" -> Block.RegtestGenesisBlock.hash,
189189
"testnet" -> Block.TestnetGenesisBlock.hash,
190190
"signet" -> Block.SignetGenesisBlock.hash,
191191
"mainnet" -> Block.LivenetGenesisBlock.hash
192192
)
193193

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

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

198198
def parseSocks5ProxyParams(config: Config): Option[Socks5ProxyParams] = {
199199
if (config.getBoolean("socks5.enabled")) {

eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import akka.actor.{ActorRef, ActorSystem, Props, SupervisorStrategy, typed}
2323
import akka.pattern.after
2424
import akka.util.Timeout
2525
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
26-
import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, Satoshi}
26+
import fr.acinq.bitcoin.scalacompat.{Block, BlockHash, BlockId, ByteVector32, Satoshi}
2727
import fr.acinq.eclair.Setup.Seeds
2828
import fr.acinq.eclair.balance.{BalanceActor, ChannelsListener}
2929
import fr.acinq.eclair.blockchain._
@@ -134,7 +134,7 @@ class Setup(val datadir: File,
134134
case "password" => BitcoinJsonRPCAuthMethod.UserPassword(config.getString("bitcoind.rpcuser"), config.getString("bitcoind.rpcpassword"))
135135
}
136136

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

139139
def getBitcoinStatus(bitcoinClient: BasicBitcoinJsonRPCClient): Future[BitcoinStatus] = for {
140140
json <- bitcoinClient.invoke("getblockchaininfo").recover { case e => throw BitcoinRPCConnectionException(e) }
@@ -147,7 +147,8 @@ class Setup(val datadir: File,
147147
ibd = (json \ "initialblockdownload").extract[Boolean]
148148
blocks = (json \ "blocks").extract[Long]
149149
headers = (json \ "headers").extract[Long]
150-
chainHash <- bitcoinClient.invoke("getblockhash", 0).map(_.extract[String]).map(s => ByteVector32.fromValidHex(s)).map(_.reverse)
150+
// NB: bitcoind confusingly returns the blockId instead of the blockHash.
151+
chainHash <- bitcoinClient.invoke("getblockhash", 0).map(_.extract[String]).map(s => BlockId(ByteVector32.fromValidHex(s))).map(BlockHash(_))
151152
bitcoinVersion <- bitcoinClient.invoke("getnetworkinfo").map(json => json \ "version").map(_.extract[Int])
152153
unspentAddresses <- bitcoinClient.invoke("listunspent").recover { _ => if (wallet.isEmpty && wallets.length > 1) throw BitcoinDefaultWalletException(wallets) else throw BitcoinWalletNotLoadedException(wallet.getOrElse(""), wallets) }
153154
.collect { case JArray(values) =>

eclair-core/src/main/scala/fr/acinq/eclair/balance/BalanceActor.scala

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package fr.acinq.eclair.balance
33
import akka.actor.typed.eventstream.EventStream
44
import akka.actor.typed.scaladsl.{ActorContext, Behaviors}
55
import akka.actor.typed.{ActorRef, Behavior}
6-
import fr.acinq.bitcoin.scalacompat.{ByteVector32, SatoshiLong}
6+
import fr.acinq.bitcoin.scalacompat.{ByteVector32, SatoshiLong, TxId}
77
import fr.acinq.eclair.NotificationsLogger
88
import fr.acinq.eclair.NotificationsLogger.NotifyNodeOperator
99
import fr.acinq.eclair.balance.BalanceActor._
@@ -40,11 +40,11 @@ object BalanceActor {
4040
}
4141
}
4242

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

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

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

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

6060
for {
6161
utxos <- bitcoinClient.listUnspent()
@@ -134,7 +134,7 @@ private class BalanceActor(context: ActorContext[Command],
134134
Behaviors.same
135135
case WrappedUtxoInfo(res) =>
136136
res match {
137-
case Success(UtxoInfo(utxos: Seq[Utxo], ancestorCount: Map[ByteVector32, Long])) =>
137+
case Success(UtxoInfo(utxos, ancestorCount)) =>
138138
val filteredByStatus: Map[String, Seq[Utxo]] = Map(
139139
Monitoring.Tags.UtxoStatuses.Confirmed -> utxos.filter(utxo => utxo.confirmations > 0),
140140
// We cannot create chains of unconfirmed transactions with more than 25 elements, so we ignore such utxos.

eclair-core/src/main/scala/fr/acinq/eclair/balance/CheckBalance.scala

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package fr.acinq.eclair.balance
22

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

58-
case class PossiblyPublishedMainAndHtlcBalance(toLocal: Map[ByteVector32, Btc] = Map.empty, htlcs: Map[ByteVector32, Btc] = Map.empty, htlcsUnpublished: Btc = 0.sat) {
58+
case class PossiblyPublishedMainAndHtlcBalance(toLocal: Map[TxId, Btc] = Map.empty, htlcs: Map[TxId, Btc] = Map.empty, htlcsUnpublished: Btc = 0.sat) {
5959
val totalToLocal: Btc = toLocal.values.map(_.toSatoshi).sum
6060
val totalHtlcs: Btc = htlcs.values.map(_.toSatoshi).sum
6161
val total: Btc = totalToLocal + totalHtlcs + htlcsUnpublished
@@ -153,7 +153,7 @@ object CheckBalance {
153153
val finalScriptPubKey = Script.write(Script.pay2wpkh(c.params.localParams.walletStaticPaymentBasepoint.get))
154154
Transactions.findPubKeyScriptIndex(remoteCommitPublished.commitTx, finalScriptPubKey) match {
155155
case Right(outputIndex) => Map(remoteCommitPublished.commitTx.txid -> remoteCommitPublished.commitTx.txOut(outputIndex).amount.toBtc)
156-
case _ => Map.empty[ByteVector32, Btc] // either we don't have an output (below dust), or we have used a non-default pubkey script
156+
case _ => Map.empty[TxId, Btc] // either we don't have an output (below dust), or we have used a non-default pubkey script
157157
}
158158
} else {
159159
remoteCommitPublished.claimMainOutputTx.toSeq.map(c => c.tx.txid -> c.tx.txOut.head.amount.toBtc).toMap
@@ -258,13 +258,13 @@ object CheckBalance {
258258
*/
259259
def prunePublishedTransactions(br: OffChainBalance, bitcoinClient: BitcoinCoreClient)(implicit ec: ExecutionContext): Future[OffChainBalance] = {
260260
for {
261-
txs: Iterable[Option[(ByteVector32, Int)]] <- Future.sequence((br.closing.localCloseBalance.toLocal.keys ++
261+
txs: Iterable[Option[(TxId, Int)]] <- Future.sequence((br.closing.localCloseBalance.toLocal.keys ++
262262
br.closing.localCloseBalance.htlcs.keys ++
263263
br.closing.remoteCloseBalance.toLocal.keys ++
264264
br.closing.remoteCloseBalance.htlcs.keys ++
265265
br.closing.mutualCloseBalance.toLocal.keys)
266266
.map(txid => bitcoinClient.getTxConfirmations(txid).map(_ map { confirmations => txid -> confirmations })))
267-
txMap: Map[ByteVector32, Int] = txs.flatten.toMap
267+
txMap: Map[TxId, Int] = txs.flatten.toMap
268268
} yield {
269269
br
270270
.modifyAll(

eclair-core/src/main/scala/fr/acinq/eclair/blockchain/BlockchainEvents.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
package fr.acinq.eclair.blockchain
1818

19-
import fr.acinq.bitcoin.scalacompat.{ByteVector32, Transaction}
19+
import fr.acinq.bitcoin.scalacompat.{BlockId, Transaction}
2020
import fr.acinq.eclair.BlockHeight
2121
import fr.acinq.eclair.blockchain.fee.FeeratesPerKw
2222

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

2727
sealed trait BlockchainEvent
2828

29-
case class NewBlock(blockHash: ByteVector32) extends BlockchainEvent
29+
case class NewBlock(blockId: BlockId) extends BlockchainEvent
3030

3131
case class NewTransaction(tx: Transaction) extends BlockchainEvent
3232

eclair-core/src/main/scala/fr/acinq/eclair/blockchain/OnChainWallet.scala

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ package fr.acinq.eclair.blockchain
1818

1919
import fr.acinq.bitcoin.psbt.Psbt
2020
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
21-
import fr.acinq.bitcoin.scalacompat.{ByteVector32, OutPoint, Satoshi, Transaction}
21+
import fr.acinq.bitcoin.scalacompat.{OutPoint, Satoshi, Transaction, TxId}
2222
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
2323
import scodec.bits.ByteVector
2424

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

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

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

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

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

140140
/** Transaction with all available witnesses. */
141141
val partiallySignedTx: Transaction = {
142-
var tx = psbt.getGlobal.getTx
143-
for (i <- 0 until psbt.getInputs.size()) {
144-
Option(psbt.getInputs.get(i).getScriptWitness).foreach { witness =>
142+
var tx = psbt.global.tx
143+
for (i <- 0 until psbt.inputs.size()) {
144+
Option(psbt.inputs.get(i).getScriptWitness).foreach { witness =>
145145
tx = tx.updateWitness(i, witness)
146146
}
147147
}

0 commit comments

Comments
 (0)