-
Notifications
You must be signed in to change notification settings - Fork 311
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge #461: Restructure electrum/esplora sync logic
9c57708 Make stop_gap a parameter to EsploraBlockchainConfig::new (LLFourn) 0f0a01a s/vin/vout/ (LLFourn) 1a64fd9 Delete src/blockchain/utils.rs (LLFourn) d3779fa Fix comments (LLFourn) d394011 Less intermediary data states in sync (LLFourn) dfb63d3 s/observed_txs/finished_txs/g (LLFourn) 188d9a4 Make variable names consistent (LLFourn) 5eadf5c Add some logging to script_sync (LLFourn) aaad560 Always get up to chunk_size heights to request headers for (LLFourn) e7c1357 Don't request conftime during tx request (LLFourn) 808d7d8 Update changelog (LLFourn) 732166f Fix feerate calculation for esplora (LLFourn) 3f5cb69 Invert dependencies in electrum sync (LLFourn) Pull request description: ## Description This PR does dependency inversion on the previous sync logic for electrum and esplora captured in the trait `ElectrumLikeSync`. This means that the sync logic does not reference the blockchain at all. Instead the blockchain asks the sync logic (in `script_sync.rs`) what it needs to continue the sync and tries to retrieve it. The initial purpose of doing this is to remove invocations of `maybe_await` in the abstract sync logic in preparation for completely removing `maybe_await` in the future. The other major benefit is it gives a lot more freedom for the esplora logic to use the rich data from the responses to complete the sync with less HTTP requests than it did previously. ## List of changes - sync logic moved to `script_sync.rs` and `ElectrumLikeSync` is gone. - esplora makes one http request per sync address. This means it makes half the number of http requests for a fully synced wallet and N*M less requests for a wallet which has N new transactions with M unique input transactions. - electrum and esplora save less raw transactions in the database. Electrum still requests input transactions for each of its transactions to calculate the fee but it does not save them to the database anymore. - The ureq and reqwest blockchain configuration is now unified into the same struct. This is the only API change. `read_timeout` and `write_timeout` have been removed in favor of a single `timeout` option which is set in both ureq and reqwest. - ureq now does concurrent (parallel) requests using threads. - An previously unnoticed bug has been fixed where by sending a lot of double spending transactions to the same address you could trick a bdk Esplora wallet into thinking it had a lot of unconfirmed coins. This is because esplora doesn't delete double spent transactions from its indexes immediately (not sure if this is a bug or a feature). A blockchain test is added for this. - BONUS: The second commit in this PR fixes the feerate calculation for esplora and adds a test (the previous algorithm didn't work at all). I could have made a separate PR but since I was touching this file a lot I decided to fix it here. ## Notes to the reviewers - The most important thing to review is the the logic in `script_sync.rs` is sound. - Look at the two commits separately. - I think CI is failing because of MSRV problems again! - It would be cool to measure how much sync time is improved for your existing wallets/projects. For `gun` the speed improvements for modest but it is at least hammering the esplora server much less. - I noticed the performance of reqwest in blocking is much worse in this patch than previously. This is because somehow reqwest is not re-using the connection for each request in this new code. I have no idea why. The plan is to get rid of the blocking reqwest implementation in a follow up PR. ### Checklists #### All Submissions: * [x] I've signed all my commits ACKs for top commit: rajarshimaitra: Retested ACK a630685 Tree-SHA512: de74981e9d1f80758a9f20a3314ed7381c6b7c635f7ede80b177651fe2f9e9468064fae26bf80d4254098accfacfe50326ae0968e915186e13313f05bf77990b
- Loading branch information
Showing
11 changed files
with
1,125 additions
and
817 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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
//! structs from the esplora API | ||
//! | ||
//! see: <https://github.com/Blockstream/esplora/blob/master/API.md> | ||
use crate::BlockTime; | ||
use bitcoin::{OutPoint, Script, Transaction, TxIn, TxOut, Txid}; | ||
|
||
#[derive(serde::Deserialize, Clone, Debug)] | ||
pub struct PrevOut { | ||
pub value: u64, | ||
pub scriptpubkey: Script, | ||
} | ||
|
||
#[derive(serde::Deserialize, Clone, Debug)] | ||
pub struct Vin { | ||
pub txid: Txid, | ||
pub vout: u32, | ||
// None if coinbase | ||
pub prevout: Option<PrevOut>, | ||
pub scriptsig: Script, | ||
#[serde(deserialize_with = "deserialize_witness")] | ||
pub witness: Vec<Vec<u8>>, | ||
pub sequence: u32, | ||
pub is_coinbase: bool, | ||
} | ||
|
||
#[derive(serde::Deserialize, Clone, Debug)] | ||
pub struct Vout { | ||
pub value: u64, | ||
pub scriptpubkey: Script, | ||
} | ||
|
||
#[derive(serde::Deserialize, Clone, Debug)] | ||
pub struct TxStatus { | ||
pub confirmed: bool, | ||
pub block_height: Option<u32>, | ||
pub block_time: Option<u64>, | ||
} | ||
|
||
#[derive(serde::Deserialize, Clone, Debug)] | ||
pub struct Tx { | ||
pub txid: Txid, | ||
pub version: i32, | ||
pub locktime: u32, | ||
pub vin: Vec<Vin>, | ||
pub vout: Vec<Vout>, | ||
pub status: TxStatus, | ||
pub fee: u64, | ||
} | ||
|
||
impl Tx { | ||
pub fn to_tx(&self) -> Transaction { | ||
Transaction { | ||
version: self.version, | ||
lock_time: self.locktime, | ||
input: self | ||
.vin | ||
.iter() | ||
.cloned() | ||
.map(|vin| TxIn { | ||
previous_output: OutPoint { | ||
txid: vin.txid, | ||
vout: vin.vout, | ||
}, | ||
script_sig: vin.scriptsig, | ||
sequence: vin.sequence, | ||
witness: vin.witness, | ||
}) | ||
.collect(), | ||
output: self | ||
.vout | ||
.iter() | ||
.cloned() | ||
.map(|vout| TxOut { | ||
value: vout.value, | ||
script_pubkey: vout.scriptpubkey, | ||
}) | ||
.collect(), | ||
} | ||
} | ||
|
||
pub fn confirmation_time(&self) -> Option<BlockTime> { | ||
match self.status { | ||
TxStatus { | ||
confirmed: true, | ||
block_height: Some(height), | ||
block_time: Some(timestamp), | ||
} => Some(BlockTime { timestamp, height }), | ||
_ => None, | ||
} | ||
} | ||
|
||
pub fn previous_outputs(&self) -> Vec<Option<TxOut>> { | ||
self.vin | ||
.iter() | ||
.cloned() | ||
.map(|vin| { | ||
vin.prevout.map(|po| TxOut { | ||
script_pubkey: po.scriptpubkey, | ||
value: po.value, | ||
}) | ||
}) | ||
.collect() | ||
} | ||
} | ||
|
||
fn deserialize_witness<'de, D>(d: D) -> Result<Vec<Vec<u8>>, D::Error> | ||
where | ||
D: serde::de::Deserializer<'de>, | ||
{ | ||
use crate::serde::Deserialize; | ||
use bitcoin::hashes::hex::FromHex; | ||
let list = Vec::<String>::deserialize(d)?; | ||
list.into_iter() | ||
.map(|hex_str| Vec::<u8>::from_hex(&hex_str)) | ||
.collect::<Result<Vec<Vec<u8>>, _>>() | ||
.map_err(serde::de::Error::custom) | ||
} |
Oops, something went wrong.