diff --git a/src/commonMain/kotlin/fr/acinq/lightning/blockchain/electrum/ElectrumClient.kt b/src/commonMain/kotlin/fr/acinq/lightning/blockchain/electrum/ElectrumClient.kt index 31af75ba6..43e82a499 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/blockchain/electrum/ElectrumClient.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/blockchain/electrum/ElectrumClient.kt @@ -1,6 +1,9 @@ package fr.acinq.lightning.blockchain.electrum import fr.acinq.bitcoin.* +import fr.acinq.lightning.blockchain.fee.FeeratePerByte +import fr.acinq.lightning.blockchain.fee.FeeratePerKw +import fr.acinq.lightning.blockchain.fee.OnChainFeerates import fr.acinq.lightning.io.TcpSocket import fr.acinq.lightning.io.linesFlow import fr.acinq.lightning.io.send @@ -23,7 +26,7 @@ sealed interface ElectrumClientCommand { sealed interface ElectrumConnectionStatus { data class Closed(val reason: TcpSocket.IOException?) : ElectrumConnectionStatus object Connecting : ElectrumConnectionStatus - data class Connected(val version: ServerVersionResponse, val height: Int, val header: BlockHeader) : ElectrumConnectionStatus + data class Connected(val version: ServerVersionResponse, val height: Int, val header: BlockHeader, val onchainFeeRates: OnChainFeerates) : ElectrumConnectionStatus } @OptIn(ExperimentalCoroutinesApi::class) @@ -139,17 +142,36 @@ class ElectrumClient( } val flow = socket.linesFlow().map { json.decodeFromString(ElectrumResponseDeserializer, it) } + val version = ServerVersion() sendRequest(version, 0) val rpcFlow = flow.filterIsInstance>().map { it.value } val theirVersion = parseJsonResponse(version, rpcFlow.first()) require(theirVersion is ServerVersionResponse) { "invalid server version response $theirVersion" } logger.info { "server version $theirVersion" } + sendRequest(HeaderSubscription, 0) val header = parseJsonResponse(HeaderSubscription, rpcFlow.first()) require(header is HeaderSubscriptionResponse) { "invalid header subscription response $header" } + + suspend fun estimateFee(confirmations: Int): EstimateFeeResponse { + val request = EstimateFees(confirmations) + sendRequest(request, 0) + val response = parseJsonResponse(request, rpcFlow.first()) + require(response is EstimateFeeResponse) { "invalid estimatefee response $response" } + return response + } + + val fees = listOf(estimateFee(2), estimateFee(6), estimateFee(18), estimateFee(144)) + logger.info { "onchain fees $fees" } + val feeRates = OnChainFeerates( + fundingFeerate = fees[3].feerate ?: FeeratePerKw(FeeratePerByte(2.sat)), + mutualCloseFeerate = fees[2].feerate ?: FeeratePerKw(FeeratePerByte(10.sat)), + claimMainFeerate = fees[1].feerate ?: FeeratePerKw(FeeratePerByte(20.sat)), + fastFeerate = fees[0].feerate ?: FeeratePerKw(FeeratePerByte(50.sat)) + ) _notifications.emit(header) - _connectionStatus.value = ElectrumConnectionStatus.Connected(theirVersion, header.blockHeight, header.header) + _connectionStatus.value = ElectrumConnectionStatus.Connected(theirVersion, header.blockHeight, header.header, feeRates) logger.info { "server tip $header" } // pending requests map diff --git a/src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt b/src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt index ac58377af..355865217 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt @@ -195,11 +195,11 @@ class Peer( } } launch { - watcher.client.connectionState.filter { it == Connection.ESTABLISHED }.collect { - // onchain fees are retrieved punctually, when electrum status moves to Connection.ESTABLISHED - // since the application is not running most of the time, and when it is, it will be only for a few minutes, this is good enough. - // (for a node that is online most of the time things would be different and we would need to re-evaluate onchain fee estimates on a regular basis) - updateEstimateFees() + watcher.client.connectionStatus.filterIsInstance().collect { + // Onchain fees are retrieved once when we establish a connection to an electrum server. + // It is acceptable since the application will typically not be running more than a few minutes at a time. + // (for a node that is online most of the time things would be different, and we would need to re-evaluate onchain fee estimates on a regular basis) + onChainFeeratesFlow.value = it.onchainFeeRates } } launch { @@ -257,24 +257,6 @@ class Peer( } } - private suspend fun updateEstimateFees() { - watcher.client.connectionState.filter { it == Connection.ESTABLISHED }.first() - val sortedFees = listOf( - watcher.client.estimateFees(2), - watcher.client.estimateFees(6), - watcher.client.estimateFees(18), - watcher.client.estimateFees(144), - ) - logger.info { "on-chain fees: $sortedFees" } - // TODO: If some feerates are null, we may implement a retry - onChainFeeratesFlow.value = OnChainFeerates( - fundingFeerate = sortedFees[3].feerate ?: FeeratePerKw(FeeratePerByte(2.sat)), - mutualCloseFeerate = sortedFees[2].feerate ?: FeeratePerKw(FeeratePerByte(10.sat)), - claimMainFeerate = sortedFees[1].feerate ?: FeeratePerKw(FeeratePerByte(20.sat)), - fastFeerate = sortedFees[0].feerate ?: FeeratePerKw(FeeratePerByte(50.sat)) - ) - } - fun connect() { if (connectionState.value is Connection.CLOSED) establishConnection() else logger.warning { "Peer is already connecting / connected" }