Fix hang when ElectrumBlockchainConfig::stop_gap == 0
* Ensure chunk_size is > 0 during wallet sync. * Slight refactoring for better readability. * Add test: test_electrum_blockchain_factory_sync_with_stop_gaps
This commit is contained in:
parent
063d51fd75
commit
8a5f89e129
@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
- New MSRV set to `1.56.1`
|
- New MSRV set to `1.56.1`
|
||||||
- Fee sniping discouraging through nLockTime - if the user specifies a `current_height`, we use that as a nlocktime, otherwise we use the last sync height (or 0 if we never synced)
|
- Fee sniping discouraging through nLockTime - if the user specifies a `current_height`, we use that as a nlocktime, otherwise we use the last sync height (or 0 if we never synced)
|
||||||
|
- Fix hang when `ElectrumBlockchainConfig::stop_gap` is zero.
|
||||||
|
|
||||||
## [v0.19.0] - [v0.18.0]
|
## [v0.19.0] - [v0.18.0]
|
||||||
|
|
||||||
|
@ -108,7 +108,10 @@ impl WalletSync for ElectrumBlockchain {
|
|||||||
let mut block_times = HashMap::<u32, u32>::new();
|
let mut block_times = HashMap::<u32, u32>::new();
|
||||||
let mut txid_to_height = HashMap::<Txid, u32>::new();
|
let mut txid_to_height = HashMap::<Txid, u32>::new();
|
||||||
let mut tx_cache = TxCache::new(database, &self.client);
|
let mut tx_cache = TxCache::new(database, &self.client);
|
||||||
let chunk_size = self.stop_gap;
|
|
||||||
|
// Set chunk_size to the smallest value capable of finding a gap greater than stop_gap.
|
||||||
|
let chunk_size = self.stop_gap + 1;
|
||||||
|
|
||||||
// The electrum server has been inconsistent somehow in its responses during sync. For
|
// The electrum server has been inconsistent somehow in its responses during sync. For
|
||||||
// example, we do a batch request of transactions and the response contains less
|
// example, we do a batch request of transactions and the response contains less
|
||||||
// tranascations than in the request. This should never happen but we don't want to panic.
|
// tranascations than in the request. This should never happen but we don't want to panic.
|
||||||
@ -145,18 +148,16 @@ impl WalletSync for ElectrumBlockchain {
|
|||||||
Request::Conftime(conftime_req) => {
|
Request::Conftime(conftime_req) => {
|
||||||
// collect up to chunk_size heights to fetch from electrum
|
// collect up to chunk_size heights to fetch from electrum
|
||||||
let needs_block_height = {
|
let needs_block_height = {
|
||||||
let mut needs_block_height_iter = conftime_req
|
let mut needs_block_height = HashSet::with_capacity(chunk_size);
|
||||||
|
conftime_req
|
||||||
.request()
|
.request()
|
||||||
.filter_map(|txid| txid_to_height.get(txid).cloned())
|
.filter_map(|txid| txid_to_height.get(txid).cloned())
|
||||||
.filter(|height| block_times.get(height).is_none());
|
.filter(|height| block_times.get(height).is_none())
|
||||||
let mut needs_block_height = HashSet::new();
|
.take(chunk_size)
|
||||||
|
.for_each(|height| {
|
||||||
|
needs_block_height.insert(height);
|
||||||
|
});
|
||||||
|
|
||||||
while needs_block_height.len() < chunk_size {
|
|
||||||
match needs_block_height_iter.next() {
|
|
||||||
Some(height) => needs_block_height.insert(height),
|
|
||||||
None => break,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
needs_block_height
|
needs_block_height
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -385,4 +386,116 @@ mod test {
|
|||||||
|
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000);
|
assert_eq!(wallet.get_balance().unwrap(), 50_000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_electrum_blockchain_factory_sync_with_stop_gaps() {
|
||||||
|
// Test whether Electrum blockchain syncs with expected behaviour given different `stop_gap`
|
||||||
|
// parameters.
|
||||||
|
//
|
||||||
|
// For each test vector:
|
||||||
|
// * Fill wallet's derived addresses with balances (as specified by test vector).
|
||||||
|
// * [0..addrs_before] => 1000sats for each address
|
||||||
|
// * [addrs_before..actual_gap] => empty addresses
|
||||||
|
// * [actual_gap..addrs_after] => 1000sats for each address
|
||||||
|
// * Then, perform wallet sync and obtain wallet balance
|
||||||
|
// * Check balance is within expected range (we can compare `stop_gap` and `actual_gap` to
|
||||||
|
// determine this).
|
||||||
|
|
||||||
|
// Generates wallet descriptor
|
||||||
|
let descriptor_of_account = |account_index: usize| -> String {
|
||||||
|
format!("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/{account_index}/*)")
|
||||||
|
};
|
||||||
|
|
||||||
|
// Amount (in satoshis) provided to a single address (which expects to have a balance)
|
||||||
|
const AMOUNT_PER_TX: u64 = 1000;
|
||||||
|
|
||||||
|
// [stop_gap, actual_gap, addrs_before, addrs_after]
|
||||||
|
//
|
||||||
|
// [0] stop_gap: Passed to [`ElectrumBlockchainConfig`]
|
||||||
|
// [1] actual_gap: Range size of address indexes without a balance
|
||||||
|
// [2] addrs_before: Range size of address indexes (before gap) which contains a balance
|
||||||
|
// [3] addrs_after: Range size of address indexes (after gap) which contains a balance
|
||||||
|
let test_vectors: Vec<[u64; 4]> = vec![
|
||||||
|
[0, 0, 0, 5],
|
||||||
|
[0, 0, 5, 5],
|
||||||
|
[0, 1, 5, 5],
|
||||||
|
[0, 2, 5, 5],
|
||||||
|
[1, 0, 5, 5],
|
||||||
|
[1, 1, 5, 5],
|
||||||
|
[1, 2, 5, 5],
|
||||||
|
[2, 1, 5, 5],
|
||||||
|
[2, 2, 5, 5],
|
||||||
|
[2, 3, 5, 5],
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut test_client = TestClient::default();
|
||||||
|
|
||||||
|
for (account_index, vector) in test_vectors.into_iter().enumerate() {
|
||||||
|
let [stop_gap, actual_gap, addrs_before, addrs_after] = vector;
|
||||||
|
let descriptor = descriptor_of_account(account_index);
|
||||||
|
|
||||||
|
let factory = Arc::new(
|
||||||
|
ElectrumBlockchain::from_config(&ElectrumBlockchainConfig {
|
||||||
|
url: test_client.electrsd.electrum_url.clone(),
|
||||||
|
socks5: None,
|
||||||
|
retry: 0,
|
||||||
|
timeout: None,
|
||||||
|
stop_gap: stop_gap as _,
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
let wallet = Wallet::new(
|
||||||
|
descriptor.as_str(),
|
||||||
|
None,
|
||||||
|
bitcoin::Network::Regtest,
|
||||||
|
MemoryDatabase::new(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// fill server-side with txs to specified address indexes
|
||||||
|
// return the max balance of the wallet (also the actual balance)
|
||||||
|
let max_balance = (0..addrs_before)
|
||||||
|
.chain(addrs_before + actual_gap..addrs_before + actual_gap + addrs_after)
|
||||||
|
.fold(0_u64, |sum, i| {
|
||||||
|
let address = wallet.get_address(AddressIndex::Peek(i as _)).unwrap();
|
||||||
|
let tx = testutils! {
|
||||||
|
@tx ( (@addr address.address) => AMOUNT_PER_TX )
|
||||||
|
};
|
||||||
|
test_client.receive(tx);
|
||||||
|
sum + AMOUNT_PER_TX
|
||||||
|
});
|
||||||
|
|
||||||
|
// generate blocks to confirm new transactions
|
||||||
|
test_client.generate(3, None);
|
||||||
|
|
||||||
|
// minimum allowed balance of wallet (based on stop gap)
|
||||||
|
let min_balance = if actual_gap > stop_gap {
|
||||||
|
addrs_before * AMOUNT_PER_TX
|
||||||
|
} else {
|
||||||
|
max_balance
|
||||||
|
};
|
||||||
|
|
||||||
|
// perform wallet sync
|
||||||
|
factory
|
||||||
|
.sync_wallet(&wallet, None, Default::default())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let wallet_balance = wallet.get_balance().unwrap();
|
||||||
|
|
||||||
|
let details = format!(
|
||||||
|
"test_vector: [stop_gap: {}, actual_gap: {}, addrs_before: {}, addrs_after: {}]",
|
||||||
|
stop_gap, actual_gap, addrs_before, addrs_after,
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
wallet_balance <= max_balance,
|
||||||
|
"wallet balance is greater than received amount: {}",
|
||||||
|
details
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
wallet_balance >= min_balance,
|
||||||
|
"wallet balance is smaller than expected: {}",
|
||||||
|
details
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user