-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Move socketBuilder to connect() This is only used when connecting, it doesn't need to be an argument of the `ElectrumClient`. * Refactor and document `linesFlow` TCP sockets with electrum servers require some non-trivial code to reconstruct individual messages from the bytes received on the socket. We process bytes in chunks, then reconstruct utf8 strings, and finally split those strings at newline characters into individual messages. We document that code, rename fields to make it easier to understand, and add unit tests. We remove it from the TCP socket abstraction, since this is specific to electrum connections. We also change the behavior in case the socket is closed while we have buffered a partial message: it doesn't make sense to emit it, as listeners won't be able to decode it. * Remove duplicate `connectionState` flow This is a pure 1:1 mapping from `connectionStatus`, there is no reason for that duplication. Clients can trivially migrate or `map` using the `toConnectionState` helper function. * Rework connection flow We clean up the coroutine hierarchy: the `ElectrumClient` has a single internal coroutine that is launched once the connection has been established with the electrum server. This coroutine launches three child coroutines and uses supervision to gracefully stop if the connection is closed or an error is received. Connection establishment happens in the context of the job that calls `connect()` and doesn't throw exceptions. * Handle electrum server errors On most RPC calls, we can gracefully handle server errors. Since we cannot trust the electrum server anyway, and they may lie to us by omission, this doesn't downgrade the security model. The previous behavior was to throw an exception, which was never properly handled and would just result in a crash of the wallet application. * Add timeout and retry to RPC calls We add an explicit timeout to RPC calls and a retry. If that retry also fails, two strategies are available: - handle the error and gracefully degrade (non-critical RPC calls) - disconnect and retry with new electrum server (critical RPCs such as subscriptions) The timeout can be updated by the application, for example when a slow network is detected or when Tor is activated.
- Loading branch information
Showing
15 changed files
with
577 additions
and
360 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
359 changes: 203 additions & 156 deletions
359
src/commonMain/kotlin/fr/acinq/lightning/blockchain/electrum/ElectrumClient.kt
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
42 changes: 28 additions & 14 deletions
42
src/commonMain/kotlin/fr/acinq/lightning/blockchain/electrum/IElectrumClient.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,35 +1,49 @@ | ||
package fr.acinq.lightning.blockchain.electrum | ||
|
||
import fr.acinq.bitcoin.BlockHeader | ||
import fr.acinq.bitcoin.ByteVector32 | ||
import fr.acinq.bitcoin.Transaction | ||
import fr.acinq.lightning.utils.Connection | ||
import kotlinx.coroutines.CompletableDeferred | ||
import fr.acinq.lightning.blockchain.fee.FeeratePerKw | ||
import kotlinx.coroutines.flow.Flow | ||
import kotlinx.coroutines.flow.StateFlow | ||
|
||
/** Note to implementers: methods exposed through this interface must *not* throw exceptions. */ | ||
interface IElectrumClient { | ||
val notifications: Flow<ElectrumSubscriptionResponse> | ||
val connectionStatus: StateFlow<ElectrumConnectionStatus> | ||
|
||
/** Return the transaction matching the txId provided, if it can be found. */ | ||
suspend fun getTx(txid: ByteVector32): Transaction? | ||
|
||
suspend fun send(request: ElectrumRequest, replyTo: CompletableDeferred<ElectrumResponse>) | ||
/** Return the block header at the given height, if it exists. */ | ||
suspend fun getHeader(blockHeight: Int): BlockHeader? | ||
|
||
suspend fun getTx(txid: ByteVector32): Transaction | ||
/** Return the block headers starting at the given height, if they exist (empty list otherwise). */ | ||
suspend fun getHeaders(startHeight: Int, count: Int): List<BlockHeader> | ||
|
||
suspend fun getMerkle(txid: ByteVector32, blockHeight: Int, contextOpt: Transaction? = null): GetMerkleResponse | ||
/** Return a merkle proof for the given transaction, if it can be found. */ | ||
suspend fun getMerkle(txid: ByteVector32, blockHeight: Int, contextOpt: Transaction? = null): GetMerkleResponse? | ||
|
||
/** Return the transaction history for a given script, or an empty list if the script is unknown. */ | ||
suspend fun getScriptHashHistory(scriptHash: ByteVector32): List<TransactionHistoryItem> | ||
|
||
/** Return the utxos matching a given script, or an empty list if the script is unknown. */ | ||
suspend fun getScriptHashUnspents(scriptHash: ByteVector32): List<UnspentItem> | ||
|
||
suspend fun startScriptHashSubscription(scriptHash: ByteVector32): ScriptHashSubscriptionResponse | ||
|
||
suspend fun startHeaderSubscription(): HeaderSubscriptionResponse | ||
/** | ||
* Try broadcasting a transaction: we cannot know whether the remote server really broadcast the transaction, | ||
* so we always consider it to be a success. The client should regularly retry transactions that don't confirm. | ||
*/ | ||
suspend fun broadcastTransaction(tx: Transaction): ByteVector32 | ||
|
||
suspend fun broadcastTransaction(tx: Transaction): BroadcastTransactionResponse | ||
/** Estimate the feerate required for a transaction to be confirmed in the next [confirmations] blocks. */ | ||
suspend fun estimateFees(confirmations: Int): FeeratePerKw? | ||
|
||
suspend fun estimateFees(confirmations: Int): EstimateFeeResponse | ||
/******************** Subscriptions ********************/ | ||
|
||
val notifications: Flow<ElectrumSubscriptionResponse> | ||
|
||
val connectionStatus: StateFlow<ElectrumConnectionStatus> | ||
/** Subscribe to changes to a given script. */ | ||
suspend fun startScriptHashSubscription(scriptHash: ByteVector32): ScriptHashSubscriptionResponse | ||
|
||
val connectionState: StateFlow<Connection> | ||
/** Subscribe to headers for new blocks found. */ | ||
suspend fun startHeaderSubscription(): HeaderSubscriptionResponse | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.