Skip to content

Commit

Permalink
Remove currentBlockHeight from extensions methods
Browse files Browse the repository at this point in the history
We can actually get it from the connection status, which simplifies the
way callers use those functions.
  • Loading branch information
t-bast committed Sep 6, 2023
1 parent ad86714 commit 75ab436
Show file tree
Hide file tree
Showing 4 changed files with 27 additions and 19 deletions.
Expand Up @@ -8,6 +8,7 @@ import fr.acinq.lightning.utils.*
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.Channel.Factory.RENDEZVOUS
import kotlinx.coroutines.flow.*
import kotlinx.serialization.json.Json
import org.kodein.log.LoggerFactory
Expand Down Expand Up @@ -57,7 +58,7 @@ class ElectrumClient(
// When connected, a dedicated coroutine will continuously read from the mailbox, send the corresponding requests to the
// electrum server, and send the response back when it is received.
// When disconnected, callers will suspend until we reconnect, at which point their messages will be processed.
private val mailbox = Channel<Action>()
private val mailbox = Channel<Action>(capacity = RENDEZVOUS)

data class ListenJob(val job: Job, val socket: TcpSocket) {
fun cancel() {
Expand All @@ -70,7 +71,7 @@ class ElectrumClient(

suspend fun connect(serverAddress: ServerAddress, socketBuilder: TcpSocket.Builder, timeout: Duration = 15.seconds): Boolean {
if (_connectionStatus.value is ElectrumConnectionStatus.Closed) {
listenJob?.cancel()
listenJob?.cancel() // the job should already be cancelled, this is for extra safety
val socket = openSocket(serverAddress, socketBuilder, timeout) ?: return false
logger.info { "connected to electrumx instance" }
return try {
Expand Down
Expand Up @@ -10,21 +10,27 @@ import fr.acinq.lightning.transactions.Transactions
import fr.acinq.lightning.utils.MDCLogger
import fr.acinq.lightning.utils.sat

suspend fun IElectrumClient.getConfirmations(txId: ByteVector32, currentBlockHeight: Int): Int? = getTx(txId)?.let { tx -> getConfirmations(tx, currentBlockHeight) }
suspend fun IElectrumClient.getConfirmations(txId: ByteVector32): Int? = getTx(txId)?.let { tx -> getConfirmations(tx) }

/**
* @return the number of confirmations, zero if the transaction is in the mempool, null if the transaction is not found
*/
suspend fun IElectrumClient.getConfirmations(tx: Transaction, currentBlockHeight: Int): Int? {
val scriptHash = ElectrumClient.computeScriptHash(tx.txOut.first().publicKeyScript)
val scriptHashHistory = getScriptHashHistory(scriptHash)
val item = scriptHashHistory.find { it.txid == tx.txid }
return item?.let { if (item.blockHeight > 0) currentBlockHeight - item.blockHeight + 1 else 0 }
suspend fun IElectrumClient.getConfirmations(tx: Transaction): Int? {
return when (val status = connectionStatus.value) {
is ElectrumConnectionStatus.Connected -> {
val currentBlockHeight = status.height
val scriptHash = ElectrumClient.computeScriptHash(tx.txOut.first().publicKeyScript)
val scriptHashHistory = getScriptHashHistory(scriptHash)
val item = scriptHashHistory.find { it.txid == tx.txid }
item?.let { if (item.blockHeight > 0) currentBlockHeight - item.blockHeight + 1 else 0 }
}
else -> null
}
}

suspend fun IElectrumClient.computeSpliceCpfpFeerate(commitments: Commitments, targetFeerate: FeeratePerKw, spliceWeight: Int, currentBlockHeight: Int, logger: MDCLogger): Pair<FeeratePerKw, Satoshi> {
suspend fun IElectrumClient.computeSpliceCpfpFeerate(commitments: Commitments, targetFeerate: FeeratePerKw, spliceWeight: Int, logger: MDCLogger): Pair<FeeratePerKw, Satoshi> {
val (parentsWeight, parentsFees) = commitments.all
.takeWhile { getConfirmations(it.fundingTxId, currentBlockHeight).let { confirmations -> confirmations == null || confirmations == 0 } } // we check for null in case the tx has been evicted
.takeWhile { getConfirmations(it.fundingTxId).let { confirmations -> confirmations == null || confirmations == 0 } } // we check for null in case the tx has been evicted
.fold(Pair(0, 0.sat)) { (parentsWeight, parentsFees), commitment ->
val weight = when (commitment.localFundingStatus) {
// weight will be underestimated if the transaction is not fully signed
Expand Down
9 changes: 3 additions & 6 deletions src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt
Expand Up @@ -411,13 +411,12 @@ class Peer(
* for a splice out, taking into account potential unconfirmed parent splices.
*/
suspend fun estimateFeeForSpliceOut(amount: Satoshi, scriptPubKey: ByteVector, targetFeerate: FeeratePerKw): Pair<FeeratePerKw, Satoshi>? {
val currentBlockHeight = currentTipFlow.filterNotNull().first().first
return channels.values
.filterIsInstance<Normal>()
.firstOrNull { it.commitments.availableBalanceForSend() > amount }
?.let { channel ->
val weight = FundingContributions.computeWeightPaid(isInitiator = true, commitment = channel.commitments.active.first(), walletInputs = emptyList(), localOutputs = listOf(TxOut(amount, scriptPubKey)))
watcher.client.computeSpliceCpfpFeerate(channel.commitments, targetFeerate, spliceWeight = weight, currentBlockHeight, logger)
watcher.client.computeSpliceCpfpFeerate(channel.commitments, targetFeerate, spliceWeight = weight, logger)
}
}

Expand All @@ -430,13 +429,12 @@ class Peer(
* should not be attempted.
*/
suspend fun estimateFeeForSpliceCpfp(channelId: ByteVector32, targetFeerate: FeeratePerKw): Pair<FeeratePerKw, Satoshi>? {
val currentBlockHeight = currentTipFlow.filterNotNull().first().first
return channels.values
.filterIsInstance<Normal>()
.find { it.channelId == channelId }
?.let { channel ->
val weight = FundingContributions.computeWeightPaid(isInitiator = true, commitment = channel.commitments.active.first(), walletInputs = emptyList(), localOutputs = emptyList())
watcher.client.computeSpliceCpfpFeerate(channel.commitments, targetFeerate, spliceWeight = weight, currentBlockHeight, logger)
watcher.client.computeSpliceCpfpFeerate(channel.commitments, targetFeerate, spliceWeight = weight, logger)
}
}

Expand Down Expand Up @@ -950,10 +948,9 @@ class Peer(
is RequestChannelOpen -> {
when (val channel = channels.values.firstOrNull { it is Normal }) {
is ChannelStateWithCommitments -> {
val currentBlockHeight = currentTipFlow.filterNotNull().first().first
val targetFeerate = swapInFeeratesFlow.filterNotNull().first()
val weight = FundingContributions.computeWeightPaid(isInitiator = true, commitment = channel.commitments.active.first(), walletInputs = cmd.walletInputs, localOutputs = emptyList())
val (feerate, fee) = watcher.client.computeSpliceCpfpFeerate(channel.commitments, targetFeerate, spliceWeight = weight, currentBlockHeight, logger)
val (feerate, fee) = watcher.client.computeSpliceCpfpFeerate(channel.commitments, targetFeerate, spliceWeight = weight, logger)

logger.info { "requesting splice-in using balance=${cmd.walletInputs.balance} feerate=$feerate fee=$fee" }

Expand Down
Expand Up @@ -203,9 +203,13 @@ class ElectrumClientTest : LightningTestSuite() {

@Test
fun `get tx confirmations`() = runTest { client ->
val blockHeight = 788_370
assertEquals(10, client.getConfirmations(ByteVector32("f1c290880b6fc9355e4f1b1b7d13b9a15babbe096adaf13d01f3a56def793fd5"), blockHeight))
assertNull(client.getConfirmations(ByteVector32("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), blockHeight))
val confirmedAt = 788_360
val currentBlockHeight = when (val status = client.connectionStatus.value) {
is ElectrumConnectionStatus.Connected -> status.height
else -> null
}!!
assertEquals(currentBlockHeight - confirmedAt, client.getConfirmations(ByteVector32("f1c290880b6fc9355e4f1b1b7d13b9a15babbe096adaf13d01f3a56def793fd5")))
assertNull(client.getConfirmations(ByteVector32("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")))
client.stop()
}

Expand Down

0 comments on commit 75ab436

Please sign in to comment.