Always drop the final hash in peer responses (#991)
To workaround a zcashd bug that squashes responses together.
This commit is contained in:
parent
c770daa51f
commit
437549d8e9
|
|
@ -198,6 +198,24 @@ where
|
||||||
while let Some(res) = requests.next().await {
|
while let Some(res) = requests.next().await {
|
||||||
match res.map_err::<Report, _>(|e| eyre!(e)) {
|
match res.map_err::<Report, _>(|e| eyre!(e)) {
|
||||||
Ok(zn::Response::BlockHashes(hashes)) => {
|
Ok(zn::Response::BlockHashes(hashes)) => {
|
||||||
|
tracing::trace!(?hashes);
|
||||||
|
|
||||||
|
// zcashd sometimes appends an unrelated hash at the start
|
||||||
|
// or end of its response.
|
||||||
|
//
|
||||||
|
// We can't discard the first hash, because it might be a
|
||||||
|
// block we want to download. So we just accept any
|
||||||
|
// out-of-order first hashes.
|
||||||
|
|
||||||
|
// We use the last hash for the tip, and we want to avoid bad
|
||||||
|
// tips. So we discard the last hash. (We don't need to worry
|
||||||
|
// about missed downloads, because we will pick them up again
|
||||||
|
// in ExtendTips.)
|
||||||
|
let hashes = match hashes.as_slice() {
|
||||||
|
[] => continue,
|
||||||
|
[rest @ .., _last] => rest,
|
||||||
|
};
|
||||||
|
|
||||||
let mut first_unknown = None;
|
let mut first_unknown = None;
|
||||||
for (i, &hash) in hashes.iter().enumerate() {
|
for (i, &hash) in hashes.iter().enumerate() {
|
||||||
if !self.state_contains(hash).await? {
|
if !self.state_contains(hash).await? {
|
||||||
|
|
@ -235,7 +253,10 @@ where
|
||||||
.retain(|t| !unknown_hashes.contains(&t.expected_next));
|
.retain(|t| !unknown_hashes.contains(&t.expected_next));
|
||||||
self.prospective_tips.insert(new_tip);
|
self.prospective_tips.insert(new_tip);
|
||||||
} else {
|
} else {
|
||||||
tracing::debug!(?new_tip, "discarding tip already queued for download");
|
tracing::debug!(
|
||||||
|
?new_tip,
|
||||||
|
"discarding prospective tip: already in download set"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let prev_download_len = download_set.len();
|
let prev_download_len = download_set.len();
|
||||||
|
|
@ -323,7 +344,14 @@ where
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
tracing::trace!(?unknown_hashes);
|
// We use the last hash for the tip, and we want to avoid
|
||||||
|
// bad tips. So we discard the last hash. (We don't need
|
||||||
|
// to worry about missed downloads, because we will pick
|
||||||
|
// them up again in the next ExtendTips.)
|
||||||
|
let unknown_hashes = match unknown_hashes {
|
||||||
|
[] => continue,
|
||||||
|
[rest @ .., _last] => rest,
|
||||||
|
};
|
||||||
|
|
||||||
let new_tip = if let Some(end) = unknown_hashes.rchunks_exact(2).next() {
|
let new_tip = if let Some(end) = unknown_hashes.rchunks_exact(2).next() {
|
||||||
CheckedTip {
|
CheckedTip {
|
||||||
|
|
@ -335,7 +363,7 @@ where
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
tracing::trace!(?hashes);
|
tracing::trace!(?unknown_hashes);
|
||||||
|
|
||||||
// Make sure we get the same tips, regardless of the
|
// Make sure we get the same tips, regardless of the
|
||||||
// order of peer responses
|
// order of peer responses
|
||||||
|
|
@ -346,7 +374,10 @@ where
|
||||||
.retain(|t| !unknown_hashes.contains(&t.expected_next));
|
.retain(|t| !unknown_hashes.contains(&t.expected_next));
|
||||||
self.prospective_tips.insert(new_tip);
|
self.prospective_tips.insert(new_tip);
|
||||||
} else {
|
} else {
|
||||||
tracing::debug!(?new_tip, "discarding tip already queued for download");
|
tracing::debug!(
|
||||||
|
?new_tip,
|
||||||
|
"discarding prospective tip: already in download set"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let prev_download_len = download_set.len();
|
let prev_download_len = download_set.len();
|
||||||
|
|
@ -370,7 +401,7 @@ where
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Queue a download for the genesis block, if it isn't currently known to
|
/// Download and verify the genesis block, if it isn't currently known to
|
||||||
/// our node.
|
/// our node.
|
||||||
async fn request_genesis(&mut self) -> Result<(), Report> {
|
async fn request_genesis(&mut self) -> Result<(), Report> {
|
||||||
// Due to Bitcoin protocol limitations, we can't request the genesis
|
// Due to Bitcoin protocol limitations, we can't request the genesis
|
||||||
|
|
@ -379,15 +410,15 @@ where
|
||||||
// - responses start with the block *after* the requested block, and
|
// - responses start with the block *after* the requested block, and
|
||||||
// - the genesis hash is used as a placeholder for "no matches".
|
// - the genesis hash is used as a placeholder for "no matches".
|
||||||
//
|
//
|
||||||
// So we just queue the genesis block here.
|
// So we just download and verify the genesis block here.
|
||||||
while !self.state_contains(self.genesis_hash).await? {
|
while !self.state_contains(self.genesis_hash).await? {
|
||||||
self.request_blocks(vec![self.genesis_hash]).await?;
|
self.request_blocks(vec![self.genesis_hash]).await?;
|
||||||
match self
|
match self
|
||||||
.pending_blocks
|
.pending_blocks
|
||||||
.next()
|
.next()
|
||||||
.await
|
.await
|
||||||
.expect("inserted a download request")
|
.expect("inserted a download and verify request")
|
||||||
.expect("block download tasks should not panic")
|
.expect("block download and verify tasks should not panic")
|
||||||
{
|
{
|
||||||
Ok(hash) => tracing::debug!(?hash, "verified and committed block to state"),
|
Ok(hash) => tracing::debug!(?hash, "verified and committed block to state"),
|
||||||
Err(e) => tracing::warn!(?e, "could not download genesis block, retrying"),
|
Err(e) => tracing::warn!(?e, "could not download genesis block, retrying"),
|
||||||
|
|
@ -397,11 +428,13 @@ where
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Queue downloads for each block that isn't currently known to our node
|
/// Queue download and verify tasks for each block that isn't currently known to our node
|
||||||
async fn request_blocks(&mut self, hashes: Vec<block::Hash>) -> Result<(), Report> {
|
async fn request_blocks(&mut self, hashes: Vec<block::Hash>) -> Result<(), Report> {
|
||||||
tracing::debug!(hashes.len = hashes.len(), "requesting blocks");
|
tracing::debug!(hashes.len = hashes.len(), "requesting blocks");
|
||||||
for hash in hashes.into_iter() {
|
for hash in hashes.into_iter() {
|
||||||
// TODO: remove this check once the sync service is more reliable
|
// Avoid re-downloading blocks that have already been verified.
|
||||||
|
// This is particularly important for nodes on slow or unreliable
|
||||||
|
// networks.
|
||||||
if self.state_contains(hash).await? {
|
if self.state_contains(hash).await? {
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
?hash,
|
?hash,
|
||||||
|
|
@ -409,7 +442,7 @@ where
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// We construct the block download requests sequentially, waiting
|
// We construct the block requests sequentially, waiting
|
||||||
// for the peer set to be ready to process each request. This
|
// for the peer set to be ready to process each request. This
|
||||||
// ensures that we start block downloads in the order we want them
|
// ensures that we start block downloads in the order we want them
|
||||||
// (though they may resolve out of order), and it means that we
|
// (though they may resolve out of order), and it means that we
|
||||||
|
|
@ -424,7 +457,7 @@ where
|
||||||
.map_err(|e| eyre!(e))?
|
.map_err(|e| eyre!(e))?
|
||||||
.call(zn::Request::BlocksByHash(iter::once(hash).collect()));
|
.call(zn::Request::BlocksByHash(iter::once(hash).collect()));
|
||||||
|
|
||||||
tracing::debug!(?hash, "requested block");
|
tracing::trace!(?hash, "requested block");
|
||||||
|
|
||||||
let span = tracing::info_span!("block_fetch_verify", ?hash);
|
let span = tracing::info_span!("block_fetch_verify", ?hash);
|
||||||
let mut verifier = self.verifier.clone();
|
let mut verifier = self.verifier.clone();
|
||||||
|
|
@ -452,11 +485,9 @@ where
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `Ok(true)` if the hash is present in the state, and `Ok(false)`
|
/// Returns `true` if the hash is present in the state, and `false`
|
||||||
/// if the hash is not present in the state.
|
/// if the hash is not present in the state.
|
||||||
///
|
///
|
||||||
/// Returns `Err(_)` if an error occurs.
|
|
||||||
///
|
|
||||||
/// TODO: handle multiple tips in the state.
|
/// TODO: handle multiple tips in the state.
|
||||||
async fn state_contains(&mut self, hash: block::Hash) -> Result<bool, Report> {
|
async fn state_contains(&mut self, hash: block::Hash) -> Result<bool, Report> {
|
||||||
match self
|
match self
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue