chore: update block compression support based on latest spec draft (#3346)

This commit is contained in:
Antonio
2026-08-19 06:47:41 +00:00
committed by GitHub
parent 14c114a525
commit 109c20edcc
18 changed files with 202 additions and 144 deletions
Generated
-1
View File
@@ -4230,7 +4230,6 @@ dependencies = [
"async-trait",
"backon",
"futures",
"itertools 0.14.0",
"logos-blockchain-chain-service",
"logos-blockchain-codec",
"logos-blockchain-core",
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+4 -4
View File
@@ -8,14 +8,14 @@ use crate::Error;
/// Every dispersed payload body is padded to this size, so it must fit the
/// largest thing the blend network carries: a block proposal.
///
/// A block proposal is at most 10000 bytes:
/// A block proposal is at most 18192 bytes:
/// - A 297-byte header
/// - At most `MAX_UNCLES` uncle headers of 361 bytes each, behind a 1-byte
/// count prefix.
/// - At most `MAX_BLOCK_TXS` transaction references of 8 bytes, behind a 2-byte
/// count prefix.
/// - At most `MAX_BLOCK_TXS` transaction references of 16 bytes, behind a
/// 2-byte count prefix.
/// - A 64-byte signature
pub const MAX_PAYLOAD_BODY_SIZE: usize = 10000;
pub const MAX_PAYLOAD_BODY_SIZE: usize = 18_192;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[repr(u8)]
+4 -4
View File
@@ -44,7 +44,7 @@ fn header(id: u8) -> Header {
codec_fixtures!(
References,
Self { mempool_transactions: three_references() } => "0300010101010101010102020202020202020303030303030303"
Self { mempool_transactions: three_references() } => "0300010101010101010101010101010101010202020202020202020202020202020203030303030303030303030303030303"
);
// Layout: `header (297B) || signature (64B)`
@@ -58,10 +58,10 @@ codec_fixtures!(
);
// `header (297B) || uncle_headers (1B count + count * 361B)
// || references (26B) || signature (64B)` — 1471 bytes with three uncles.
// || references (50B) || signature (64B)` — 1495 bytes with three uncles.
//
// `references` is a `BoundedVec` capped at 1024, so it carries a 2-byte
// little-endian length prefix ahead of its 3 * 8-byte hash prefixes.
// little-endian length prefix ahead of its 3 * 16-byte hash prefixes.
codec_fixtures!(
Proposal,
Self {
@@ -69,5 +69,5 @@ codec_fixtures!(
uncle_headers: three_uncle_headers(),
references: References { mempool_transactions: three_references() },
signature: Ed25519Signature::from_bytes(&[0x01u8; _]),
} => "0100000000000000000000000000000000000000000000000000000000000000002a0000000000000022222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222555500000000000000000000000000000000000000000000000000000000000033333333333333333333333333333333333333333333333333333333333333334444000000000000000000000000000000000000000000000000000000000000030166666666666666666666666666666666666666666666666666666666666666662a0000000000000022222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222555500000000000000000000000000000000000000000000000000000000000033333333333333333333333333333333333333333333333333333333333333334444000000000000000000000000000000000000000000000000000000000000666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666660177777777777777777777777777777777777777777777777777777777777777772a0000000000000022222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222555500000000000000000000000000000000000000000000000000000000000033333333333333333333333333333333333333333333333333333333333333334444000000000000000000000000000000000000000000000000000000000000777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777770188888888888888888888888888888888888888888888888888888888888888882a000000000000002222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222255550000000000000000000000000000000000000000000000000000000000003333333333333333333333333333333333333333333333333333333333333333444400000000000000000000000000000000000000000000000000000000000088888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888030001010101010101010202020202020202030303030303030301010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101"
} => "0100000000000000000000000000000000000000000000000000000000000000002a0000000000000022222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222555500000000000000000000000000000000000000000000000000000000000033333333333333333333333333333333333333333333333333333333333333334444000000000000000000000000000000000000000000000000000000000000030166666666666666666666666666666666666666666666666666666666666666662a0000000000000022222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222555500000000000000000000000000000000000000000000000000000000000033333333333333333333333333333333333333333333333333333333333333334444000000000000000000000000000000000000000000000000000000000000666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666660177777777777777777777777777777777777777777777777777777777777777772a0000000000000022222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222555500000000000000000000000000000000000000000000000000000000000033333333333333333333333333333333333333333333333333333333333333334444000000000000000000000000000000000000000000000000000000000000777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777770188888888888888888888888888888888888888888888888888888888888888882a000000000000002222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222255550000000000000000000000000000000000000000000000000000000000003333333333333333333333333333333333333333333333333333333333333333444400000000000000000000000000000000000000000000000000000000000088888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888030001010101010101010101010101010101020202020202020202020202020202020303030303030303030303030303030301010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101"
);
+3 -15
View File
@@ -31,18 +31,6 @@ const MAX_BLOCK_TRANSACTIONS: usize = 1024;
/// Note: This is not the total block size.
pub const MAX_BLOCK_TRANSACTIONS_SIZE: usize = 1024 * 1024 * 2;
/// The most mempool transactions that may share a single reference prefix
/// before a proposal is treated as unreconstructable.
pub const MAX_CANDIDATES_PER_REFERENCE: usize = 8;
/// The most candidate combinations a validator will try while resolving a
/// proposal's references.
///
/// With `N_comb = product(|C_i|)` over the per-reference candidate sets, this
/// bounds reconstruction work so that prefix collisions stay a cost problem
/// rather than a verification-time denial-of-service vector.
pub const MAX_RECONSTRUCTION_COMBINATIONS: usize = 32;
pub type BlockNumber = u64;
#[derive(Debug, thiserror::Error)]
@@ -845,15 +833,15 @@ mod tests {
assert!(matches!(err, Error::Header(HeaderError::GenesisSlot)));
}
/// The specification fixes the maximum proposal at 10,000 bytes:
/// The specification fixes the maximum proposal at 18,192 bytes:
/// `header (297) || uncle_headers (1 + MAX_UNCLES * 361)
/// || references (2 + 8192) || signature (64)`.
/// || references (2 + 16384) || signature (64)`.
#[test]
fn maximum_proposal_matches_the_specified_size() {
use lb_codec::BinaryEncode as _;
use lb_cryptarchia_engine::MAX_UNCLES;
const SPECIFIED_MAX_PROPOSAL_SIZE: usize = 10000;
const SPECIFIED_MAX_PROPOSAL_SIZE: usize = 18_192;
let proof = create_proof();
let uncle = signed_uncle(1, &proof);
+2 -2
View File
@@ -25,6 +25,6 @@ codec_fixtures!(
codec_fixtures!(
TxHashPrefix,
Self([0u8; 8]) => "0000000000000000",
Self([1u8; 8]) => "0101010101010101"
Self([0u8; _]) => "00000000000000000000000000000000",
Self([1u8; _]) => "01010101010101010101010101010101"
);
+2 -2
View File
@@ -15,7 +15,7 @@ serde_bytes_newtype!(TxHash, 32);
/// Number of leading hash bytes a block proposal uses to refer to a
/// transaction.
const REFERENCE_PREFIX_BYTES: usize = 8;
pub const REFERENCE_PREFIX_BYTES: usize = 16;
/// The leading [`REFERENCE_PREFIX_BYTES`] bytes of a [`TxHash`] — how a block
/// proposal refers to a transaction that is already in a validator's mempool.
@@ -162,7 +162,7 @@ mod tests {
assert_eq!(
TxHash(hash).prefix(),
TxHashPrefix([0, 1, 2, 3, 4, 5, 6, 7])
TxHashPrefix([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])
);
}
-1
View File
@@ -16,7 +16,6 @@ workspace = true
async-trait = { workspace = true }
backon = { features = ["tokio-sleep"], workspace = true }
futures = { workspace = true }
itertools = { features = ["use_alloc"], workspace = true }
lb-chain-service = { workspace = true }
lb-codec = { workspace = true }
lb-core = { workspace = true }
+167 -99
View File
@@ -15,13 +15,9 @@ use std::{
use bootstrap::ibd::ChainNetworkIbdBlockProcessor;
use futures::{StreamExt as _, future::join_all};
use itertools::Itertools as _;
use lb_chain_service::api::{CryptarchiaServiceApi, CryptarchiaServiceData};
use lb_core::{
block::{
Block, BlockTransactions, MAX_CANDIDATES_PER_REFERENCE, MAX_RECONSTRUCTION_COMBINATIONS,
Proposal,
},
block::{Block, BlockTransactions, Proposal, verify_header_alone},
header::HeaderId,
mantle::{
traits::{Hashable, MantleTxWithProofs},
@@ -63,7 +59,7 @@ pub use crate::{
};
use crate::{
bootstrap::ibd::InitialBlockDownload,
mempool::{MempoolAdapter as _, adapter::MempoolAdapter},
mempool::{MempoolAdapter as MempoolAdapterTrait, adapter::MempoolAdapter},
relays::ChainNetworkRelays,
sync::{
orphan_handler::OrphanBlocksDownloader,
@@ -85,22 +81,14 @@ pub enum Error {
Serialisation(#[from] lb_core::codec::Error),
#[error("Invalid block: {0}")]
InvalidBlock(String),
#[error("Failed to reconstruct block: {0} mempool transactions not found")]
MissingMempoolTransactions(usize),
#[error("Reference {index} ({prefix}) matches no local transaction")]
UnresolvedReference { index: usize, prefix: TxHashPrefix },
#[error("Reference {index} ({prefix}) matches more than the {max} local transactions allowed")]
AmbiguousReference {
index: usize,
prefix: TxHashPrefix,
max: usize,
},
#[error(
"Resolving the references needs {combinations} combinations, more than the {max} allowed"
)]
TooManyReconstructionCombinations { combinations: usize, max: usize },
#[error("Header is not valid on its own: {0}")]
InvalidHeader(lb_core::block::HeaderError),
#[error("No combination of candidate transactions reproduces the block root")]
NoMatchingReconstruction,
#[error("Reference {index} ({prefix}) matches no local transaction")]
UnresolvedReference { index: usize, prefix: TxHashPrefix },
#[error("Reference {index} ({prefix}) matches more than one local transaction")]
CollidingReference { index: usize, prefix: TxHashPrefix },
#[error("Mempool error: {0}")]
Mempool(String),
#[error("Block header id not found: {0}")]
@@ -586,6 +574,23 @@ where
}
}
// The header must stand on its own before any mempool scanning.
// `references` is unauthenticated, so tampered copies of a genuine
// proposal are cheap to mint, and reconstruction walks the mempool once
// per reference. A bad signature is a property of the proposal itself —
// identical at every node — so it is final and recorded against
// `block_id`.
if let Err(e) = verify_header_alone(proposal.header(), proposal.signature()) {
let e = Error::InvalidHeader(e);
metrics::consensus_observe_proposal_reconstruct_err("network", &e);
error!(
target: LOG_TARGET, %e, ?block_id,
"Proposal header failed the checks that need the header alone",
);
orphan_downloader.insert_rejected_block(block_id);
return;
}
let reconstruct_started_at = Instant::now();
let block = match reconstruct_block_from_proposal(proposal, relays.mempool_adapter()).await
{
@@ -602,6 +607,11 @@ where
"Failed to reconstruct block from proposal: {:?}",
e
);
// Deliberately not recorded against `block_id`. Failing to
// resolve a reference says the transaction has not reached
// *this* mempool, not that the block is invalid; the same block
// may arrive in full through chain synchronisation and must
// then be judged on its merits.
return;
}
};
@@ -1008,99 +1018,63 @@ where
/// Reconstruct a `Block` from a `Proposal` by resolving its reference prefixes
/// against the local mempool.
///
/// A reference is only the leading bytes of a transaction hash, so it may match
/// several mempool transactions. `header.body_root` still commits to the full
/// hashes, so at most one combination of candidates can reproduce it — and
/// `Block::reconstruct` is what checks that, so the first combination that
/// reconstructs *is* the match.
/// A reference is a 16-byte prefix of the transaction hash. Two distinct hashes
/// cannot be found or manufactured to share one at any feasible cost, so every
/// reference resolves to exactly one local transaction and resolution never
/// branches. `header.body_root` still commits to the full hashes and
/// `Block::reconstruct` checks it, which is what catches the residual,
/// cryptographically negligible case of a prefix matching the wrong
/// transaction.
///
/// The caller must have validated the header first: this walks the mempool once
/// per reference, so an unauthenticated proposal has to be discarded before it
/// gets here.
async fn reconstruct_block_from_proposal<Item>(
proposal: Proposal,
mempool: &MempoolAdapter<Item>,
mempool: &impl MempoolAdapterTrait<Item>,
) -> Result<Block<Item>, Error>
where
Item: MantleTxWithProofs<Hash = TxHash> + Clone + Send + Sync + 'static,
{
let candidates = candidates_for_proposal(&proposal, mempool).await?;
let header = proposal.header().clone();
let uncle_headers = proposal.uncle_headers().clone();
let signature = *proposal.signature();
let try_rebuild_with_txs = |transactions: Vec<Item>| {
let transactions = BlockTransactions::try_from(transactions).ok()?;
Block::reconstruct(
header.clone(),
uncle_headers.clone(),
transactions,
signature,
)
.ok()
};
// A proposal with no references still has one candidate block: the empty one.
if candidates.is_empty() {
return try_rebuild_with_txs(Vec::new()).ok_or(Error::NoMatchingReconstruction);
}
// Try all combinations of candidates and find the one that can be used to
// reconstruct the block from the proposal.
candidates
.into_iter()
.multi_cartesian_product()
.find_map(try_rebuild_with_txs)
.ok_or(Error::NoMatchingReconstruction)
}
/// The candidate transactions for every reference in `proposal`, in reference
/// order, subject to both caps from the block-construction specification.
///
/// Returning here means the cartesian product of the sets is within budget, so
/// the search that follows is bounded before it starts.
async fn candidates_for_proposal<Item>(
proposal: &Proposal,
mempool: &MempoolAdapter<Item>,
) -> Result<Vec<Vec<Item>>, Error>
where
Item: MantleTxWithProofs<Hash = TxHash> + Clone + Send + Sync + 'static,
{
let mut candidates = Vec::with_capacity(proposal.mempool_transactions().len());
let mut combinations = 1usize;
let mut transactions = Vec::with_capacity(proposal.mempool_transactions().len());
for (index, prefix) in proposal.mempool_transactions().iter().copied().enumerate() {
let found = candidates_for_reference(index, prefix, mempool).await?;
combinations = combinations.saturating_mul(found.len());
if combinations > MAX_RECONSTRUCTION_COMBINATIONS {
return Err(Error::TooManyReconstructionCombinations {
combinations,
max: MAX_RECONSTRUCTION_COMBINATIONS,
});
}
candidates.push(found);
transactions.push(resolve_reference(index, prefix, mempool).await?);
}
Ok(candidates)
Block::reconstruct(
proposal.header().clone(),
proposal.uncle_headers().clone(),
BlockTransactions::try_from(transactions)?,
*proposal.signature(),
)
.map_err(|_| Error::NoMatchingReconstruction)
}
/// The local transactions a single reference could mean, refusing as soon as
/// the reference is unusable.
/// The single local transaction a reference means.
///
/// One candidate beyond the cap is taken so that "too many" is distinguishable
/// from "exactly at the limit" without draining the stream.
async fn candidates_for_reference<Item>(
/// A reference resolves only when the match is unique. Zero matches means the
/// transaction has not reached this mempool; two or more would be a prefix
/// collision, which is infeasible to manufacture and vanishingly unlikely to
/// occur by chance, and is treated as unresolved rather than searched. Because
/// the match is unique when it exists, the result does not depend on the order
/// in which the mempool is scanned.
///
/// Two candidates are taken so that "collides" is distinguishable from
/// "resolves" without draining the stream.
async fn resolve_reference<Item>(
index: usize,
prefix: TxHashPrefix,
mempool: &MempoolAdapter<Item>,
) -> Result<Vec<Item>, Error>
mempool: &impl MempoolAdapterTrait<Item>,
) -> Result<Item, Error>
where
Item: Hashable<Hash = TxHash> + Send + Sync + 'static,
{
let candidates: Vec<Item> = mempool
let mut candidates: Vec<Item> = mempool
.get_transactions_by_prefix(prefix)
.await
.map_err(|e| Error::Mempool(format!("Failed to resolve reference {index}: {e}")))?
.take(MAX_CANDIDATES_PER_REFERENCE.saturating_add(1))
.take(2)
// We collect into a `Vec` since we are only taking at most 2 elements.
.collect()
.await;
@@ -1109,12 +1083,8 @@ where
metrics::consensus_observe_proposal_missing_txs(1);
Err(Error::UnresolvedReference { index, prefix })
}
found if found > MAX_CANDIDATES_PER_REFERENCE => Err(Error::AmbiguousReference {
index,
prefix,
max: MAX_CANDIDATES_PER_REFERENCE,
}),
_ => Ok(candidates),
1 => Ok(candidates.remove(0)),
_ => Err(Error::CollidingReference { index, prefix }),
}
}
@@ -1122,8 +1092,106 @@ where
mod tests {
use std::sync::atomic::{AtomicUsize, Ordering};
use futures::stream;
use lb_core::mantle::{traits::Hasher, transactions::hash::REFERENCE_PREFIX_BYTES};
use lb_tx_service::TxsWithCommonPrefix;
use super::*;
/// A transaction that is nothing but its hash, which is all
/// [`resolve_reference`] looks at.
#[derive(Clone, Debug, PartialEq, Eq)]
struct HashOnlyTx(TxHash);
impl Hashable for HashOnlyTx {
const HASHER: Hasher<Self> = |tx| tx.0;
type Hash = TxHash;
fn as_signing(&self) -> Vec<u8> {
self.0.0.to_vec()
}
}
/// A mempool that answers every prefix with the same fixed candidate list,
/// so a test states only how many transactions a reference matches.
struct FixedCandidates(Vec<HashOnlyTx>);
#[async_trait::async_trait]
impl MempoolAdapterTrait<HashOnlyTx> for FixedCandidates {
async fn add_transaction(&self, _tx: HashOnlyTx) -> Result<(), DynError> {
unimplemented!("resolution never adds")
}
async fn remove_transactions(&self, _ids: &[TxHash]) -> Result<(), DynError> {
unimplemented!("resolution never removes")
}
async fn get_transactions_by_prefix(
&self,
_prefix: TxHashPrefix,
) -> Result<TxsWithCommonPrefix<HashOnlyTx>, DynError> {
Ok(Box::pin(stream::iter(self.0.clone())))
}
}
fn tx(byte: u8) -> HashOnlyTx {
HashOnlyTx(TxHash([byte; 32]))
}
/// Two transactions that agree on the reference prefix and differ only past
/// it — the collision a 16-byte prefix makes infeasible, constructed by
/// hand.
fn colliding_pair() -> Vec<HashOnlyTx> {
let mut first = [0xAAu8; 32];
let mut second = [0xAAu8; 32];
first[REFERENCE_PREFIX_BYTES] = 0x01;
second[REFERENCE_PREFIX_BYTES] = 0x02;
vec![HashOnlyTx(TxHash(first)), HashOnlyTx(TxHash(second))]
}
#[tokio::test]
async fn a_reference_resolves_to_its_one_matching_transaction() {
let mempool = FixedCandidates(vec![tx(7)]);
let resolved = resolve_reference(0, TxHash([7u8; 32]).prefix(), &mempool)
.await
.expect("a unique match resolves");
assert_eq!(resolved, tx(7));
}
#[tokio::test]
async fn a_reference_matching_nothing_locally_is_unresolved() {
let mempool = FixedCandidates(Vec::new());
let prefix = TxHash([7u8; 32]).prefix();
assert!(matches!(
resolve_reference(3, prefix, &mempool).await,
Err(Error::UnresolvedReference { index: 3, prefix: p }) if p == prefix
));
}
/// A collision is reported rather than searched: resolution must not
/// branch, so that two validators holding the same mempool always
/// decide alike.
#[tokio::test]
async fn a_reference_matching_two_transactions_is_not_searched() {
let candidates = colliding_pair();
let prefix = candidates[0].hash().prefix();
assert_eq!(
prefix,
candidates[1].hash().prefix(),
"the pair must collide"
);
let mempool = FixedCandidates(candidates);
assert!(matches!(
resolve_reference(1, prefix, &mempool).await,
Err(Error::CollidingReference { index: 1, prefix: p }) if p == prefix
));
}
fn future_block_error() -> Error {
Error::Cryptarchia(lb_chain_service::api::ApiError::FutureBlock {
block_slot: Slot::new(2),
@@ -12,9 +12,10 @@ pub trait MempoolAdapter<Tx>: Send + Sync {
/// The local transactions a single proposal reference could mean.
///
/// A reference is only the leading hash bytes, so several mempool
/// transactions may answer to it. The stream is unbounded and unordered:
/// deciding how many candidates are acceptable is the caller's job.
/// A reference is only the leading hash bytes, so in principle several
/// mempool transactions could answer to it. The stream is unbounded and
/// unordered: what a non-unique match means is a consensus question, so it
/// is the caller's to decide.
async fn get_transactions_by_prefix(
&self,
prefix: TxHashPrefix,
+4 -1
View File
@@ -43,7 +43,10 @@ pub fn consensus_observe_proposal_reconstruct_ok(duration: Duration) {
pub fn consensus_observe_proposal_reconstruct_err(origin: &'static str, err: &Error) {
let reason = match err {
Error::MissingMempoolTransactions(_) => "missing_txs",
Error::InvalidHeader(_) => "invalid_header",
Error::UnresolvedReference { .. } => "missing_txs",
Error::CollidingReference { .. } => "colliding_reference",
Error::NoMatchingReconstruction => "body_root_mismatch",
Error::Mempool(_) => "mempool",
Error::InvalidBlock(_) => "invalid_block",
_ => "other",
+4 -4
View File
@@ -70,10 +70,10 @@ where
/// Secondary index over [`Self::pending_items`], keyed by the hash prefix a
/// block proposal would use to refer to a transaction.
///
/// Buckets are plain vectors: a 64-bit prefix makes collisions vanishingly
/// rare, so almost every one holds a single key, and the branching cap puts
/// a hard ceiling of a handful on the rest. A set would cost a hash table
/// per bucket to deduplicate keys that are already unique.
/// Buckets are plain vectors: a 128-bit prefix cannot be made to collide at
/// any feasible cost, so every one holds a single key in practice. A set
/// would cost a hash table per bucket to deduplicate keys that are already
/// unique.
by_prefix: HashMap<Key::Prefix, Vec<Key>>,
removed_items: BTreeMap<Key, u64>,
last_item_timestamp: u64,
+2 -2
View File
@@ -459,8 +459,8 @@ where
/// Every transaction whose hash starts with `prefix`.
///
/// The prefix index is consulted first, so the storage round-trip only
/// covers keys that actually match. No policy is applied here: how many
/// candidates are tolerable is a consensus question, so it belongs to the
/// covers keys that actually match. No policy is applied here: what a
/// non-unique match means is a consensus question, so it belongs to the
/// caller.
async fn get_transactions_by_prefix(
pool: &Pool,
+1 -1
View File
@@ -817,7 +817,7 @@ fn prefix_lookup_tracks_the_pending_set() {
);
// A prefix that no transaction carries stays empty.
let absent = TxHashPrefix([0xFFu8; 8]);
let absent = TxHashPrefix([0xFFu8; _]);
if absent != prefix {
assert!(
app.runtime()