From 709485dcb7f76f55b882b5070f93c13b71bc1689 Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Wed, 29 Apr 2026 20:57:05 +0800 Subject: [PATCH 001/335] perf(bench): buffer RPC fetches in generate-big-block (#23830) Co-authored-by: Claude Opus 4.7 (1M context) --- .../src/bench/generate_big_block.rs | 168 ++++++++++++------ 1 file changed, 112 insertions(+), 56 deletions(-) diff --git a/bin/reth-bench/src/bench/generate_big_block.rs b/bin/reth-bench/src/bench/generate_big_block.rs index 5826cce7712..53a504c054a 100644 --- a/bin/reth-bench/src/bench/generate_big_block.rs +++ b/bin/reth-bench/src/bench/generate_big_block.rs @@ -21,6 +21,7 @@ use alloy_rpc_types_engine::{ }; use clap::Parser; use eyre::Context; +use futures::{stream, StreamExt}; use reth_chainspec::EthChainSpec; use reth_cli::chainspec::ChainSpecParser; use reth_cli_runner::CliContext; @@ -270,6 +271,15 @@ pub struct Command { /// the flattened BAL on the stored payload. #[arg(long, default_value_t = false)] bal: bool, + + /// Maximum number of in-flight RPC fetches to keep buffered ahead of the merger. + /// + /// Each entry is one full per-block fetch (block + receipts, plus BAL when `--bal` is + /// set). Larger values absorb RPC latency at the cost of more concurrent connections + /// and memory; the buffer persists across `--num-big-blocks` so prefetching continues + /// across big-block boundaries. + #[arg(long, value_name = "PREFETCH_BUFFER", default_value_t = 32)] + prefetch_buffer: usize, } impl Command { @@ -322,13 +332,27 @@ impl Command { } let mut prev_big_block_header: Option = None; - // Track the next block to fetch across big blocks so they don't overlap. + // Persistent prefetch stream: keeps `prefetch_buffer` per-block fetches in flight + // ahead of the merger across all big blocks. Each item is a fully materialized + // `FetchedBlock` (or `None` once the chain tip is reached on this fetch). + let prefetch_buffer = self.prefetch_buffer.max(1); + let bal_enabled = self.bal; + let block_stream = stream::iter(self.from_block..) + .map(|block_number| { + let provider = provider.clone(); + async move { fetch_one_block(provider, block_number, bal_enabled).await } + }) + .buffered(prefetch_buffer); + let mut block_stream = Box::pin(block_stream); + + // Track the next block number we expect from the stream (purely for logging / + // big-block range bookkeeping; the stream produces blocks in `from_block..` order). let mut next_block = self.from_block; for big_block_idx in 0..self.num_big_blocks { let range_start = next_block; - // Fetch consecutive blocks until the gas target is reached. + // Drain the prefetch stream until the gas target is reached for this big block. let mut blocks = Vec::new(); let mut block_receipts: Vec> = Vec::new(); let mut block_access_lists: Vec> = Vec::new(); @@ -337,16 +361,11 @@ impl Command { let mut reached_chain_tip = false; while accumulated_block_gas < self.target_gas { let block_number = next_block; - info!(target: "reth-bench", block_number, big_block = big_block_idx, "Fetching block"); + info!(target: "reth-bench", block_number, big_block = big_block_idx, "Awaiting prefetched block"); - let fetch_result = tokio::try_join!( - provider.get_block_by_number(block_number.into()).full(), - provider.get_block_receipts(block_number.into()), - ); - - let (rpc_block, receipts) = match fetch_result { - Ok((Some(block), Some(receipts))) => (block, receipts), - Ok((None, _) | (_, None)) => { + let fetched = match block_stream.next().await { + Some(Ok(Some(fetched))) => fetched, + Some(Ok(None)) => { warn!( target: "reth-bench", block_number, @@ -355,52 +374,16 @@ impl Command { reached_chain_tip = true; break; } - Err(e) => return Err(e.into()), - }; - - let block_access_list = if self.bal { - Some(fetch_block_access_list(&provider, block_number).await.wrap_err_with( - || format!("Failed to fetch BAL for block {block_number}"), - )?) - } else { - None + Some(Err(e)) => return Err(e), + // The block-number stream is open-ended; this only fires if the + // upstream `iter(from..)` is somehow exhausted. + None => { + reached_chain_tip = true; + break; + } }; - - // Convert RPC receipts to consensus receipts - let consensus_receipts: Vec = receipts - .iter() - .map(|r| { - let inner = &r.inner.inner.inner; - let tx_type = r.inner.inner.r#type.try_into().unwrap_or_default(); - Receipt { - tx_type, - success: inner.receipt.status.coerce_status(), - cumulative_gas_used: inner.receipt.cumulative_gas_used, - logs: inner - .receipt - .logs - .iter() - .map(|log| alloy_primitives::Log { - address: log.inner.address, - data: log.inner.data.clone(), - }) - .collect(), - } - }) - .collect(); - - // Convert to consensus block - let block = rpc_block - .into_inner() - .map_header(|header| header.map(|h| h.into_header_with_defaults())) - .try_map_transactions(|tx| -> eyre::Result { - tx.try_into().map_err(|_| eyre::eyre!("unsupported tx type")) - })? - .into_consensus(); - - // Convert to ExecutionData - let (payload, sidecar) = ExecutionPayload::from_block_slow(&block); - let execution_data = ExecutionData { payload, sidecar }; + let FetchedBlock { execution_data, consensus_receipts, block_access_list } = + fetched; let block_gas = execution_data.payload.as_v1().gas_used; let block_blob_gas = @@ -674,6 +657,79 @@ impl Command { } } +/// One fully-materialized block fetched by the prefetcher. +struct FetchedBlock { + /// Execution payload with sidecar derived from the RPC block. + execution_data: ExecutionData, + /// Consensus-format receipts (`cumulative_gas_used` is still per-block, callers offset + /// it when merging). + consensus_receipts: Vec, + /// `eth_getBlockAccessListByBlockNumber` result when `--bal` is enabled. + block_access_list: Option, +} + +/// Fetches one block + receipts (and optionally its BAL) from the RPC. Returns `Ok(None)` +/// when the block doesn't exist yet (chain-tip reached). +async fn fetch_one_block( + provider: RootProvider, + block_number: u64, + bal_enabled: bool, +) -> eyre::Result> { + let (rpc_block, receipts) = tokio::try_join!( + provider.get_block_by_number(block_number.into()).full(), + provider.get_block_receipts(block_number.into()), + )?; + let (rpc_block, receipts) = match (rpc_block, receipts) { + (Some(b), Some(r)) => (b, r), + _ => return Ok(None), + }; + + let block_access_list = if bal_enabled { + Some( + fetch_block_access_list(&provider, block_number) + .await + .wrap_err_with(|| format!("Failed to fetch BAL for block {block_number}"))?, + ) + } else { + None + }; + + let consensus_receipts: Vec = receipts + .iter() + .map(|r| { + let inner = &r.inner.inner.inner; + let tx_type = r.inner.inner.r#type.try_into().unwrap_or_default(); + Receipt { + tx_type, + success: inner.receipt.status.coerce_status(), + cumulative_gas_used: inner.receipt.cumulative_gas_used, + logs: inner + .receipt + .logs + .iter() + .map(|log| alloy_primitives::Log { + address: log.inner.address, + data: log.inner.data.clone(), + }) + .collect(), + } + }) + .collect(); + + let block = rpc_block + .into_inner() + .map_header(|header| header.map(|h| h.into_header_with_defaults())) + .try_map_transactions(|tx| -> eyre::Result { + tx.try_into().map_err(|_| eyre::eyre!("unsupported tx type")) + })? + .into_consensus(); + + let (payload, sidecar) = ExecutionPayload::from_block_slow(&block); + let execution_data = ExecutionData { payload, sidecar }; + + Ok(Some(FetchedBlock { execution_data, consensus_receipts, block_access_list })) +} + fn merge_block_access_list( merged: &mut BlockAccessList, incoming: BlockAccessList, From 077e5eecfef02e2fc32d35cd6f79ab5975b975a1 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Wed, 29 Apr 2026 18:36:44 +0400 Subject: [PATCH 002/335] chore: don't enforce non-empty blocks in e2e payload building (#23837) Co-authored-by: Matthias Seitz --- crates/e2e-test-utils/src/payload.rs | 30 +++++++--------------------- 1 file changed, 7 insertions(+), 23 deletions(-) diff --git a/crates/e2e-test-utils/src/payload.rs b/crates/e2e-test-utils/src/payload.rs index 47c533e9769..fe0d4452c9f 100644 --- a/crates/e2e-test-utils/src/payload.rs +++ b/crates/e2e-test-utils/src/payload.rs @@ -1,8 +1,8 @@ use futures_util::StreamExt; -use reth_node_api::{BlockBody, PayloadAttributes, PayloadKind}; +use reth_node_api::{PayloadAttributes, PayloadKind}; use reth_payload_builder::{PayloadBuilderHandle, PayloadId}; use reth_payload_builder_primitives::Events; -use reth_payload_primitives::{BuiltPayload, PayloadTypes}; +use reth_payload_primitives::PayloadTypes; use tokio_stream::wrappers::BroadcastStream; /// Helper for payload operations @@ -53,27 +53,11 @@ impl PayloadTestContext { /// /// Panics if the payload builder does not produce a non-empty payload within 30 seconds. pub async fn wait_for_built_payload(&self, payload_id: PayloadId) { - let start = std::time::Instant::now(); - loop { - let payload = - self.payload_builder.best_payload(payload_id).await.transpose().ok().flatten(); - if payload.is_none_or(|p| p.block().body().transactions().is_empty()) { - assert!( - start.elapsed() < std::time::Duration::from_secs(30), - "timed out waiting for a non-empty payload for {payload_id} — \ - check that the chain spec supports all generated tx types" - ); - tokio::time::sleep(std::time::Duration::from_millis(20)).await; - continue - } - // Resolve payload once its built - self.payload_builder - .resolve_kind(payload_id, PayloadKind::Earliest) - .await - .unwrap() - .unwrap(); - break; - } + self.payload_builder + .resolve_kind(payload_id, PayloadKind::WaitForPending) + .await + .unwrap() + .unwrap(); } /// Expects the next event to be a built payload event or panics From 4ffde69d94a96b29dcf12d955b0880156df5109b Mon Sep 17 00:00:00 2001 From: Derek Cofausper <256792747+decofe@users.noreply.github.com> Date: Wed, 29 Apr 2026 08:29:42 -0700 Subject: [PATCH 003/335] fix(engine): apply finalized state after syncing FCU head import (#23838) Co-authored-by: Centaur AI Co-authored-by: Amp Co-authored-by: klkvr --- crates/engine/primitives/src/forkchoice.rs | 22 ++++++-- crates/engine/tree/src/tree/mod.rs | 28 ++++++++++ crates/engine/tree/src/tree/tests.rs | 62 ++++++++++++++++++++++ 3 files changed, 108 insertions(+), 4 deletions(-) diff --git a/crates/engine/primitives/src/forkchoice.rs b/crates/engine/primitives/src/forkchoice.rs index 69cb5990711..533bbeb41bb 100644 --- a/crates/engine/primitives/src/forkchoice.rs +++ b/crates/engine/primitives/src/forkchoice.rs @@ -21,7 +21,8 @@ impl ForkchoiceStateTracker { /// `sync_target` to `None`, since we're now fully synced. pub const fn set_latest(&mut self, state: ForkchoiceState, status: ForkchoiceStatus) { if status.is_valid() { - self.set_valid(state); + self.last_syncing = None; + self.last_valid = Some(state); } else if status.is_syncing() { self.last_syncing = Some(state); } @@ -30,11 +31,24 @@ impl ForkchoiceStateTracker { self.latest = Some(received); } - const fn set_valid(&mut self, state: ForkchoiceState) { - // we no longer need to sync to this state. + /// Promotes a previously tracked syncing forkchoice state to valid, without overwriting a + /// newer `latest` state. + /// + /// This is used when a `Syncing` FCU's head finally becomes canonical via the downloaded-block + /// flow, so the safe/finalized anchors of that FCU can be applied. Unlike + /// [`Self::set_latest`], this preserves a newer `latest` (e.g. an `Invalid` FCU received + /// after the syncing one) and only flips `latest` to `Valid` when it still refers to the same + /// syncing FCU being promoted. + pub fn promote_sync_target_to_valid(&mut self, state: ForkchoiceState) { self.last_syncing = None; - self.last_valid = Some(state); + + if let Some(received) = self.latest.as_mut() && + received.state == state && + received.status.is_syncing() + { + received.status = ForkchoiceStatus::Valid; + } } /// Returns the [`ForkchoiceStatus`] of the latest received FCU. diff --git a/crates/engine/tree/src/tree/mod.rs b/crates/engine/tree/src/tree/mod.rs index 7837063decc..1edf1496e23 100644 --- a/crates/engine/tree/src/tree/mod.rs +++ b/crates/engine/tree/src/tree/mod.rs @@ -1917,9 +1917,37 @@ where self.on_canonical_chain_update(chain_update); } + self.on_canonicalized_sync_target(target); + Ok(()) } + /// Applies the tracked forkchoice state once its sync target head becomes canonical. + fn on_canonicalized_sync_target(&mut self, target: B256) { + let Some(sync_target_state) = self + .state + .forkchoice_state_tracker + .sync_target_state() + .filter(|state| state.head_block_hash == target) + else { + return; + }; + + if let Err(outcome) = self.ensure_consistent_forkchoice_state(sync_target_state) { + debug!( + target: "engine::tree", + head = %sync_target_state.head_block_hash, + safe = %sync_target_state.safe_block_hash, + finalized = %sync_target_state.finalized_block_hash, + ?outcome, + "Canonicalized sync target head before safe/finalized could be applied" + ); + return; + } + + self.state.forkchoice_state_tracker.promote_sync_target_to_valid(sync_target_state); + } + /// Convenience function to handle an optional tree event. fn on_maybe_tree_event(&mut self, event: Option) -> ProviderResult<()> { if let Some(event) = event { diff --git a/crates/engine/tree/src/tree/tests.rs b/crates/engine/tree/src/tree/tests.rs index 552a21d11a5..193200f2a32 100644 --- a/crates/engine/tree/src/tree/tests.rs +++ b/crates/engine/tree/src/tree/tests.rs @@ -2254,3 +2254,65 @@ fn test_on_valid_downloaded_head_sync_target_returns_make_canonical() { other => panic!("Expected MakeCanonical for head block, got: {other:?}"), } } + +/// Tests that canonicalizing a downloaded sync target head also applies the tracked finalized +/// block from the original `SYNCING` forkchoice state. +#[test] +fn test_canonicalizing_downloaded_sync_target_head_updates_finalized() { + reth_tracing::init_test_tracing(); + + let chain_spec = MAINNET.clone(); + let mut test_harness = TestHarness::new(chain_spec); + + let blocks: Vec<_> = test_harness.block_builder.get_executed_blocks(0..3).collect(); + let genesis = &blocks[0]; + let finalized_block = &blocks[1]; + let head_block = &blocks[2]; + + test_harness = test_harness.with_blocks(vec![ + genesis.clone(), + finalized_block.clone(), + head_block.clone(), + ]); + + let finalized_num_hash = finalized_block.recovered_block().num_hash(); + let head_num_hash = head_block.recovered_block().num_hash(); + + test_harness.tree.state.tree_state.set_canonical_head(genesis.recovered_block().num_hash()); + + let fcu_state = ForkchoiceState { + head_block_hash: head_num_hash.hash, + safe_block_hash: head_num_hash.hash, + finalized_block_hash: finalized_num_hash.hash, + }; + test_harness + .tree + .state + .forkchoice_state_tracker + .set_latest(fcu_state, ForkchoiceStatus::Syncing); + + let event = test_harness + .tree + .on_valid_downloaded_block(head_num_hash) + .unwrap() + .expect("expected canonicalization event for sync target head"); + + test_harness.tree.on_tree_event(event).unwrap(); + + assert_eq!(test_harness.tree.state.tree_state.canonical_block_hash(), head_num_hash.hash); + assert_eq!( + test_harness.tree.canonical_in_memory_state.get_finalized_num_hash(), + Some(finalized_num_hash), + "Finalized block from the syncing FCU should be applied once the head becomes canonical" + ); + assert_eq!( + test_harness.tree.canonical_in_memory_state.get_safe_num_hash(), + Some(head_num_hash), + "Safe block from the syncing FCU should be applied once the head becomes canonical" + ); + assert_eq!( + test_harness.tree.state.forkchoice_state_tracker.last_valid_state(), + Some(fcu_state) + ); + assert!(test_harness.tree.state.forkchoice_state_tracker.sync_target_state().is_none()); +} From d25de30050c0cf89e97a08222ae108f81fa2d4c7 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Thu, 30 Apr 2026 02:59:10 +0400 Subject: [PATCH 004/335] feat: customizable discovery defaults (#23843) Co-authored-by: Matthias Seitz --- crates/node/core/src/args/mod.rs | 2 +- crates/node/core/src/args/network.rs | 267 +++++++++++++++---- docs/vocs/docs/pages/cli/reth/node.mdx | 4 +- docs/vocs/docs/pages/cli/reth/p2p/body.mdx | 4 +- docs/vocs/docs/pages/cli/reth/p2p/header.mdx | 4 +- docs/vocs/docs/pages/cli/reth/stage/run.mdx | 4 +- 6 files changed, 230 insertions(+), 55 deletions(-) diff --git a/crates/node/core/src/args/mod.rs b/crates/node/core/src/args/mod.rs index 3703d4144f0..6d6fbbd0078 100644 --- a/crates/node/core/src/args/mod.rs +++ b/crates/node/core/src/args/mod.rs @@ -2,7 +2,7 @@ /// NetworkArg struct for configuring the network mod network; -pub use network::{DefaultNetworkArgs, DiscoveryArgs, NetworkArgs}; +pub use network::{DefaultDiscoveryArgs, DefaultNetworkArgs, DiscoveryArgs, NetworkArgs}; /// RpcServerArg struct for configuring the RPC mod rpc_server; diff --git a/crates/node/core/src/args/network.rs b/crates/node/core/src/args/network.rs index 614bcf30259..b682633805e 100644 --- a/crates/node/core/src/args/network.rs +++ b/crates/node/core/src/args/network.rs @@ -11,7 +11,10 @@ use std::{ }; use crate::version::version_metadata; -use clap::Args; +use clap::{ + builder::{OsStr, Resettable}, + Args, +}; use reth_chainspec::EthChainSpec; use reth_cli_util::{get_secret_key, load_secret_key::SecretKeyError}; use reth_config::Config; @@ -569,15 +572,8 @@ impl NetworkArgs { let rlpx_socket = (addr, self.port).into(); self.discovery.apply_to_builder(builder, rlpx_socket, chain_bootnodes) }) - .listener_addr(SocketAddr::new( - addr, // set discovery port based on instance number - self.port, - )) - .discovery_addr(SocketAddr::new( - self.discovery.addr, - // set discovery port based on instance number - self.discovery.port, - )) + .listener_addr(SocketAddr::new(addr, self.port)) + .discovery_addr(SocketAddr::new(self.discovery.addr, self.discovery.port)) .disable_tx_gossip(self.disable_tx_gossip) .required_block_hashes(self.required_block_hashes.clone()) .eth_max_message_size_opt(self.eth_max_message_size.map(NonZeroUsize::get)) @@ -727,19 +723,172 @@ impl Default for NetworkArgs { } } +/// Global static discovery defaults +static DISCOVERY_DEFAULTS: OnceLock = OnceLock::new(); + +/// Default values for discovery CLI arguments that can be customized. +#[derive(Debug, Clone, Copy)] +pub struct DefaultDiscoveryArgs { + /// Default for `--disable-discovery`. + pub disable_discovery: bool, + /// Default for `--disable-dns-discovery`. + pub disable_dns_discovery: bool, + /// Default for `--disable-discv4-discovery`. + pub disable_discv4_discovery: bool, + /// Default for `--disable-discv5-discovery`. + pub disable_discv5_discovery: bool, + /// Default for `--disable-nat`. + pub disable_nat: bool, + /// Default UDP address for devp2p discovery v4. + pub addr: IpAddr, + /// Default UDP port for devp2p discovery v4. + pub port: u16, + /// Default UDP IPv4 address for devp2p discovery v5. + pub discv5_addr: Option, + /// Default UDP IPv6 address for devp2p discovery v5. + pub discv5_addr_ipv6: Option, + /// Default UDP IPv4 port for devp2p discovery v5. + pub discv5_port: Option, + /// Default UDP IPv6 port for devp2p discovery v5. + pub discv5_port_ipv6: Option, + /// Default discv5 periodic lookup interval (seconds). + pub discv5_lookup_interval: u64, + /// Default discv5 bootstrap lookup interval (seconds). + pub discv5_bootstrap_lookup_interval: u64, + /// Default discv5 bootstrap lookup countdown. + pub discv5_bootstrap_lookup_countdown: u64, +} + +impl DefaultDiscoveryArgs { + /// Initialize the global discovery defaults with this configuration. + pub fn try_init(self) -> Result<(), Self> { + DISCOVERY_DEFAULTS.set(self) + } + + /// Get a reference to the global discovery defaults. + pub fn get_global() -> &'static Self { + DISCOVERY_DEFAULTS.get_or_init(Self::default) + } + + /// Set the default for `--disable-discovery`. + pub const fn with_disable_discovery(mut self, disable: bool) -> Self { + self.disable_discovery = disable; + self + } + + /// Set the default for `--disable-dns-discovery`. + pub const fn with_disable_dns_discovery(mut self, disable: bool) -> Self { + self.disable_dns_discovery = disable; + self + } + + /// Set the default for `--disable-discv4-discovery`. + pub const fn with_disable_discv4_discovery(mut self, disable: bool) -> Self { + self.disable_discv4_discovery = disable; + self + } + + /// Set the default for `--disable-discv5-discovery`. + pub const fn with_disable_discv5_discovery(mut self, disable: bool) -> Self { + self.disable_discv5_discovery = disable; + self + } + + /// Set the default for `--disable-nat`. + pub const fn with_disable_nat(mut self, disable: bool) -> Self { + self.disable_nat = disable; + self + } + + /// Set the default discovery v4 address. + pub const fn with_addr(mut self, addr: IpAddr) -> Self { + self.addr = addr; + self + } + + /// Set the default discovery v4 port. + pub const fn with_port(mut self, port: u16) -> Self { + self.port = port; + self + } + + /// Set the default discovery v5 IPv4 address. + pub fn with_discv5_addr(mut self, addr: impl Into>) -> Self { + self.discv5_addr = addr.into(); + self + } + + /// Set the default discovery v5 IPv6 address. + pub fn with_discv5_addr_ipv6(mut self, addr: impl Into>) -> Self { + self.discv5_addr_ipv6 = addr.into(); + self + } + + /// Set the default discovery V5 port. + pub fn with_discv5_port(mut self, port: impl Into>) -> Self { + self.discv5_port = port.into(); + self + } + + /// Set the default discovery v5 IPv6 port. + pub fn with_discv5_port_ipv6(mut self, port: impl Into>) -> Self { + self.discv5_port_ipv6 = port.into(); + self + } + + /// Set the default discv5 periodic lookup interval (seconds). + pub const fn with_discv5_lookup_interval(mut self, interval: u64) -> Self { + self.discv5_lookup_interval = interval; + self + } + + /// Set the default discv5 bootstrap lookup interval (seconds). + pub const fn with_discv5_bootstrap_lookup_interval(mut self, interval: u64) -> Self { + self.discv5_bootstrap_lookup_interval = interval; + self + } + + /// Set the default discv5 bootstrap lookup countdown. + pub const fn with_discv5_bootstrap_lookup_countdown(mut self, countdown: u64) -> Self { + self.discv5_bootstrap_lookup_countdown = countdown; + self + } +} + +impl Default for DefaultDiscoveryArgs { + fn default() -> Self { + Self { + disable_discovery: false, + disable_dns_discovery: false, + disable_discv4_discovery: false, + disable_discv5_discovery: false, + disable_nat: false, + addr: DEFAULT_DISCOVERY_ADDR, + port: DEFAULT_DISCOVERY_PORT, + discv5_addr: None, + discv5_addr_ipv6: None, + discv5_port: Some(DEFAULT_DISCOVERY_V5_PORT), + discv5_port_ipv6: Some(DEFAULT_DISCOVERY_V5_PORT), + discv5_lookup_interval: DEFAULT_SECONDS_LOOKUP_INTERVAL, + discv5_bootstrap_lookup_interval: DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL, + discv5_bootstrap_lookup_countdown: DEFAULT_COUNT_BOOTSTRAP_LOOKUPS, + } + } +} + /// Arguments to setup discovery #[derive(Debug, Clone, Args, PartialEq, Eq)] pub struct DiscoveryArgs { /// Disable the discovery service. - #[arg(short, long, default_value_if("dev", "true", "true"))] + #[arg(short, long, default_value_if("dev", "true", "true"), default_value_t = DefaultDiscoveryArgs::get_global().disable_discovery)] pub disable_discovery: bool, /// Disable the DNS discovery. - #[arg(long, conflicts_with = "disable_discovery")] + #[arg(long, conflicts_with = "disable_discovery", default_value_t = DefaultDiscoveryArgs::get_global().disable_dns_discovery)] pub disable_dns_discovery: bool, /// Disable Discv4 discovery. - #[arg(long, conflicts_with = "disable_discovery")] + #[arg(long, conflicts_with = "disable_discovery", default_value_t = DefaultDiscoveryArgs::get_global().disable_discv4_discovery)] pub disable_discv4_discovery: bool, /// Enable Discv5 discovery. @@ -750,57 +899,57 @@ pub struct DiscoveryArgs { pub enable_discv5_discovery: bool, /// Disable Discv5 discovery. - #[arg(long, conflicts_with = "disable_discovery")] + #[arg(long, conflicts_with = "disable_discovery", default_value_t = DefaultDiscoveryArgs::get_global().disable_discv5_discovery)] pub disable_discv5_discovery: bool, /// Disable Nat discovery. - #[arg(long, conflicts_with = "disable_discovery")] + #[arg(long, conflicts_with = "disable_discovery", default_value_t = DefaultDiscoveryArgs::get_global().disable_nat)] pub disable_nat: bool, /// The UDP address to use for devp2p peer discovery version 4. - #[arg(id = "discovery.addr", long = "discovery.addr", value_name = "DISCOVERY_ADDR", default_value_t = DEFAULT_DISCOVERY_ADDR)] + #[arg(id = "discovery.addr", long = "discovery.addr", value_name = "DISCOVERY_ADDR", default_value_t = DefaultDiscoveryArgs::get_global().addr)] pub addr: IpAddr, /// The UDP port to use for devp2p peer discovery version 4. - #[arg(id = "discovery.port", long = "discovery.port", value_name = "DISCOVERY_PORT", default_value_t = DEFAULT_DISCOVERY_PORT)] + #[arg(id = "discovery.port", long = "discovery.port", value_name = "DISCOVERY_PORT", default_value_t = DefaultDiscoveryArgs::get_global().port)] pub port: u16, /// The UDP IPv4 address to use for devp2p peer discovery version 5. Overwritten by `RLPx` /// address, if it's also IPv4. - #[arg(id = "discovery.v5.addr", long = "discovery.v5.addr", value_name = "DISCOVERY_V5_ADDR", default_value = None)] + #[arg(id = "discovery.v5.addr", long = "discovery.v5.addr", value_name = "DISCOVERY_V5_ADDR", default_value = Resettable::from(DefaultDiscoveryArgs::get_global().discv5_addr.map(|a| OsStr::from(a.to_string()))))] pub discv5_addr: Option, /// The UDP IPv6 address to use for devp2p peer discovery version 5. Overwritten by `RLPx` /// address, if it's also IPv6. - #[arg(id = "discovery.v5.addr.ipv6", long = "discovery.v5.addr.ipv6", value_name = "DISCOVERY_V5_ADDR_IPV6", default_value = None)] + #[arg(id = "discovery.v5.addr.ipv6", long = "discovery.v5.addr.ipv6", value_name = "DISCOVERY_V5_ADDR_IPV6", default_value = Resettable::from(DefaultDiscoveryArgs::get_global().discv5_addr_ipv6.map(|a| OsStr::from(a.to_string()))))] pub discv5_addr_ipv6: Option, /// The UDP IPv4 port to use for devp2p peer discovery version 5. Not used unless `--addr` is /// IPv4, or `--discovery.v5.addr` is set. - #[arg(id = "discovery.v5.port", long = "discovery.v5.port", value_name = "DISCOVERY_V5_PORT", - default_value_t = DEFAULT_DISCOVERY_V5_PORT)] - pub discv5_port: u16, + #[arg(id = "discovery.v5.port", long = "discovery.v5.port", value_name = "DISCOVERY_V5_PORT", default_value = Resettable::from(DefaultDiscoveryArgs::get_global().discv5_port.map(|p| OsStr::from(p.to_string()))))] + pub discv5_port: Option, /// The UDP IPv6 port to use for devp2p peer discovery version 5. Not used unless `--addr` is /// IPv6, or `--discovery.addr.ipv6` is set. - #[arg(id = "discovery.v5.port.ipv6", long = "discovery.v5.port.ipv6", value_name = "DISCOVERY_V5_PORT_IPV6", - default_value_t = DEFAULT_DISCOVERY_V5_PORT)] - pub discv5_port_ipv6: u16, + /// + /// If not provided, discovery V5 defaults to same port as discovery V4 (--discovery.port). + #[arg(id = "discovery.v5.port.ipv6", long = "discovery.v5.port.ipv6", value_name = "DISCOVERY_V5_PORT_IPV6", default_value = Resettable::from(DefaultDiscoveryArgs::get_global().discv5_port_ipv6.map(|p| OsStr::from(p.to_string()))))] + pub discv5_port_ipv6: Option, /// The interval in seconds at which to carry out periodic lookup queries, for the whole /// run of the program. - #[arg(id = "discovery.v5.lookup-interval", long = "discovery.v5.lookup-interval", value_name = "DISCOVERY_V5_LOOKUP_INTERVAL", default_value_t = DEFAULT_SECONDS_LOOKUP_INTERVAL)] + #[arg(id = "discovery.v5.lookup-interval", long = "discovery.v5.lookup-interval", value_name = "DISCOVERY_V5_LOOKUP_INTERVAL", default_value_t = DefaultDiscoveryArgs::get_global().discv5_lookup_interval)] pub discv5_lookup_interval: u64, /// The interval in seconds at which to carry out boost lookup queries, for a fixed number of /// times, at bootstrap. #[arg(id = "discovery.v5.bootstrap.lookup-interval", long = "discovery.v5.bootstrap.lookup-interval", value_name = "DISCOVERY_V5_BOOTSTRAP_LOOKUP_INTERVAL", - default_value_t = DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL)] + default_value_t = DefaultDiscoveryArgs::get_global().discv5_bootstrap_lookup_interval)] pub discv5_bootstrap_lookup_interval: u64, /// The number of times to carry out boost lookup queries at bootstrap. #[arg(id = "discovery.v5.bootstrap.lookup-countdown", long = "discovery.v5.bootstrap.lookup-countdown", value_name = "DISCOVERY_V5_BOOTSTRAP_LOOKUP_COUNTDOWN", - default_value_t = DEFAULT_COUNT_BOOTSTRAP_LOOKUPS)] + default_value_t = DefaultDiscoveryArgs::get_global().discv5_bootstrap_lookup_countdown)] pub discv5_bootstrap_lookup_countdown: u64, } @@ -850,6 +999,7 @@ impl DiscoveryArgs { discv5_lookup_interval, discv5_bootstrap_lookup_interval, discv5_bootstrap_lookup_countdown, + port, .. } = self; @@ -867,8 +1017,9 @@ impl DiscoveryArgs { let mut discv5_config_builder = reth_discv5::discv5::ConfigBuilder::new(ListenConfig::from_two_sockets( - discv5_addr_ipv4.map(|addr| SocketAddrV4::new(addr, *discv5_port)), - discv5_addr_ipv6.map(|addr| SocketAddrV6::new(addr, *discv5_port_ipv6, 0, 0)), + discv5_addr_ipv4.map(|addr| SocketAddrV4::new(addr, discv5_port.unwrap_or(*port))), + discv5_addr_ipv6 + .map(|addr| SocketAddrV6::new(addr, discv5_port_ipv6.unwrap_or(*port), 0, 0)), )); if has_discv5_addr_args || self.disable_nat { @@ -898,14 +1049,14 @@ impl DiscoveryArgs { /// discovery binds to the sockets. pub const fn with_unused_discovery_port(mut self) -> Self { self.port = 0; - self.discv5_port = 0; - self.discv5_port_ipv6 = 0; + self.discv5_port = Some(0); + self.discv5_port_ipv6 = Some(0); self } /// Set the discovery V5 port - pub const fn with_discv5_port(mut self, port: u16) -> Self { - self.discv5_port = port; + pub fn with_discv5_port(mut self, port: impl Into>) -> Self { + self.discv5_port = port.into(); self } @@ -917,29 +1068,45 @@ impl DiscoveryArgs { pub fn adjust_instance_ports(&mut self, instance: u16) { debug_assert_ne!(instance, 0, "instance must be non-zero"); self.port += instance - 1; - self.discv5_port += instance - 1; - self.discv5_port_ipv6 += instance - 1; + self.discv5_port = self.discv5_port.map(|port| port + instance - 1); + self.discv5_port_ipv6 = self.discv5_port_ipv6.map(|port| port + instance - 1); } } impl Default for DiscoveryArgs { fn default() -> Self { + let DefaultDiscoveryArgs { + disable_discovery, + disable_dns_discovery, + disable_discv4_discovery, + disable_discv5_discovery, + disable_nat, + addr, + port, + discv5_addr, + discv5_addr_ipv6, + discv5_port, + discv5_port_ipv6, + discv5_lookup_interval, + discv5_bootstrap_lookup_interval, + discv5_bootstrap_lookup_countdown, + } = *DefaultDiscoveryArgs::get_global(); Self { - disable_discovery: false, - disable_dns_discovery: false, - disable_discv4_discovery: false, + disable_discovery, + disable_dns_discovery, + disable_discv4_discovery, enable_discv5_discovery: false, - disable_discv5_discovery: false, - disable_nat: false, - addr: DEFAULT_DISCOVERY_ADDR, - port: DEFAULT_DISCOVERY_PORT, - discv5_addr: None, - discv5_addr_ipv6: None, - discv5_port: DEFAULT_DISCOVERY_V5_PORT, - discv5_port_ipv6: DEFAULT_DISCOVERY_V5_PORT, - discv5_lookup_interval: DEFAULT_SECONDS_LOOKUP_INTERVAL, - discv5_bootstrap_lookup_interval: DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL, - discv5_bootstrap_lookup_countdown: DEFAULT_COUNT_BOOTSTRAP_LOOKUPS, + disable_discv5_discovery, + disable_nat, + addr, + port, + discv5_addr, + discv5_addr_ipv6, + discv5_port, + discv5_port_ipv6, + discv5_lookup_interval, + discv5_bootstrap_lookup_interval, + discv5_bootstrap_lookup_countdown, } } } diff --git a/docs/vocs/docs/pages/cli/reth/node.mdx b/docs/vocs/docs/pages/cli/reth/node.mdx index 4e88bde832d..3d5a60beb57 100644 --- a/docs/vocs/docs/pages/cli/reth/node.mdx +++ b/docs/vocs/docs/pages/cli/reth/node.mdx @@ -115,7 +115,9 @@ Networking: [default: 9200] --discovery.v5.port.ipv6 - The UDP IPv6 port to use for devp2p peer discovery version 5. Not used unless `--addr` is IPv6, or `--discovery.addr.ipv6` is set + The UDP IPv6 port to use for devp2p peer discovery version 5. Not used unless `--addr` is IPv6, or `--discovery.addr.ipv6` is set. + + If not provided, discovery V5 defaults to same port as discovery V4 (--discovery.port). [default: 9200] diff --git a/docs/vocs/docs/pages/cli/reth/p2p/body.mdx b/docs/vocs/docs/pages/cli/reth/p2p/body.mdx index a12a5741da6..f55534e616a 100644 --- a/docs/vocs/docs/pages/cli/reth/p2p/body.mdx +++ b/docs/vocs/docs/pages/cli/reth/p2p/body.mdx @@ -55,7 +55,9 @@ Networking: [default: 9200] --discovery.v5.port.ipv6 - The UDP IPv6 port to use for devp2p peer discovery version 5. Not used unless `--addr` is IPv6, or `--discovery.addr.ipv6` is set + The UDP IPv6 port to use for devp2p peer discovery version 5. Not used unless `--addr` is IPv6, or `--discovery.addr.ipv6` is set. + + If not provided, discovery V5 defaults to same port as discovery V4 (--discovery.port). [default: 9200] diff --git a/docs/vocs/docs/pages/cli/reth/p2p/header.mdx b/docs/vocs/docs/pages/cli/reth/p2p/header.mdx index da60a42d03d..33f17e8e80a 100644 --- a/docs/vocs/docs/pages/cli/reth/p2p/header.mdx +++ b/docs/vocs/docs/pages/cli/reth/p2p/header.mdx @@ -55,7 +55,9 @@ Networking: [default: 9200] --discovery.v5.port.ipv6 - The UDP IPv6 port to use for devp2p peer discovery version 5. Not used unless `--addr` is IPv6, or `--discovery.addr.ipv6` is set + The UDP IPv6 port to use for devp2p peer discovery version 5. Not used unless `--addr` is IPv6, or `--discovery.addr.ipv6` is set. + + If not provided, discovery V5 defaults to same port as discovery V4 (--discovery.port). [default: 9200] diff --git a/docs/vocs/docs/pages/cli/reth/stage/run.mdx b/docs/vocs/docs/pages/cli/reth/stage/run.mdx index 667c4cb2c45..8d4e7411e68 100644 --- a/docs/vocs/docs/pages/cli/reth/stage/run.mdx +++ b/docs/vocs/docs/pages/cli/reth/stage/run.mdx @@ -208,7 +208,9 @@ Networking: [default: 9200] --discovery.v5.port.ipv6 - The UDP IPv6 port to use for devp2p peer discovery version 5. Not used unless `--addr` is IPv6, or `--discovery.addr.ipv6` is set + The UDP IPv6 port to use for devp2p peer discovery version 5. Not used unless `--addr` is IPv6, or `--discovery.addr.ipv6` is set. + + If not provided, discovery V5 defaults to same port as discovery V4 (--discovery.port). [default: 9200] From fcfa8287f69a3509fcfef95d1b4dc8948ec86665 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Thu, 30 Apr 2026 05:19:09 +0200 Subject: [PATCH 005/335] chore(mdbx): replace deprecated MDBX_NOTLS with MDBX_NOSTICKYTHREADS (#23378) --- crates/storage/libmdbx-rs/mdbx-sys/build.rs | 1 + crates/storage/libmdbx-rs/src/flags.rs | 2 +- crates/storage/libmdbx-rs/src/txn_pool.rs | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/storage/libmdbx-rs/mdbx-sys/build.rs b/crates/storage/libmdbx-rs/mdbx-sys/build.rs index c265d02e233..d3d3234e1bc 100644 --- a/crates/storage/libmdbx-rs/mdbx-sys/build.rs +++ b/crates/storage/libmdbx-rs/mdbx-sys/build.rs @@ -97,6 +97,7 @@ fn generate_bindings(mdbx: &Path, out_file: &Path) { let bindings = bindgen::Builder::default() .header(mdbx.join("mdbx.h").to_string_lossy()) .allowlist_var("^(MDBX|mdbx)_.*") + .blocklist_item("MDBX_NOTLS") .allowlist_type("^(MDBX|mdbx)_.*") .allowlist_function("^(MDBX|mdbx)_.*") .size_t_is_usize(true) diff --git a/crates/storage/libmdbx-rs/src/flags.rs b/crates/storage/libmdbx-rs/src/flags.rs index 6aefab57b19..d1f7da4fd49 100644 --- a/crates/storage/libmdbx-rs/src/flags.rs +++ b/crates/storage/libmdbx-rs/src/flags.rs @@ -199,7 +199,7 @@ impl EnvironmentFlags { flags |= ffi::MDBX_LIFORECLAIM; } - flags |= ffi::MDBX_NOTLS; + flags |= ffi::MDBX_NOSTICKYTHREADS; flags } diff --git a/crates/storage/libmdbx-rs/src/txn_pool.rs b/crates/storage/libmdbx-rs/src/txn_pool.rs index f8d09a3a6b4..e429ba124c2 100644 --- a/crates/storage/libmdbx-rs/src/txn_pool.rs +++ b/crates/storage/libmdbx-rs/src/txn_pool.rs @@ -3,8 +3,8 @@ use crossbeam_queue::ArrayQueue; /// Lock-free pool of reset read-only MDBX transaction handles. /// -/// With `MDBX_NOTLS` (which reth always sets), every `mdbx_txn_begin_ex` for a read transaction -/// calls `mvcc_bind_slot`, which acquires `lck_rdt_lock` — a pthread mutex. Under high +/// With `MDBX_NOSTICKYTHREADS` (which reth always sets), every `mdbx_txn_begin_ex` for a read +/// transaction calls `mvcc_bind_slot`, which acquires `lck_rdt_lock` — a pthread mutex. Under high /// concurrency (e.g., prewarming), this becomes a contention point. /// /// This pool caches transaction handles that have been reset via `mdbx_txn_reset`. A reset handle From 0eeb8c5ef7e6df23afd7ef2d413246d9131d754c Mon Sep 17 00:00:00 2001 From: figtracer Date: Thu, 30 Apr 2026 12:10:12 +0100 Subject: [PATCH 006/335] fix(net): prevent eth/68 tx request packing overflow (#23848) --- .../net/network/src/transactions/fetcher.rs | 48 +++++++++++++++++-- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/crates/net/network/src/transactions/fetcher.rs b/crates/net/network/src/transactions/fetcher.rs index 3310c3eef16..dd7f46592b3 100644 --- a/crates/net/network/src/transactions/fetcher.rs +++ b/crates/net/network/src/transactions/fetcher.rs @@ -283,11 +283,12 @@ impl TransactionFetcher { unreachable!("this method is called upon reception of an eth68 announcement") }; - let next_acc_size = acc_size_response + size; + let next_acc_size = acc_size_response.checked_add(size).filter(|next_acc_size| { + *next_acc_size <= + self.info.soft_limit_byte_size_pooled_transactions_response_on_pack_request + }); - if next_acc_size <= - self.info.soft_limit_byte_size_pooled_transactions_response_on_pack_request - { + if let Some(next_acc_size) = next_acc_size { // only update accumulated size of tx response if tx will fit in without exceeding // soft limit acc_size_response = next_acc_size; @@ -721,7 +722,7 @@ impl TransactionFetcher { .and_then(|entry| entry.tx_encoded_len()) .unwrap_or(AVERAGE_BYTE_SIZE_TX_ENCODED); - acc_size_response += size; + acc_size_response = acc_size_response.saturating_add(size); // 4. Check if acc size or hashes count is at limit, if so stop looping. // if expected response is full enough or the number of hashes in the request is @@ -1368,6 +1369,43 @@ mod test { assert_eq!(expected_surplus_hashes, surplus_eth68_hashes); } + #[test] + fn pack_eth68_request_does_not_overflow_announced_size() { + reth_tracing::init_test_tracing(); + + let tx_fetcher = &mut TransactionFetcher::::default(); + + let eth68_hashes = + [B256::from_slice(&[1; 32]), B256::from_slice(&[2; 32]), B256::from_slice(&[3; 32])]; + let eth68_sizes = [ + DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESP_ON_PACK_GET_POOLED_TRANSACTIONS_REQ - MEDIAN_BYTE_SIZE_SMALL_LEGACY_TX_ENCODED - 1, + usize::MAX, + 2, + ]; + + let expected_request_hashes = + [eth68_hashes[0], eth68_hashes[2]].into_iter().collect::>(); + let expected_surplus_hashes = std::iter::once(eth68_hashes[1]).collect::>(); + + let mut eth68_hashes_to_request = RequestTxHashes::with_capacity(3); + let valid_announcement_data = TestValidAnnouncementData( + eth68_hashes + .into_iter() + .zip(eth68_sizes) + .map(|(hash, size)| (hash, Some((0u8, size)))) + .collect::>(), + ); + + let surplus_eth68_hashes = + tx_fetcher.pack_request_eth68(&mut eth68_hashes_to_request, valid_announcement_data); + + let eth68_hashes_to_request = eth68_hashes_to_request.into_iter().collect::>(); + let surplus_eth68_hashes = surplus_eth68_hashes.into_iter().collect::>(); + + assert_eq!(expected_request_hashes, eth68_hashes_to_request); + assert_eq!(expected_surplus_hashes, surplus_eth68_hashes); + } + #[tokio::test] async fn test_on_fetch_pending_hashes() { reth_tracing::init_test_tracing(); From fa2279ff4cedeb6b2c8b25df129aef9d8d48b9bc Mon Sep 17 00:00:00 2001 From: Derek Cofausper <256792747+decofe@users.noreply.github.com> Date: Thu, 30 Apr 2026 05:12:26 -0700 Subject: [PATCH 007/335] chore: default to min-trace-logs (#23851) Co-authored-by: Centaur AI Co-authored-by: Amp --- Makefile | 2 +- bin/reth-bb/Cargo.toml | 7 ++++++- bin/reth-bench/README.md | 2 +- bin/reth/Cargo.toml | 2 +- docs/vocs/docs/pages/installation/source.mdx | 4 ++-- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index fd04a20d35c..65db2e6d8b3 100644 --- a/Makefile +++ b/Makefile @@ -245,7 +245,7 @@ maxperf: ## Builds `reth` with the most aggressive optimisations. .PHONY: maxperf-no-asm maxperf-no-asm: ## Builds `reth` with the most aggressive optimisations, minus the "asm-keccak" feature. - RUSTFLAGS="-C target-cpu=native" cargo build --profile maxperf --no-default-features --features jemalloc,min-debug-logs,otlp,otlp-logs,reth-revm/portable,js-tracer,keccak-cache-global,rocksdb + RUSTFLAGS="-C target-cpu=native" cargo build --profile maxperf --no-default-features --features jemalloc,min-trace-logs,otlp,otlp-logs,reth-revm/portable,js-tracer,keccak-cache-global,rocksdb fmt: cargo +nightly fmt diff --git a/bin/reth-bb/Cargo.toml b/bin/reth-bb/Cargo.toml index d0e5526bb45..eb3ab1d0ce1 100644 --- a/bin/reth-bb/Cargo.toml +++ b/bin/reth-bb/Cargo.toml @@ -70,7 +70,7 @@ default = [ "reth-cli-util/jemalloc", "asm-keccak", "keccak-cache-global", - "min-debug-logs", + "min-trace-logs", ] jemalloc = [ @@ -101,6 +101,11 @@ min-debug-logs = [ "reth-ethereum-cli/min-debug-logs", "reth-node-core/min-debug-logs", ] +min-trace-logs = [ + "tracing/release_max_level_trace", + "reth-ethereum-cli/min-trace-logs", + "reth-node-core/min-trace-logs", +] [[bin]] name = "reth-bb" diff --git a/bin/reth-bench/README.md b/bin/reth-bench/README.md index aa63c6ec336..f44245e82b0 100644 --- a/bin/reth-bench/README.md +++ b/bin/reth-bench/README.md @@ -86,7 +86,7 @@ RUSTFLAGS="-C target-cpu=native" cargo build --profile profiling --features "jem Finally, if the purpose of the benchmark is to profile the node when `snmalloc` is configured as the default allocator, it would be built with the following command: ```bash -RUSTFLAGS="-C target-cpu=native" cargo build --profile profiling --no-default-features --features "snmalloc-native,asm-keccak,min-debug-logs" +RUSTFLAGS="-C target-cpu=native" cargo build --profile profiling --no-default-features --features "snmalloc-native,asm-keccak,min-trace-logs" ``` ### Run the Benchmark: diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index 424f93a5d23..471bc8293ed 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -88,7 +88,7 @@ default = [ "js-tracer", "keccak-cache-global", "asm-keccak", - "min-debug-logs", + "min-trace-logs", ] otlp = [ diff --git a/docs/vocs/docs/pages/installation/source.mdx b/docs/vocs/docs/pages/installation/source.mdx index d6283d83350..fb34e5a0d3a 100644 --- a/docs/vocs/docs/pages/installation/source.mdx +++ b/docs/vocs/docs/pages/installation/source.mdx @@ -110,11 +110,11 @@ RUSTFLAGS="-C target-cpu=native" cargo build --profile maxperf **Features** -The following performance features are enabled by default: `jemalloc` (except on Windows), `asm-keccak`, and `min-debug-logs`. +The following performance features are enabled by default: `jemalloc` (except on Windows), `asm-keccak`, and `min-trace-logs`. Some additional optional features are available: -- `min-LEVEL-logs`, where `LEVEL` is one of `error`, `warn`, `info`, `debug`, `trace`: disables compilation of logs of lower level than the given one; `min-debug-logs` is enabled by default +- `min-LEVEL-logs`, where `LEVEL` is one of `error`, `warn`, `info`, `debug`, `trace`: disables compilation of logs of lower level than the given one; `min-trace-logs` is enabled by default You can build with maximum performance optimizations using: From e98fb4af981d84ff2c50f6f86d863060a77484e6 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 30 Apr 2026 14:15:41 +0200 Subject: [PATCH 008/335] feat(engine): convert built payload to execution data (#23859) --- .../ethereum/engine-primitives/src/payload.rs | 106 +++++++++++++++++- 1 file changed, 103 insertions(+), 3 deletions(-) diff --git a/crates/ethereum/engine-primitives/src/payload.rs b/crates/ethereum/engine-primitives/src/payload.rs index 0318b095ef9..1e9a9eaaf59 100644 --- a/crates/ethereum/engine-primitives/src/payload.rs +++ b/crates/ethereum/engine-primitives/src/payload.rs @@ -8,9 +8,11 @@ use alloy_eips::{ }; use alloy_primitives::{Bytes, U256}; use alloy_rpc_types_engine::{ - BlobsBundleV1, BlobsBundleV2, ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, - ExecutionPayloadEnvelopeV4, ExecutionPayloadEnvelopeV5, ExecutionPayloadEnvelopeV6, - ExecutionPayloadFieldV2, ExecutionPayloadV1, ExecutionPayloadV3, ExecutionPayloadV4, + BlobsBundleV1, BlobsBundleV2, CancunPayloadFields, ExecutionData, ExecutionPayload, + ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, ExecutionPayloadEnvelopeV4, + ExecutionPayloadEnvelopeV5, ExecutionPayloadEnvelopeV6, ExecutionPayloadFieldV2, + ExecutionPayloadSidecar, ExecutionPayloadV1, ExecutionPayloadV3, ExecutionPayloadV4, + PraguePayloadFields, }; use reth_ethereum_primitives::EthPrimitives; use reth_payload_primitives::BuiltPayload; @@ -189,6 +191,39 @@ impl EthBuiltPayload { execution_requests: requests.unwrap_or_default(), }) } + + /// Converts built payload into [`ExecutionData`]. + pub fn into_execution_data(self) -> ExecutionData { + let Self { block, requests, block_access_list, .. } = self; + let block_hash = block.hash(); + let block = Arc::unwrap_or_clone(block).into_block(); + + let (payload, sidecar) = ExecutionPayload::from_block_unchecked_with_extras( + block_hash, + &block, + block_access_list, + ); + + let sidecar = if let Some(requests) = requests { + block.header.parent_beacon_block_root.map_or(sidecar, |parent_beacon_block_root| { + ExecutionPayloadSidecar::v4( + CancunPayloadFields { + parent_beacon_block_root, + versioned_hashes: block + .body + .blob_versioned_hashes_iter() + .copied() + .collect(), + }, + PraguePayloadFields::new(requests), + ) + }) + } else { + sidecar + }; + + ExecutionData::new(payload, sidecar) + } } impl BuiltPayload for EthBuiltPayload { @@ -264,6 +299,12 @@ impl TryFrom for ExecutionPayloadEnvelopeV6 { } } +impl From for ExecutionData { + fn from(value: EthBuiltPayload) -> Self { + value.into_execution_data() + } +} + /// An enum representing blob transaction sidecars belonging to [`EthBuiltPayload`]. #[derive(Clone, Default, Debug)] pub enum BlobSidecars { @@ -350,3 +391,62 @@ impl From> for BlobSidecars value.collect::>().into() } } + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::B256; + use reth_primitives_traits::Block as _; + + #[test] + fn into_execution_data_preserves_requests() { + let requests = Requests::from_requests([Bytes::from_static(&[1, 2])]); + let parent_beacon_block_root = B256::with_last_byte(1); + + let mut block = reth_ethereum_primitives::Block::default(); + block.header.parent_beacon_block_root = Some(parent_beacon_block_root); + block.header.requests_hash = Some(requests.requests_hash()); + + let payload = EthBuiltPayload::new( + Arc::new(block.seal_slow()), + U256::ZERO, + Some(requests.clone()), + None, + ); + + let execution_data: ExecutionData = payload.into(); + + assert_eq!( + execution_data.sidecar.parent_beacon_block_root(), + Some(parent_beacon_block_root) + ); + assert_eq!(execution_data.sidecar.requests(), Some(&requests)); + } + + #[test] + fn into_execution_data_preserves_block_access_list() { + let block_access_list = Bytes::from_static(&[0xc0]); + + let mut block = reth_ethereum_primitives::Block::default(); + block.header.parent_beacon_block_root = Some(B256::ZERO); + block.header.block_access_list_hash = Some(B256::ZERO); + + let payload = EthBuiltPayload::new( + Arc::new(block.seal_slow()), + U256::ZERO, + None, + Some(block_access_list.clone()), + ); + + let execution_data: ExecutionData = payload.into(); + + assert_eq!(execution_data.payload.block_access_list(), Some(&block_access_list)); + } + + #[test] + fn execution_data_has_infallible_try_from_impl() { + fn assert_try_from>() {} + + assert_try_from::(); + } +} From 8d7c15f38e7358b7bef9ee01d107fe0f49d6a523 Mon Sep 17 00:00:00 2001 From: Karl Yu <43113774+0xKarl98@users.noreply.github.com> Date: Fri, 1 May 2026 14:23:53 +0800 Subject: [PATCH 009/335] feat(net): add optional BAL fetching for block ranges (#23779) Co-authored-by: Matthias Seitz --- crates/net/p2p/src/full_block.rs | 379 ++++++++++++++++++++++++++++--- crates/net/p2p/src/lib.rs | 1 + crates/net/peers/src/lib.rs | 24 +- 3 files changed, 374 insertions(+), 30 deletions(-) diff --git a/crates/net/p2p/src/full_block.rs b/crates/net/p2p/src/full_block.rs index 45bedcbd39c..22055d6bf62 100644 --- a/crates/net/p2p/src/full_block.rs +++ b/crates/net/p2p/src/full_block.rs @@ -1,6 +1,6 @@ use super::headers::client::HeadersRequest; use crate::{ - block_access_lists::client::BlockAccessListsClient, + block_access_lists::client::{BalRequirement, BlockAccessListsClient}, bodies::client::{BodiesClient, SingleBodyRequest}, download::DownloadClient, error::PeerRequestResult, @@ -30,6 +30,10 @@ use std::{ }; use tracing::debug; +/// Response returned by [`FetchFullBlockRangeWithOptionalAccessListsFuture`]. +pub type FullBlockRangeWithOptionalAccessListsResponse = + (Vec>, Option); + /// A Client that can fetch full blocks from the network. #[derive(Debug, Clone)] pub struct FullBlockClient @@ -72,7 +76,7 @@ where /// Returns a future that fetches [`SealedBlock`]s for the given hash and count. /// - /// Note: this future is cancel safe + /// Note: this future is cancel safe. /// /// Caution: This does no validation of body (transactions) responses but guarantees that /// the starting [`SealedHeader`] matches the requested hash, and that the number of headers and @@ -101,25 +105,6 @@ where } } -impl FetchFullBlockFuture -where - Client: BlockClient, -{ - fn new(client: Client, consensus: Arc>, hash: B256) -> Self { - Self { - hash, - consensus, - request: FullBlockRequest { - header: Some(client.get_header(hash.into())), - body: Some(client.get_block_body(hash)), - }, - client, - header: None, - body: None, - } - } -} - impl FullBlockClient where Client: BlockClient + BlockAccessListsClient, @@ -142,6 +127,26 @@ where bal_request_state: BalRequestState::Pending(client.get_block_access_lists(vec![hash])), } } + + /// Returns a future that fetches [`SealedBlock`]s and optionally their [`BlockAccessLists`] for + /// the given hash and count. + /// + /// The block range is always the primary result. Access lists are requested after the block + /// range is downloaded. `Some` may contain a partial prefix when the peer truncates the BAL + /// response, while `None` means the optional BAL lookup failed or was unavailable. + pub fn get_full_block_range_with_optional_access_lists( + &self, + hash: B256, + count: u64, + ) -> FetchFullBlockRangeWithOptionalAccessListsFuture { + let client = self.client.clone(); + FetchFullBlockRangeWithOptionalAccessListsFuture { + blocks: self.get_full_block_range(hash, count), + client, + block_result: None, + access_lists: OptionalBlockAccessListsState::WaitingForBlocks, + } + } } /// A future that downloads a full block from the network. @@ -165,6 +170,20 @@ impl FetchFullBlockFuture where Client: BlockClient, { + fn new(client: Client, consensus: Arc>, hash: B256) -> Self { + Self { + hash, + consensus, + request: FullBlockRequest { + header: Some(client.get_header(hash.into())), + body: Some(client.get_block_body(hash)), + }, + client, + header: None, + body: None, + } + } + /// Returns the hash of the block being requested. pub const fn hash(&self) -> &B256 { &self.hash @@ -435,6 +454,145 @@ impl BalRequestState { } } +/// A future that downloads a range of full blocks and optionally their block access lists from the +/// network. +/// +/// This composes the existing full block range downloader with a follow-up optional block +/// access-list request, so callers that only need blocks can keep using +/// [`FetchFullBlockRangeFuture`]. +#[must_use = "futures do nothing unless polled"] +#[expect(missing_debug_implementations)] +pub struct FetchFullBlockRangeWithOptionalAccessListsFuture +where + Client: BlockClient + BlockAccessListsClient, +{ + blocks: FetchFullBlockRangeFuture, + client: Client, + block_result: Option>>, + access_lists: OptionalBlockAccessListsState<::Output>, +} + +impl FetchFullBlockRangeWithOptionalAccessListsFuture +where + Client: BlockClient + + BlockAccessListsClient, +{ + fn start_access_lists_request_if_possible(&mut self) { + if !matches!(self.access_lists, OptionalBlockAccessListsState::WaitingForBlocks) { + return + } + + // BALs are requested by block hash, so wait until the block range is fully assembled. + let Some(blocks) = self.block_result.as_ref() else { return }; + let hashes = blocks.iter().map(|block| block.hash()).collect::>(); + self.access_lists = OptionalBlockAccessListsState::Pending( + self.client.get_block_access_lists_with_requirement(hashes, BalRequirement::Optional), + ); + } + + fn on_access_lists_response(&mut self, response: WithPeerId) { + let (peer, access_lists) = response.split(); + let expected = + self.block_result.as_ref().expect("blocks should be ready before BAL response").len(); + let received = access_lists.0.len(); + + if received > expected { + debug!( + target: "downloaders", + start_hash = ?self.blocks.start_hash(), + expected, + received, + "Received wrong access list range response", + ); + self.client.report_bad_message(peer); + self.access_lists = OptionalBlockAccessListsState::Ready(None); + return + } + + // Short BAL responses are allowed by the wire protocol. Preserve the returned prefix and + // let callers decide whether they need to fetch the remaining entries. + self.access_lists = OptionalBlockAccessListsState::Ready(Some(access_lists)); + } + + /// Starts and polls the optional BAL request once, if it is ready to make progress. + fn poll_access_lists(&mut self, cx: &mut Context<'_>) { + self.start_access_lists_request_if_possible(); + + let poll = match &mut self.access_lists { + OptionalBlockAccessListsState::Pending(fut) => fut.poll_unpin(cx), + OptionalBlockAccessListsState::WaitingForBlocks | + OptionalBlockAccessListsState::Ready(_) => return, + }; + + match poll { + Poll::Pending => {} + Poll::Ready(Ok(access_lists)) => self.on_access_lists_response(access_lists), + Poll::Ready(Err(err)) => { + debug!( + target: "downloaders", + %err, + start_hash = ?self.blocks.start_hash(), + "Access list range download failed", + ); + + // Optional BAL lookup is best-effort: missing eth/71 support or request failures + // should not block returning the downloaded block range. + self.access_lists = OptionalBlockAccessListsState::Ready(None); + } + } + } + + /// Returns the block range once blocks and the optional BAL lookup are both complete. + fn take_response( + &mut self, + ) -> Option> { + let OptionalBlockAccessListsState::Ready(access_lists) = &mut self.access_lists else { + return None + }; + + let blocks = self.block_result.take()?; + Some((blocks, access_lists.take())) + } +} + +impl Future for FetchFullBlockRangeWithOptionalAccessListsFuture +where + Client: BlockClient + + BlockAccessListsClient + + 'static, +{ + type Output = FullBlockRangeWithOptionalAccessListsResponse; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.get_mut(); + + // Complete the normal block range first, then issue a separate BAL hash-list request. + if this.block_result.is_none() && + let Poll::Ready(blocks) = this.blocks.poll_unpin(cx) + { + this.block_result = Some(blocks); + } + + this.poll_access_lists(cx); + + if let Some(response) = this.take_response() { + return Poll::Ready(response) + } + + Poll::Pending + } +} + +/// Tracks an optional BAL range request and its completed result. +enum OptionalBlockAccessListsState { + /// The block hashes needed for `GetBlockAccessLists` are not known yet. + WaitingForBlocks, + /// A `GetBlockAccessLists` request is in flight. + Pending(Req), + /// `None` means the block range is available but optional BAL data is not. + Ready(Option), +} + impl Debug for FetchFullBlockFuture where Client: BlockClient, @@ -534,11 +692,6 @@ impl FetchFullBlockRangeFuture where Client: BlockClient, { - /// Returns the block hashes for the given range, if they are available. - pub fn range_block_hashes(&self) -> Option> { - self.headers.as_ref().map(|h| h.iter().map(|h| h.hash()).collect()) - } - /// Returns whether or not the bodies map is fully populated with requested headers and bodies. fn is_bodies_complete(&self) -> bool { self.bodies.len() == self.count as usize @@ -1046,6 +1199,9 @@ mod tests { inner: TestFullBlockClient, access_lists: Arc>>, access_list_requests: Arc, + access_list_soft_limit: Arc, + extra_access_list_entries: Arc, + unsupported_access_lists: Arc, bad_messages: Arc, empty_first_response: Arc, } @@ -1056,6 +1212,9 @@ mod tests { inner: TestFullBlockClient::default(), access_lists: Arc::new(Mutex::new(HashMap::default())), access_list_requests: Arc::new(AtomicUsize::new(0)), + access_list_soft_limit: Arc::new(AtomicUsize::new(usize::MAX)), + extra_access_list_entries: Arc::new(AtomicUsize::new(0)), + unsupported_access_lists: Arc::new(AtomicBool::new(false)), bad_messages: Arc::new(AtomicUsize::new(0)), empty_first_response: Arc::new(AtomicBool::new(false)), } @@ -1067,6 +1226,39 @@ mod tests { self.inner.insert(header.clone(), body); self.access_lists.lock().insert(header.hash(), access_list); } + + fn set_access_list_soft_limit(&self, limit: usize) { + self.access_list_soft_limit.store(limit, Ordering::SeqCst); + } + + fn set_extra_access_list_entries(&self, count: usize) { + self.extra_access_list_entries.store(count, Ordering::SeqCst); + } + + fn set_access_lists_unsupported(&self, unsupported: bool) { + self.unsupported_access_lists.store(unsupported, Ordering::SeqCst); + } + } + + /// Inserts headers with block access lists and returns the last header and block body. + fn insert_headers_with_access_lists_into_client( + client: &FullBlockWithAccessListsClient, + range: Range, + ) -> (SealedHeader, BlockBody) { + let mut sealed_header: SealedHeader = SealedHeader::default(); + let body = BlockBody::default(); + for block_idx in range { + let (mut header, hash) = sealed_header.split(); + header.parent_hash = hash; + header.number += 1; + + sealed_header = SealedHeader::seal_slow(header); + let access_list = Bytes::from(vec![EMPTY_LIST_CODE, block_idx as u8]); + + client.insert(sealed_header.clone(), body.clone(), access_list); + } + + (sealed_header, body) } impl DownloadClient for FullBlockWithAccessListsClient { @@ -1118,6 +1310,10 @@ mod tests { ) -> Self::Output { self.access_list_requests.fetch_add(1, Ordering::SeqCst); + if self.unsupported_access_lists.load(Ordering::SeqCst) { + return futures::future::ready(Err(RequestError::UnsupportedCapability)) + } + if self.empty_first_response.swap(false, Ordering::SeqCst) { return futures::future::ready(Ok(WithPeerId::new( PeerId::random(), @@ -1125,8 +1321,9 @@ mod tests { ))) } - let access_lists = hashes + let mut access_lists: Vec<_> = hashes .into_iter() + .take(self.access_list_soft_limit.load(Ordering::SeqCst)) .map(|hash| { self.access_lists .lock() @@ -1135,6 +1332,9 @@ mod tests { .unwrap_or_else(|| Bytes::from_static(&[EMPTY_LIST_CODE])) }) .collect(); + for _ in 0..self.extra_access_list_entries.load(Ordering::SeqCst) { + access_lists.push(Bytes::from_static(&[EMPTY_LIST_CODE])); + } futures::future::ready(Ok(WithPeerId::new( PeerId::random(), @@ -1262,6 +1462,131 @@ mod tests { assert_eq!(body_requests.load(Ordering::SeqCst), 3); } + #[tokio::test] + async fn download_full_block_range_with_access_lists() { + let client = FullBlockWithAccessListsClient::default(); + let (header, _) = insert_headers_with_access_lists_into_client(&client, 0..3); + + let access_lists = Arc::clone(&client.access_lists); + let request_count = Arc::clone(&client.access_list_requests); + let client = FullBlockClient::test_client(client); + + let response = timeout( + Duration::from_secs(1), + client.get_full_block_range_with_optional_access_lists(header.hash(), 3), + ) + .await + .expect("range request should complete"); + + let (blocks, received_access_lists) = response; + assert_eq!(blocks.len(), 3); + let expected = { + let access_lists = access_lists.lock(); + blocks + .iter() + .map(|block| access_lists.get(&block.hash()).cloned().expect("access list exists")) + .collect::>() + }; + assert_eq!(received_access_lists, Some(BlockAccessLists(expected))); + assert_eq!(request_count.load(Ordering::SeqCst), 1); + } + + #[tokio::test] + async fn download_full_block_range_with_access_lists_preserves_empty_response() { + let client = FullBlockWithAccessListsClient::default(); + client.empty_first_response.store(true, Ordering::SeqCst); + let (header, _) = insert_headers_with_access_lists_into_client(&client, 0..3); + + let request_count = Arc::clone(&client.access_list_requests); + let client = FullBlockClient::test_client(client); + + let response = timeout( + Duration::from_secs(1), + client.get_full_block_range_with_optional_access_lists(header.hash(), 3), + ) + .await + .expect("range request should complete without access lists"); + + let (blocks, received_access_lists) = response; + assert_eq!(blocks.len(), 3); + assert_eq!(received_access_lists, Some(BlockAccessLists(Vec::new()))); + assert_eq!(request_count.load(Ordering::SeqCst), 1); + } + + #[tokio::test] + async fn download_full_block_range_with_access_lists_preserves_short_response() { + let client = FullBlockWithAccessListsClient::default(); + client.set_access_list_soft_limit(2); + let (header, _) = insert_headers_with_access_lists_into_client(&client, 0..5); + + let access_lists = Arc::clone(&client.access_lists); + let request_count = Arc::clone(&client.access_list_requests); + let client = FullBlockClient::test_client(client); + + let (blocks, received_access_lists) = timeout( + Duration::from_secs(1), + client.get_full_block_range_with_optional_access_lists(header.hash(), 5), + ) + .await + .expect("range request should complete without access lists"); + + assert_eq!(blocks.len(), 5); + let expected = { + let access_lists = access_lists.lock(); + blocks + .iter() + .take(2) + .map(|block| access_lists.get(&block.hash()).cloned().expect("access list exists")) + .collect::>() + }; + assert_eq!(received_access_lists, Some(BlockAccessLists(expected))); + assert_eq!(request_count.load(Ordering::SeqCst), 1); + } + + #[tokio::test] + async fn download_full_block_range_with_access_lists_returns_none_when_unavailable() { + let client = FullBlockWithAccessListsClient::default(); + client.set_access_lists_unsupported(true); + let (header, _) = insert_headers_with_access_lists_into_client(&client, 0..3); + + let request_count = Arc::clone(&client.access_list_requests); + let client = FullBlockClient::test_client(client); + + let (blocks, received_access_lists) = timeout( + Duration::from_secs(1), + client.get_full_block_range_with_optional_access_lists(header.hash(), 3), + ) + .await + .expect("range request should complete without access lists"); + + assert_eq!(blocks.len(), 3); + assert!(received_access_lists.is_none()); + assert_eq!(request_count.load(Ordering::SeqCst), 1); + } + + #[tokio::test] + async fn download_full_block_range_with_access_lists_rejects_long_response() { + let client = FullBlockWithAccessListsClient::default(); + client.set_extra_access_list_entries(1); + let (header, _) = insert_headers_with_access_lists_into_client(&client, 0..3); + + let request_count = Arc::clone(&client.access_list_requests); + let bad_messages = Arc::clone(&client.bad_messages); + let client = FullBlockClient::test_client(client); + + let (blocks, received_access_lists) = timeout( + Duration::from_secs(1), + client.get_full_block_range_with_optional_access_lists(header.hash(), 3), + ) + .await + .expect("range request should complete without access lists"); + + assert_eq!(blocks.len(), 3); + assert!(received_access_lists.is_none()); + assert_eq!(request_count.load(Ordering::SeqCst), 1); + assert_eq!(bad_messages.load(Ordering::SeqCst), 1); + } + #[tokio::test] async fn download_full_block_range_with_invalid_header() { let client = TestFullBlockClient::default(); diff --git a/crates/net/p2p/src/lib.rs b/crates/net/p2p/src/lib.rs index 4457fdd9f9d..6a08740e117 100644 --- a/crates/net/p2p/src/lib.rs +++ b/crates/net/p2p/src/lib.rs @@ -58,6 +58,7 @@ pub use block_access_lists::client::{BalRequirement, BlockAccessListsClient}; pub use bodies::client::BodiesClient; pub use headers::client::HeadersClient; pub use receipts::client::ReceiptsClient; +pub use reth_eth_wire_types::BlockAccessLists; use reth_primitives_traits::Block; /// Helper trait that unifies network behaviour needed for fetching entire blocks. diff --git a/crates/net/peers/src/lib.rs b/crates/net/peers/src/lib.rs index 9b6ee1f0280..fe02ffe59cc 100644 --- a/crates/net/peers/src/lib.rs +++ b/crates/net/peers/src/lib.rs @@ -125,20 +125,38 @@ pub enum AnyNode { impl AnyNode { /// Returns the peer id of the node. + #[cfg(not(feature = "secp256k1"))] + pub const fn peer_id(&self) -> PeerId { + match self { + Self::NodeRecord(record) => record.id, + Self::PeerId(peer_id) => *peer_id, + } + } + + /// Returns the peer id of the node. + #[cfg(feature = "secp256k1")] pub fn peer_id(&self) -> PeerId { match self { Self::NodeRecord(record) => record.id, - #[cfg(feature = "secp256k1")] Self::Enr(enr) => pk2id(&enr.public_key()), Self::PeerId(peer_id) => *peer_id, } } /// Returns the full node record if available. + #[cfg(not(feature = "secp256k1"))] + pub const fn node_record(&self) -> Option { + match self { + Self::NodeRecord(record) => Some(*record), + Self::PeerId(_) => None, + } + } + + /// Returns the full node record if available. + #[cfg(feature = "secp256k1")] pub fn node_record(&self) -> Option { match self { Self::NodeRecord(record) => Some(*record), - #[cfg(feature = "secp256k1")] Self::Enr(enr) => { let node_record = NodeRecord { address: enr @@ -152,7 +170,7 @@ impl AnyNode { .into_ipv4_mapped(); Some(node_record) } - _ => None, + Self::PeerId(_) => None, } } } From c77e449e4ed75f952c4713f2f6feb26467c57948 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Fri, 1 May 2026 08:35:24 +0200 Subject: [PATCH 010/335] fix(cli): verify repaired trie state root before commit (#23854) Co-authored-by: Brian Picciano <933154+mediocregopher@users.noreply.github.com> Co-authored-by: Amp --- crates/cli/commands/src/db/repair_trie.rs | 50 +++++++++++++++++++++-- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/crates/cli/commands/src/db/repair_trie.rs b/crates/cli/commands/src/db/repair_trie.rs index fcda895c33b..602f8bbb4b4 100644 --- a/crates/cli/commands/src/db/repair_trie.rs +++ b/crates/cli/commands/src/db/repair_trie.rs @@ -1,3 +1,4 @@ +use alloy_consensus::BlockHeader as AlloyBlockHeader; use clap::Parser; use metrics::{self, Counter}; use reth_chainspec::EthChainSpec; @@ -18,7 +19,9 @@ use reth_node_metrics::{ server::{MetricServer, MetricServerConfig}, version::VersionInfo, }; -use reth_provider::{providers::ProviderNodeTypes, ChainSpecProvider, StageCheckpointReader}; +use reth_provider::{ + providers::ProviderNodeTypes, ChainSpecProvider, HeaderProvider, StageCheckpointReader, +}; use reth_stages::StageId; use reth_storage_api::StorageSettingsCache; use reth_tasks::TaskExecutor; @@ -27,7 +30,8 @@ use reth_trie::{ Nibbles, }; use reth_trie_db::{ - DatabaseHashedCursorFactory, DatabaseTrieCursorFactory, StorageTrieEntryLike, TrieTableAdapter, + DatabaseHashedCursorFactory, DatabaseStateRoot, DatabaseTrieCursorFactory, + StorageTrieEntryLike, TrieTableAdapter, }; use std::{ net::SocketAddr, @@ -215,7 +219,7 @@ fn verify_and_repair(tool: &DbTool) -> eyre::Result<()> verify_checkpoints(provider_rw.as_ref())?; let inconsistent_nodes = reth_trie_db::with_adapter!(tool.provider_factory, |A| { - do_verify_and_repair::<_, A>(&mut provider_rw)? + do_verify_and_repair::<_, A>(&mut provider_rw, finish_checkpoint.block_number)? }); if inconsistent_nodes == 0 { @@ -230,6 +234,7 @@ fn verify_and_repair(tool: &DbTool) -> eyre::Result<()> fn do_verify_and_repair( provider_rw: &mut reth_provider::DatabaseProviderRW, + block_number: u64, ) -> eyre::Result where ::TXMut: DbTxMut + DbTx, @@ -329,9 +334,48 @@ where } } + drop(account_trie_cursor); + drop(storage_trie_cursor); + + if inconsistent_nodes > 0 { + // Refuse to commit repaired trie tables unless they reproduce the canonical tip state + // root. + verify_repaired_state_root::<_, A>(provider_rw, block_number)?; + } + Ok(inconsistent_nodes as usize) } +fn verify_repaired_state_root( + provider_rw: &reth_provider::DatabaseProviderRW, + block_number: u64, +) -> eyre::Result<()> +where + ::TXMut: DbTxMut + DbTx, +{ + type DbStateRoot<'a, TX, A> = reth_trie::StateRoot< + DatabaseTrieCursorFactory<&'a TX, A>, + DatabaseHashedCursorFactory<&'a TX>, + >; + + let expected_state_root = provider_rw + .header_by_number(block_number)? + .ok_or_else(|| { + eyre::eyre!("Missing canonical header at database block tip {block_number}") + })? + .state_root(); + + let computed_state_root = DbStateRoot::<_, A>::from_tx(provider_rw.tx_ref()).root()?; + + if computed_state_root != expected_state_root { + return Err(eyre::eyre!( + "Repaired trie state root mismatch at block {block_number}: computed {computed_state_root}, header {expected_state_root}", + )) + } + + Ok(()) +} + /// Output progress information based on the last seen account path. fn output_progress(last_account: Nibbles, start_time: Instant, inconsistent_nodes: u64) { // Calculate percentage based on position in the trie path space From 0d07d6ae69d4226881fec2bf1a83f8183c453101 Mon Sep 17 00:00:00 2001 From: Soubhik Singha Mahapatra <160333583+Soubhik-10@users.noreply.github.com> Date: Fri, 1 May 2026 16:56:40 +0530 Subject: [PATCH 011/335] chore: update rpc-compat expected failures (#23867) --- .github/scripts/hive/expected_failures.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/scripts/hive/expected_failures.yaml b/.github/scripts/hive/expected_failures.yaml index 62788f24c1f..773da3d7d5a 100644 --- a/.github/scripts/hive/expected_failures.yaml +++ b/.github/scripts/hive/expected_failures.yaml @@ -2,7 +2,9 @@ rpc-compat: - debug_getRawBlock/get-invalid-number (reth) - debug_getRawTransaction/get-invalid-hash (reth) - + - debug_getRawReceipts/get-invalid-number (reth) + - debug_getRawHeader/get-invalid-number (reth) + - debug_getRawReceipts/get-block-n (reth) - eth_getStorageAt/get-storage-invalid-key-too-large (reth) - eth_getStorageAt/get-storage-invalid-key (reth) - eth_getTransactionReceipt/get-legacy-contract (reth) From 70fc51e3de5e8f7fc901b99b7d15aeb7c96d663e Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Fri, 1 May 2026 20:12:22 +0800 Subject: [PATCH 012/335] fix(reth-bb): renumber bal_index space at segment boundaries (#23868) --- bin/reth-bb/src/evm.rs | 83 ++++++++++++++++++- bin/reth-bb/src/evm_config.rs | 26 +++++- .../src/bench/generate_big_block.rs | 40 +++++++-- 3 files changed, 136 insertions(+), 13 deletions(-) diff --git a/bin/reth-bb/src/evm.rs b/bin/reth-bb/src/evm.rs index a4cd0761be2..92360024391 100644 --- a/bin/reth-bb/src/evm.rs +++ b/bin/reth-bb/src/evm.rs @@ -106,6 +106,28 @@ pub(crate) type BlockHashSeeder = fn(&mut DB, &[(u64, B256)]); /// Function pointer that reads the BAL index from the DB. pub(crate) type BalIndexReader = fn(&DB) -> u64; +/// Function pointer that bumps the BAL index in the DB. +/// +/// Injected from `ConfigureEvm::create_executor` like the other DB callbacks +/// so `BbBlockExecutor` can advance `bal_index` between sub-events of a +/// segment boundary (post-N's `finish()` and pre-N+1's +/// `apply_pre_execution_changes()`) without requiring additional trait bounds +/// on `DB`. The renumbering scheme places these on consecutive `bal_indexes` so +/// workers reading the BAL overlay see post-N's writes via the strict +/// less-than `BalWrites::get` semantic. +pub(crate) type BalIndexBumper = fn(&mut DB); + +/// Function pointer that overwrites the BAL index in the DB. +/// +/// Used in `BbBlockExecutor::initialize` to map a worker's incoming +/// `bal_index = i + 1` (the standard "tx i + 1" convention from +/// `execute_block_in_pool`) onto the renumbered space `i + 1 + 2k`, where +/// `k` is the segment index containing tx `i`. Renumbering reserves two +/// extra `bal_indexes` per segment boundary (one for each segment's +/// post-execution and one for the next segment's pre-execution), so workers' +/// strict less-than reads can see those boundary writes. +pub(crate) type BalIndexSetter = fn(&mut DB, u64); + /// Block executor that wraps [`EthBlockExecutor`] and handles segment-boundary /// changes for big-block execution. /// @@ -143,6 +165,14 @@ where block_hash_seeder: Option>, /// Callback to read the BAL index from the DB. bal_index_reader: Option>, + /// Callback to bump `bal_index` on the DB. See [`BalIndexBumper`]. Used at + /// segment boundaries to put post-N's writes and pre-N+1's writes on + /// consecutive `bal_indexes`. + bal_index_bumper: Option>, + /// Callback to set `bal_index` on the DB. See [`BalIndexSetter`]. Used in + /// [`Self::initialize`] to renumber a worker's incoming `bal_index` into + /// the boundary-padded space. + bal_index_setter: Option>, /// Whether the executor has selected its starting segment. initialized: bool, } @@ -163,6 +193,7 @@ where >, TxEnv: FromRecoveredTx + FromTxWithEncoded, { + #[expect(clippy::too_many_arguments)] pub(crate) fn new( evm: EthEvm, ctx: EthBlockExecutionCtx<'a>, @@ -171,6 +202,8 @@ where plan: Option, block_hash_seeder: Option>, bal_index_reader: Option>, + bal_index_bumper: Option>, + bal_index_setter: Option>, ) -> Self { let inner = EthBlockExecutor::new(evm, ctx, spec, receipt_builder); Self { @@ -182,6 +215,8 @@ where shared_hook: Arc::new(Mutex::new(None)), block_hash_seeder, bal_index_reader, + bal_index_bumper, + bal_index_setter, initialized: false, } } @@ -232,6 +267,17 @@ where self.reseed_block_hashes_for(block_number); if bal_index > 0 { + // Renumber the worker's bal_index from the raw "tx i + 1" + // convention to "tx i + 1 + 2k" where k is the segment index. + // This reserves two `bal_indexes` per crossed segment boundary + // (one for post-N's `finish()`, one for pre-N+1's + // `apply_pre_execution_changes`) so worker reads via + // `BalWrites::get` see those writes via the strict less-than + // semantic. + if let Some(setter) = self.bal_index_setter { + let renumbered = bal_index + 2 * segment_idx as u64; + setter(self.inner_mut().evm_mut().db_mut(), renumbered); + } self.plan = None; } @@ -329,13 +375,22 @@ where // Finish the inner executor for the completed segment. This applies // post-execution system calls (EIP-7002/7251) and withdrawal balance - // increments via EthBlockExecutor::finish(). + // increments via EthBlockExecutor::finish() at the current bal_index + // (= K, the boundary's "post-N slot"). let mut inner = self.inner.take().expect("inner executor must exist"); inner.ctx = prev_ctx; let spec = inner.spec.clone(); let receipt_builder = inner.receipt_builder; let (mut evm, result) = inner.finish()?; + // Renumbering: bump bal_index so the new segment's + // `apply_pre_execution_changes` writes land at K+1 instead of colliding + // with post-N at K. Without this, BAL workers querying at K can't see + // either boundary write via `BalWrites::get`'s strict less-than. + if let Some(bumper) = self.bal_index_bumper { + bumper(evm.db_mut()); + } + // Receipts already have globally-correct cumulative_gas_used (fixed // up in commit_transaction). Update the offset with this segment's // gas so that subsequent segments' receipts are adjusted correctly. @@ -387,9 +442,17 @@ where .saturating_to::(); self.reseed_block_hashes_for(new_block_number); - // Apply pre-execution changes for the new segment (EIP-2935, EIP-4788). + // Apply pre-execution changes for the new segment (EIP-2935, EIP-4788) + // at bal_index K+1. self.inner_mut().apply_pre_execution_changes()?; + // Renumbering: bump bal_index so the upcoming `inner.commit_transaction` + // for tx 0 of this segment lands at K+2 (visible to its worker via + // strict less-than reads of K+2, which include both K and K+1). + if let Some(bumper) = self.bal_index_bumper { + bumper(self.inner_mut().evm_mut().db_mut()); + } + trace!(target: "engine::bb::evm", "Started segment {seg_idx}"); Ok(()) @@ -586,6 +649,8 @@ impl BbBlockExecutorFactory { ctx: EthBlockExecutionCtx<'a>, block_hash_seeder: Option>, bal_index_reader: Option>, + bal_index_bumper: Option>, + bal_index_setter: Option>, ) -> BbBlockExecutor<'a, DB, I, PrecompilesMap, &'a Spec> where Spec: alloy_evm::eth::spec::EthExecutorSpec, @@ -601,6 +666,8 @@ impl BbBlockExecutorFactory { plan, block_hash_seeder, bal_index_reader, + bal_index_bumper, + bal_index_setter, ) } } @@ -635,6 +702,16 @@ where I: Inspector>, { let plan = self.peek_plan(); - BbBlockExecutor::new(evm, ctx, &self.spec, self.receipt_builder, plan, None, None) + BbBlockExecutor::new( + evm, + ctx, + &self.spec, + self.receipt_builder, + plan, + None, + None, + None, + None, + ) } } diff --git a/bin/reth-bb/src/evm_config.rs b/bin/reth-bb/src/evm_config.rs index c371f6afce6..a0e1e437065 100644 --- a/bin/reth-bb/src/evm_config.rs +++ b/bin/reth-bb/src/evm_config.rs @@ -110,6 +110,27 @@ fn read_bal_index(state: &&mut revm::database::State) -> u64 { state.bal_state.bal_index() } +/// Bumps the BAL index on a `&mut State`. +/// +/// Used as a [`BalIndexBumper`](crate::evm::BalIndexBumper) callback so the +/// generic [`BbBlockExecutor`](crate::evm::BbBlockExecutor) can advance +/// `bal_index` between sub-events of a segment boundary (post-N's `finish()` +/// and pre-N+1's `apply_pre_execution_changes()`) without a trait bound on +/// `DB`. +fn bump_bal_index(state: &mut &mut revm::database::State) { + state.bump_bal_index(); +} + +/// Sets the BAL index on a `&mut State`. +/// +/// Used as a [`BalIndexSetter`](crate::evm::BalIndexSetter) callback so +/// [`BbBlockExecutor::initialize`](crate::evm::BbBlockExecutor) can renumber +/// a worker's incoming `bal_index = i + 1` into the boundary-padded space +/// `i + 1 + 2*k` (where `k` is the worker's segment index). +fn set_bal_index(state: &mut &mut revm::database::State, index: u64) { + state.set_bal_index(index); +} + // --------------------------------------------------------------------------- // ConfigureEvm // --------------------------------------------------------------------------- @@ -183,12 +204,15 @@ where Some(read_bal_index::); // Inject concrete function pointers that know the `State` type so - // the generic executor can reseed block hashes and read `bal_index`. + // the generic executor can manipulate `bal_index` and reseed block + // hashes without a trait bound on `DB`. self.executor_factory.create_executor_with_seeder( evm, ctx, Some(seed_state_block_hashes::), bal_index_reader, + Some(bump_bal_index::), + Some(set_bal_index::), ) } } diff --git a/bin/reth-bench/src/bench/generate_big_block.rs b/bin/reth-bench/src/bench/generate_big_block.rs index 53a504c054a..6b1bbad54e6 100644 --- a/bin/reth-bench/src/bench/generate_big_block.rs +++ b/bin/reth-bench/src/bench/generate_big_block.rs @@ -456,9 +456,12 @@ impl Command { let mut total_gas_limit = base.payload.as_v1().gas_limit; // Concatenate transactions from subsequent blocks and build env_switches - for ((block_data, receipts), block_access_list) in - blocks.into_iter().zip(block_receipts).zip(block_access_lists) + for (block_idx, ((block_data, receipts), block_access_list)) in + blocks.into_iter().zip(block_receipts).zip(block_access_lists).enumerate() { + // Segment index in the merged big block. The base block is + // segment 0; subsequent blocks are segments 1, 2, ... + let segment_idx = (block_idx + 1) as u64; let block_v1 = block_data.payload.as_v1(); let block_gas = block_v1.gas_used; total_gas_used += block_gas; @@ -469,6 +472,7 @@ impl Command { merged_block_access_list.get_or_insert_with(Default::default), block_access_list, cumulative_tx_count as u64, + segment_idx, ); } @@ -734,6 +738,7 @@ fn merge_block_access_list( merged: &mut BlockAccessList, incoming: BlockAccessList, tx_index_offset: u64, + segment_idx: u64, ) { let mut account_positions = merged .iter() @@ -742,7 +747,7 @@ fn merge_block_access_list( .collect::>(); for mut account_changes in incoming { - shift_account_changes(&mut account_changes, tx_index_offset); + shift_account_changes(&mut account_changes, tx_index_offset, segment_idx); if let Some(&idx) = account_positions.get(&account_changes.address) { merge_account_changes(&mut merged[idx], account_changes); @@ -753,20 +758,37 @@ fn merge_block_access_list( } } -fn shift_account_changes(account_changes: &mut AccountChanges, tx_index_offset: u64) { +fn shift_account_changes( + account_changes: &mut AccountChanges, + tx_index_offset: u64, + segment_idx: u64, +) { + // Per-block BALs use block_access_index = 0 for pre-execution writes + // (system contract calls before any tx), 1..tx_count for tx commits, and + // tx_count+1 for post-execution. + // + // Renumbering: each segment boundary reserves two distinct bal_indexes — + // one for the prior segment's `finish()` (post-execution withdrawals + + // EIP-7002/7251 system calls) and one for the new segment's + // `apply_pre_execution_changes()` (EIP-2935/EIP-4788). The renumbered + // bal_index for a block-local idx in segment `k` is + // `idx + tx_index_offset + 2*k`. This ensures BAL workers reading via + // `BalWrites::get` (strict less-than) see all prior segments' boundary + // writes. + let shift = tx_index_offset + 2 * segment_idx; for slot_changes in &mut account_changes.storage_changes { for change in &mut slot_changes.changes { - change.block_access_index += tx_index_offset; + change.block_access_index += shift; } } for change in &mut account_changes.balance_changes { - change.block_access_index += tx_index_offset; + change.block_access_index += shift; } for change in &mut account_changes.nonce_changes { - change.block_access_index += tx_index_offset; + change.block_access_index += shift; } for change in &mut account_changes.code_changes { - change.block_access_index += tx_index_offset; + change.block_access_index += shift; } } @@ -865,7 +887,7 @@ mod tests { }, ]; - merge_block_access_list(&mut merged, incoming, 3); + merge_block_access_list(&mut merged, incoming, 3, 0); assert_eq!(merged.len(), 2); From 38c627ce8f1a3bb82bed8a6beb3016f62c50016d Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Fri, 1 May 2026 20:47:26 +0400 Subject: [PATCH 013/335] chore(deps): bump hickory-resolver (#23914) --- Cargo.lock | 190 +++++++++++++++++++++++++++----- Cargo.toml | 2 +- crates/net/dns/src/resolver.rs | 15 ++- crates/net/network/src/error.rs | 4 +- deny.toml | 4 + 5 files changed, 180 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d185cfdcc14..6abc29632f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2173,6 +2173,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + [[package]] name = "chrono" version = "0.4.44" @@ -2592,6 +2603,16 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -3373,18 +3394,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "enum-as-inner" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "enum-ordinalize" version = "4.3.2" @@ -4254,6 +4263,7 @@ dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", + "rand_core 0.10.1", "wasip2", "wasip3", ] @@ -4503,48 +4513,72 @@ dependencies = [ ] [[package]] -name = "hickory-proto" -version = "0.25.2" +name = "hickory-net" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" +checksum = "e2295ed2f9c31e471e1428a8f88a3f0e1f4b27c15049592138d1eebe9c35b183" dependencies = [ "async-trait", "cfg-if", "data-encoding", - "enum-as-inner", "futures-channel", "futures-io", "futures-util", + "hickory-proto", + "idna", + "ipnet", + "jni 0.22.4", + "rand 0.10.1", + "thiserror 2.0.18", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "hickory-proto" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bab31817bfb44672a252e97fe81cd0c18d1b2cf892108922f6818820df8c643" +dependencies = [ + "data-encoding", "idna", "ipnet", + "jni 0.22.4", "once_cell", - "rand 0.9.4", + "prefix-trie", + "rand 0.10.1", "ring", "serde", "thiserror 2.0.18", "tinyvec", - "tokio", "tracing", "url", ] [[package]] name = "hickory-resolver" -version = "0.25.2" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" +checksum = "f0d58d28879ceecde6607729660c2667a081ccdc082e082675042793960f178c" dependencies = [ "cfg-if", "futures-util", + "hickory-net", "hickory-proto", "ipconfig", + "ipnet", + "jni 0.22.4", "moka", + "ndk-context", "once_cell", "parking_lot", - "rand 0.9.4", + "rand 0.10.1", "resolv-conf", "serde", "smallvec", + "system-configuration", "thiserror 2.0.18", "tokio", "tracing", @@ -5073,6 +5107,9 @@ name = "ipnet" version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +dependencies = [ + "serde", +] [[package]] name = "iri-string" @@ -5167,6 +5204,36 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link 0.2.1", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version 0.4.1", + "simd_cesu8", + "syn 2.0.117", +] + [[package]] name = "jni-sys" version = "0.3.1" @@ -5995,6 +6062,12 @@ dependencies = [ "unsigned-varint", ] +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + [[package]] name = "nix" version = "0.31.2" @@ -6712,6 +6785,17 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prefix-trie" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23370be78b7e5bcbb0cab4a02047eb040279a693c78daad04c2c5f1c24a83503" +dependencies = [ + "either", + "ipnet", + "num-traits", +] + [[package]] name = "pretty_assertions" version = "1.4.1" @@ -7015,6 +7099,17 @@ dependencies = [ "serde", ] +[[package]] +name = "rand" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.1", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -7054,6 +7149,12 @@ dependencies = [ "serde", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + [[package]] name = "rand_xorshift" version = "0.4.0" @@ -10968,9 +11069,9 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19787cda76408ec5404443dc8b31795c87cd8fec49762dc75fa727740d34acc1" dependencies = [ - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", - "jni", + "jni 0.21.1", "log", "once_cell", "rustls", @@ -10989,9 +11090,9 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" dependencies = [ - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", - "jni", + "jni 0.21.1", "log", "once_cell", "rustls", @@ -11189,7 +11290,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ "bitflags 2.11.1", - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -11479,6 +11580,22 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version 0.4.1", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "similar" version = "2.7.0" @@ -11751,6 +11868,27 @@ dependencies = [ "windows 0.62.2", ] +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags 2.11.1", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tag_ptr" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index bb76b5ffb4b..f30a175896b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -666,7 +666,7 @@ data-encoding = "2" delegate = "0.13" digest = "0.10.5" hash-db = "=0.15.2" -hickory-resolver = "0.25.0" +hickory-resolver = "0.26.1" hmac = "0.12.1" human_bytes = "0.4.1" indexmap = "2" diff --git a/crates/net/dns/src/resolver.rs b/crates/net/dns/src/resolver.rs index 434f34c75aa..7b966184bd6 100644 --- a/crates/net/dns/src/resolver.rs +++ b/crates/net/dns/src/resolver.rs @@ -1,8 +1,8 @@ //! Perform DNS lookups use dashmap::DashMap; -use hickory_resolver::name_server::ConnectionProvider; -pub use hickory_resolver::{ResolveError, TokioResolver}; +pub use hickory_resolver::{net::NetError, TokioResolver}; +use hickory_resolver::{proto::rr::RData, ConnectionProvider}; use std::future::Future; use tracing::trace; @@ -23,8 +23,11 @@ impl Resolver for hickory_resolver::Resolver

{ None } Ok(lookup) => { - let txt = lookup.into_iter().next()?; - let entry = txt.iter().next()?; + let txt = lookup.answers().iter().find_map(|r| match &r.data { + RData::TXT(txt) => Some(txt), + _ => None, + })?; + let entry = txt.txt_data.first()?; String::from_utf8(entry.to_vec()).ok() } } @@ -59,8 +62,8 @@ impl DnsResolver { /// Constructs a new Tokio based Resolver with the system configuration. /// /// This will use `/etc/resolv.conf` on Unix OSes and the registry on Windows. - pub fn from_system_conf() -> Result { - TokioResolver::builder_tokio().map(|builder| Self::new(builder.build())) + pub fn from_system_conf() -> Result { + TokioResolver::builder_tokio()?.build().map(Self::new) } } diff --git a/crates/net/network/src/error.rs b/crates/net/network/src/error.rs index b6509a0db0c..fee5326b047 100644 --- a/crates/net/network/src/error.rs +++ b/crates/net/network/src/error.rs @@ -1,7 +1,7 @@ //! Possible errors when interacting with the network. use crate::session::PendingSessionHandshakeError; -use reth_dns_discovery::resolver::ResolveError; +use reth_dns_discovery::resolver::NetError; use reth_ecies::ECIESErrorImpl; use reth_eth_wire::{ errors::{EthHandshakeError, EthStreamError, P2PHandshakeError, P2PStreamError}, @@ -62,7 +62,7 @@ pub enum NetworkError { /// /// See also [`DnsResolver`](reth_dns_discovery::DnsResolver::from_system_conf) #[error("failed to configure DNS resolver: {0}")] - DnsResolver(#[from] ResolveError), + DnsResolver(#[from] NetError), } impl NetworkError { diff --git a/deny.toml b/deny.toml index 7f792c3016f..f4888f42900 100644 --- a/deny.toml +++ b/deny.toml @@ -16,6 +16,10 @@ ignore = [ "RUSTSEC-2026-0002", # https://rustsec.org/advisories/RUSTSEC-2026-0097 rand is unsound with a custom logger "RUSTSEC-2026-0097", + # https://rustsec.org/advisories/RUSTSEC-2026-0118 vulnerable DnssecDnsHandle code was + # moved from hickory-proto to hickory-net in 0.26.0; we use hickory-net 0.26.1 which has + # the fix, and no patched hickory-proto release exists + "RUSTSEC-2026-0118", ] # This section is considered when running `cargo deny check bans`. From 6d9ea5af49c2b405e9bf2fc56cd88ea97db9d763 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 2 May 2026 17:11:29 +0200 Subject: [PATCH 014/335] feat(engine): add shared block accessor to EthBuiltPayload (#23862) --- crates/ethereum/engine-primitives/src/payload.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/ethereum/engine-primitives/src/payload.rs b/crates/ethereum/engine-primitives/src/payload.rs index 1e9a9eaaf59..fa21d67a0fb 100644 --- a/crates/ethereum/engine-primitives/src/payload.rs +++ b/crates/ethereum/engine-primitives/src/payload.rs @@ -62,6 +62,11 @@ impl EthBuiltPayload { &self.block } + /// Returns the built block with shared ownership. + pub const fn block_arc(&self) -> &Arc> { + &self.block + } + /// Fees of the block pub const fn fees(&self) -> U256 { self.fees From b2794548e98a153be0a7f689f1a917944d823856 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Sat, 2 May 2026 17:27:23 +0200 Subject: [PATCH 015/335] feat(eth-wire): add capability message id helpers (#23908) Co-authored-by: Matthias Seitz --- crates/net/eth-wire/src/capability.rs | 64 ++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/crates/net/eth-wire/src/capability.rs b/crates/net/eth-wire/src/capability.rs index 9691acb4399..475e85e6158 100644 --- a/crates/net/eth-wire/src/capability.rs +++ b/crates/net/eth-wire/src/capability.rs @@ -188,6 +188,34 @@ impl SharedCapabilities { self.0.iter().find(|c| c.version() == cap.version as u8 && c.name() == cap.name) } + /// Converts a capability-local message ID into the relative `RLPx` message ID used by + /// [`P2PStream`](crate::P2PStream). + /// + /// `P2PStream` strips the reserved p2p message ID range before yielding subprotocol messages, + /// so the returned ID is relative to the first shared capability, not the absolute wire ID. + #[inline] + pub fn relative_message_id(&self, cap: &Capability, message_id: u8) -> Option { + let shared = self.find(cap)?; + if message_id >= shared.num_messages() { + return None + } + + shared.relative_message_id_offset().checked_add(message_id) + } + + /// Converts a relative `RLPx` message ID back into the message ID local to `cap`. + /// + /// Returns `None` if `cap` is not shared, if the relative ID belongs to a different + /// capability, or if it is outside the capability's negotiated message range. + #[inline] + pub fn capability_message_id(&self, cap: &Capability, relative_message_id: u8) -> Option { + let shared = self.find(cap)?; + let start = shared.relative_message_id_offset(); + let end = start.checked_add(shared.num_messages())?; + + (start..end).contains(&relative_message_id).then(|| relative_message_id - start) + } + /// Returns the matching shared capability for the given capability offset. /// /// `offset` is the multiplexed message id offset of the capability relative to the reserved @@ -362,7 +390,7 @@ impl UnsupportedCapabilityError { #[cfg(test)] mod tests { use super::*; - use crate::{Capabilities, Capability}; + use crate::{Capabilities, Capability, SnapVersion}; use alloy_primitives::bytes::Bytes; use alloy_rlp::{Decodable, Encodable}; use reth_eth_wire_types::RawCapabilityMessage; @@ -530,6 +558,40 @@ mod tests { assert_eq!(shared_eth.name(), "eth"); } + #[test] + fn relative_message_id_accounts_for_intermediate_capabilities() { + let intermediate_cap = Capability::new_static("foo", 1); + let intermediate = Protocol::new(intermediate_cap.clone(), 3); + let snap = Capability::snap(SnapVersion::V1); + let eth = Capability::eth(EthVersion::Eth69); + let local_capabilities = + vec![EthVersion::Eth69.into(), intermediate, Protocol::snap(SnapVersion::V1)]; + let peer_capabilities = vec![eth, intermediate_cap, snap.clone()]; + + let shared = SharedCapabilities::try_new(local_capabilities, peer_capabilities).unwrap(); + let snap_id = shared.relative_message_id(&snap, 2).unwrap(); + + assert_eq!(snap_id, EthMessageID::message_count(EthVersion::Eth69) + 3 + 2); + assert_eq!(shared.capability_message_id(&snap, snap_id), Some(2)); + } + + #[test] + fn capability_message_id_rejects_other_capability_range() { + let intermediate_cap = Capability::new_static("foo", 1); + let intermediate = Protocol::new(intermediate_cap.clone(), 3); + let snap = Capability::snap(SnapVersion::V1); + let local_capabilities = + vec![EthVersion::Eth69.into(), intermediate, Protocol::snap(SnapVersion::V1)]; + let peer_capabilities = + vec![Capability::eth(EthVersion::Eth69), intermediate_cap.clone(), snap.clone()]; + + let shared = SharedCapabilities::try_new(local_capabilities, peer_capabilities).unwrap(); + let intermediate_id = shared.relative_message_id(&intermediate_cap, 1).unwrap(); + + assert_eq!(shared.capability_message_id(&snap, intermediate_id), None); + assert_eq!(shared.relative_message_id(&snap, SnapVersion::V1.message_count()), None); + } + #[test] fn test_raw_capability_rlp() { let msg = RawCapabilityMessage { id: 1, payload: Bytes::from(vec![0x01, 0x02, 0x03]) }; From 44879c32020594ce9e322b264e4ce59022b964db Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 2 May 2026 17:55:26 +0200 Subject: [PATCH 016/335] feat(payload): expose built payload block access list (#23860) --- crates/ethereum/engine-primitives/src/payload.rs | 4 ++++ crates/payload/basic/src/stack.rs | 9 ++++++++- crates/payload/primitives/src/traits.rs | 9 ++++++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/crates/ethereum/engine-primitives/src/payload.rs b/crates/ethereum/engine-primitives/src/payload.rs index fa21d67a0fb..7f52d8e2a3c 100644 --- a/crates/ethereum/engine-primitives/src/payload.rs +++ b/crates/ethereum/engine-primitives/src/payload.rs @@ -242,6 +242,10 @@ impl BuiltPayload for EthBuiltPayload { self.fees } + fn block_access_list(&self) -> Option<&Bytes> { + self.block_access_list.as_ref() + } + fn requests(&self) -> Option { self.requests.clone() } diff --git a/crates/payload/basic/src/stack.rs b/crates/payload/basic/src/stack.rs index f953f16be23..9f390dd4d9a 100644 --- a/crates/payload/basic/src/stack.rs +++ b/crates/payload/basic/src/stack.rs @@ -3,7 +3,7 @@ use crate::{ PayloadConfig, }; -use alloy_primitives::{B256, U256}; +use alloy_primitives::{Bytes, B256, U256}; use reth_payload_builder::PayloadId; use reth_payload_primitives::{BuiltPayload, PayloadAttributes}; use reth_primitives_traits::{NodePrimitives, SealedBlock}; @@ -135,6 +135,13 @@ where } } + fn block_access_list(&self) -> Option<&Bytes> { + match self { + Self::Left(l) => l.block_access_list(), + Self::Right(r) => r.block_access_list(), + } + } + fn requests(&self) -> Option { match self { Self::Left(l) => l.requests(), diff --git a/crates/payload/primitives/src/traits.rs b/crates/payload/primitives/src/traits.rs index c031b604f53..3bc340922ed 100644 --- a/crates/payload/primitives/src/traits.rs +++ b/crates/payload/primitives/src/traits.rs @@ -3,7 +3,7 @@ use crate::PayloadBuilderError; use alloc::{boxed::Box, sync::Arc, vec::Vec}; use alloy_eips::{eip4895::Withdrawal, eip7685::Requests}; -use alloy_primitives::{B256, U256}; +use alloy_primitives::{Bytes, B256, U256}; use alloy_rlp::Encodable; use alloy_rpc_types_engine::{PayloadAttributes as EthPayloadAttributes, PayloadId}; use core::fmt; @@ -81,6 +81,13 @@ pub trait BuiltPayload: Send + Sync + fmt::Debug { /// Returns the total fees collected from all transactions in this block. fn fees(&self) -> U256; + /// Returns the EIP-7928 block access list included in this payload. + /// + /// Returns `None` for payloads that do not carry a block access list. + fn block_access_list(&self) -> Option<&Bytes> { + None + } + /// Returns the complete execution result including state updates. /// /// Returns `None` if execution data is not available or not tracked. From 30fe86d25568399bd2b64ff93562b440a5000ca4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Horv=C3=A1th?= Date: Sat, 2 May 2026 23:54:39 +0800 Subject: [PATCH 017/335] docs: update admin namespace docs (#23916) --- docs/vocs/docs/pages/jsonrpc/admin.mdx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/vocs/docs/pages/jsonrpc/admin.mdx b/docs/vocs/docs/pages/jsonrpc/admin.mdx index f8dd678ebc3..7280f2b128f 100644 --- a/docs/vocs/docs/pages/jsonrpc/admin.mdx +++ b/docs/vocs/docs/pages/jsonrpc/admin.mdx @@ -80,6 +80,7 @@ Returns true if the peer was successfully removed. Returns all information known about the running node. These include general information about the node itself, as well as what protocols it participates in, its IP and ports. +The `id` field is the keccak256 hash of the node's `enode` public key, encoded as 64 lowercase hex characters without a `0x` prefix. | Client | Method invocation | | ------ | ------------------------------ | @@ -94,7 +95,7 @@ These include general information about the node itself, as well as what protoco "id": 1, "result": { "enode": "enode://44826a5d6a55f88a18298bca4773fca5749cdc3a5c9f308aa7d810e9b31123f3e7c5fba0b1d70aac5308426f47df2a128a6747040a3815cc7dd7167d03be320d@[::]:30303", - "id": "44826a5d6a55f88a18298bca4773fca5749cdc3a5c9f308aa7d810e9b31123f3e7c5fba0b1d70aac5308426f47df2a128a6747040a3815cc7dd7167d03be320d", + "id": "8915d6ec2f53ede650d5b9bea77d7756f177092171648ee1d8cbc550d334fa7a", "ip": "::", "listenAddr": "[::]:30303", "name": "reth/v0.0.1/x86_64-unknown-linux-gnu", @@ -117,6 +118,7 @@ These include general information about the node itself, as well as what protoco ## `admin_peers` Returns information about peers currently known to the node. +For each peer, `id` is the keccak256 hash of the peer `enode` public key, encoded as 64 lowercase hex characters without a `0x` prefix. | Client | Method invocation | | ------ | ------------------------------ | @@ -128,7 +130,7 @@ Returns information about peers currently known to the node. // > {"jsonrpc":"2.0","id":1,"method":"admin_peers","params":[]} {"jsonrpc":"2.0","id":1,"result":[ { - "id":"44826a5d6a55f88a18298bca4773fca...", + "id":"8915d6ec2f53ede650d5b9bea77d7756f177092171648ee1d8cbc550d334fa7a", "name":"reth/v0.0.1/x86_64-unknown-linux-gnu", "enode":"enode://44826a5d6a55f88a18298bca4773fca5749cdc3a5c9f308aa7d810e9b31123f3e7c5fba0b1d70aac5308426f47df2a128a6747040a3815cc7dd7167d03be320d@192.168.1.1:30303", "enr":"enr:-IS4QHCYr...", @@ -190,7 +192,7 @@ The subscription emits events with the following structure: "result": { "type": "add", // or "drop", "error" "peer": { - "id": "44826a5d6a55f88a18298bca4773fca5749cdc3a5c9f308aa7d810e9b31123f3e7c5fba0b1d70aac5308426f47df2a128a6747040a3815cc7dd7167d03be320d", + "id": "8915d6ec2f53ede650d5b9bea77d7756f177092171648ee1d8cbc550d334fa7a", "enode": "enode://44826a5d6a55f88a18298bca4773fca5749cdc3a5c9f308aa7d810e9b31123f3e7c5fba0b1d70aac5308426f47df2a128a6747040a3815cc7dd7167d03be320d@192.168.1.1:30303", "addr": "192.168.1.1:30303" }, @@ -208,7 +210,7 @@ The subscription emits events with the following structure: {"jsonrpc": "2.0", "id": 1, "result": "0xcd0c3e8af590364c09d0fa6a1210faf5"} // Example event when a peer connects -{"jsonrpc":"2.0","method":"admin_subscription","params":{"subscription":"0xcd0c3e8af590364c09d0fa6a1210faf5","result":{"type":"add","peer":{"id":"44826a5d6a55f88a18298bca4773fca5749cdc3a5c9f308aa7d810e9b31123f3e7c5fba0b1d70aac5308426f47df2a128a6747040a3815cc7dd7167d03be320d","enode":"enode://44826a5d6a55f88a18298bca4773fca5749cdc3a5c9f308aa7d810e9b31123f3e7c5fba0b1d70aac5308426f47df2a128a6747040a3815cc7dd7167d03be320d@192.168.1.1:30303","addr":"192.168.1.1:30303"}}}} +{"jsonrpc":"2.0","method":"admin_subscription","params":{"subscription":"0xcd0c3e8af590364c09d0fa6a1210faf5","result":{"type":"add","peer":{"id":"8915d6ec2f53ede650d5b9bea77d7756f177092171648ee1d8cbc550d334fa7a","enode":"enode://44826a5d6a55f88a18298bca4773fca5749cdc3a5c9f308aa7d810e9b31123f3e7c5fba0b1d70aac5308426f47df2a128a6747040a3815cc7dd7167d03be320d@192.168.1.1:30303","addr":"192.168.1.1:30303"}}}} // Unsubscribe // > {"jsonrpc":"2.0","id":2,"method":"admin_peerEvents_unsubscribe","params":["0xcd0c3e8af590364c09d0fa6a1210faf5"]} From ddb3819ec945958ca01032390b8c7a1c83e8df2f Mon Sep 17 00:00:00 2001 From: Karl Yu <43113774+0xKarl98@users.noreply.github.com> Date: Sun, 3 May 2026 15:13:58 +0800 Subject: [PATCH 018/335] feat(storage): add in-memory BAL retention (#23873) Co-authored-by: Matthias Seitz --- Cargo.lock | 5 +- Cargo.toml | 2 +- crates/storage/provider/Cargo.toml | 1 + crates/storage/provider/src/bal.rs | 169 +++++++++++++++++++++++++++-- crates/storage/provider/src/lib.rs | 2 +- 5 files changed, 166 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6abc29632f3..4dfd3c8b791 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -250,9 +250,9 @@ dependencies = [ [[package]] name = "alloy-eip7928" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "407510740da514b694fecb44d8b3cebdc60d448f70cc5d24743e8ba273448a6e" +checksum = "ec6ae911a2fc304a7cb80a79fb7bed6d1474aed4e7c203df1f8ff538f64fc78d" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -9557,6 +9557,7 @@ name = "reth-provider" version = "2.2.0" dependencies = [ "alloy-consensus", + "alloy-eip7928", "alloy-eips 2.0.4", "alloy-genesis", "alloy-primitives", diff --git a/Cargo.toml b/Cargo.toml index f30a175896b..f93cd646b90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -449,7 +449,7 @@ alloy-sol-types = { version = "1.5.6", default-features = false } alloy-chains = { version = "0.2.33", default-features = false } alloy-eip2124 = { version = "0.2.0", default-features = false } -alloy-eip7928 = { version = "0.3.4", default-features = false } +alloy-eip7928 = { version = "0.3.5", default-features = false } alloy-evm = { version = "0.34.0", default-features = false } alloy-rlp = { version = "0.3.13", default-features = false, features = ["core-net"] } alloy-trie = { version = "0.9.4", default-features = false } diff --git a/crates/storage/provider/Cargo.toml b/crates/storage/provider/Cargo.toml index f5ca403530f..40c4f13268d 100644 --- a/crates/storage/provider/Cargo.toml +++ b/crates/storage/provider/Cargo.toml @@ -35,6 +35,7 @@ reth-static-file-types = { workspace = true, features = ["std"] } reth-fs-util.workspace = true # ethereum +alloy-eip7928.workspace = true alloy-eips.workspace = true alloy-genesis.workspace = true alloy-primitives.workspace = true diff --git a/crates/storage/provider/src/bal.rs b/crates/storage/provider/src/bal.rs index b6804a1b456..2f7e6013709 100644 --- a/crates/storage/provider/src/bal.rs +++ b/crates/storage/provider/src/bal.rs @@ -1,32 +1,129 @@ +use alloy_eip7928::BAL_RETENTION_PERIOD_SLOTS; use alloy_primitives::{BlockHash, BlockNumber, Bytes}; use parking_lot::RwLock; +use reth_prune_types::PruneMode; use reth_storage_api::{BalStore, GetBlockAccessListLimit}; use reth_storage_errors::provider::ProviderResult; -use std::{collections::HashMap, sync::Arc}; +use std::{ + collections::{BTreeMap, HashMap}, + sync::Arc, +}; /// Basic in-memory BAL store keyed by block hash. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct InMemoryBalStore { - entries: Arc>>, + config: BalConfig, + inner: Arc>, +} + +impl InMemoryBalStore { + /// Creates a new in-memory BAL store with the given config. + pub fn new(config: BalConfig) -> Self { + Self { config, inner: Arc::new(RwLock::new(InMemoryBalStoreInner::default())) } + } +} + +impl Default for InMemoryBalStore { + fn default() -> Self { + Self::new(BalConfig::default()) + } +} + +/// Configuration for BAL storage. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub struct BalConfig { + /// Retention policy for BALs kept in memory. + in_memory_retention: Option, +} + +impl BalConfig { + /// Returns a config with no in-memory BAL retention limit. + pub const fn unbounded() -> Self { + Self { in_memory_retention: None } + } + + /// Returns a config with the given in-memory BAL retention policy. + pub const fn with_in_memory_retention(in_memory_retention: PruneMode) -> Self { + Self { in_memory_retention: Some(in_memory_retention) } + } +} + +impl Default for BalConfig { + fn default() -> Self { + Self::with_in_memory_retention(PruneMode::Distance(BAL_RETENTION_PERIOD_SLOTS)) + } +} + +#[derive(Debug, Default)] +struct InMemoryBalStoreInner { + entries: HashMap, + hashes_by_number: BTreeMap>, + highest_block_number: Option, +} + +impl InMemoryBalStoreInner { + // Inserts a BAL and keeps the block-number index in sync. + fn insert(&mut self, block_hash: BlockHash, block_number: BlockNumber, bal: Bytes) { + let empty_block_number = + self.entries.insert(block_hash, BalEntry { block_number, bal }).and_then(|entry| { + let hashes = self.hashes_by_number.get_mut(&entry.block_number)?; + hashes.retain(|hash| *hash != block_hash); + hashes.is_empty().then_some(entry.block_number) + }); + + if let Some(block_number) = empty_block_number { + self.hashes_by_number.remove(&block_number); + } + + self.hashes_by_number.entry(block_number).or_default().push(block_hash); + self.highest_block_number = Some( + self.highest_block_number.map_or(block_number, |highest| highest.max(block_number)), + ); + } + + // Removes BALs outside the configured retention window. + fn prune(&mut self, prune_mode: Option) { + let Some(prune_mode) = prune_mode else { return }; + let Some(tip) = self.highest_block_number else { return }; + + while let Some((&block_number, _)) = self.hashes_by_number.first_key_value() { + if !prune_mode.should_prune(block_number, tip) { + break + } + + let Some((_, hashes)) = self.hashes_by_number.pop_first() else { break }; + for hash in hashes { + self.entries.remove(&hash); + } + } + } +} + +#[derive(Debug)] +struct BalEntry { + block_number: BlockNumber, + bal: Bytes, } impl BalStore for InMemoryBalStore { fn insert( &self, block_hash: BlockHash, - _block_number: BlockNumber, + block_number: BlockNumber, bal: Bytes, ) -> ProviderResult<()> { - self.entries.write().insert(block_hash, bal); + let mut inner = self.inner.write(); + inner.insert(block_hash, block_number, bal); + inner.prune(self.config.in_memory_retention); Ok(()) } fn get_by_hashes(&self, block_hashes: &[BlockHash]) -> ProviderResult>> { - let entries = self.entries.read(); + let inner = self.inner.read(); let mut result = Vec::with_capacity(block_hashes.len()); for hash in block_hashes { - result.push(entries.get(hash).cloned()); + result.push(inner.entries.get(hash).map(|entry| entry.bal.clone())); } Ok(result) @@ -38,11 +135,15 @@ impl BalStore for InMemoryBalStore { limit: GetBlockAccessListLimit, out: &mut Vec, ) -> ProviderResult<()> { - let entries = self.entries.read(); + let inner = self.inner.read(); let mut size = 0; for hash in block_hashes { - let bal = entries.get(hash).cloned().unwrap_or_else(|| Bytes::from_static(&[0xc0])); + let bal = inner + .entries + .get(hash) + .map(|entry| entry.bal.clone()) + .unwrap_or_else(|| Bytes::from_static(&[0xc0])); size += bal.len(); out.push(bal); @@ -106,4 +207,54 @@ mod tests { assert_eq!(limited, vec![bal0, bal1]); } + + #[test] + fn default_retention_prunes_old_bals() { + let store = InMemoryBalStore::default(); + let old_hash = B256::random(); + let retained_hash = B256::random(); + let tip_hash = B256::random(); + let old_bal = Bytes::from_static(b"old"); + let retained_bal = Bytes::from_static(b"retained"); + let tip_bal = Bytes::from_static(b"tip"); + + store.insert(old_hash, 1, old_bal).unwrap(); + store.insert(retained_hash, BAL_RETENTION_PERIOD_SLOTS, retained_bal.clone()).unwrap(); + store.insert(tip_hash, BAL_RETENTION_PERIOD_SLOTS + 2, tip_bal.clone()).unwrap(); + + assert_eq!( + store.get_by_hashes(&[old_hash, retained_hash, tip_hash]).unwrap(), + vec![None, Some(retained_bal), Some(tip_bal)] + ); + } + + #[test] + fn unbounded_retention_keeps_old_bals() { + let store = InMemoryBalStore::new(BalConfig::unbounded()); + let old_hash = B256::random(); + let tip_hash = B256::random(); + let old_bal = Bytes::from_static(b"old"); + let tip_bal = Bytes::from_static(b"tip"); + + store.insert(old_hash, 1, old_bal.clone()).unwrap(); + store.insert(tip_hash, BAL_RETENTION_PERIOD_SLOTS + 1, tip_bal.clone()).unwrap(); + + assert_eq!( + store.get_by_hashes(&[old_hash, tip_hash]).unwrap(), + vec![Some(old_bal), Some(tip_bal)] + ); + } + + #[test] + fn reinserting_hash_updates_number_index() { + let store = + InMemoryBalStore::new(BalConfig::with_in_memory_retention(PruneMode::Before(2))); + let hash = B256::random(); + let bal = Bytes::from_static(b"bal"); + + store.insert(hash, 1, Bytes::from_static(b"old")).unwrap(); + store.insert(hash, 2, bal.clone()).unwrap(); + + assert_eq!(store.get_by_hashes(&[hash]).unwrap(), vec![Some(bal)]); + } } diff --git a/crates/storage/provider/src/lib.rs b/crates/storage/provider/src/lib.rs index 890051b9d1b..dfadfbc0ae1 100644 --- a/crates/storage/provider/src/lib.rs +++ b/crates/storage/provider/src/lib.rs @@ -39,7 +39,7 @@ pub mod either_writer; pub use either_writer::*; mod bal; -pub use bal::InMemoryBalStore; +pub use bal::{BalConfig, InMemoryBalStore}; pub use reth_chain_state::{ CanonStateNotification, CanonStateNotificationSender, CanonStateNotificationStream, From d2b4ab53d4189b682afe12af090111347df3b1c5 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 4 May 2026 11:03:18 +0200 Subject: [PATCH 019/335] chore: fix nightly clippy warnings (#23923) --- crates/cli/commands/src/test_vectors/compact.rs | 8 ++++---- crates/consensus/debug-client/src/providers/etherscan.rs | 2 +- crates/storage/provider/src/test_utils/mock.rs | 2 +- crates/transaction-pool/src/blobstore/mem.rs | 4 ++-- examples/beacon-api-sidecar-fetcher/src/mined_sidecar.rs | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/crates/cli/commands/src/test_vectors/compact.rs b/crates/cli/commands/src/test_vectors/compact.rs index f4636f5f83b..5f83308510a 100644 --- a/crates/cli/commands/src/test_vectors/compact.rs +++ b/crates/cli/commands/src/test_vectors/compact.rs @@ -194,7 +194,7 @@ where T: for<'a> Arbitrary<'a> + reth_codecs::Compact, { let type_name = type_name::(); - print!("{}", &type_name); + print!("{}", type_name); let mut bytes = std::iter::repeat_n(0u8, 256).collect::>(); let mut compact_buffer = vec![]; @@ -230,7 +230,7 @@ where serde_json::to_writer( std::io::BufWriter::new( - std::fs::File::create(format!("{VECTORS_FOLDER}/{}.json", &type_name)).unwrap(), + std::fs::File::create(format!("{VECTORS_FOLDER}/{}.json", type_name)).unwrap(), ), &values, )?; @@ -247,10 +247,10 @@ where T: reth_codecs::Compact, { let type_name = type_name::(); - print!("{}", &type_name); + print!("{}", type_name); // Read the file where the vectors are stored - let file_path = format!("{VECTORS_FOLDER}/{}.json", &type_name); + let file_path = format!("{VECTORS_FOLDER}/{}.json", type_name); let file = File::open(&file_path).wrap_err_with(|| format!("Failed to open vector {type_name}."))?; let reader = BufReader::new(file); diff --git a/crates/consensus/debug-client/src/providers/etherscan.rs b/crates/consensus/debug-client/src/providers/etherscan.rs index ea21d95e73d..92dab80bf7f 100644 --- a/crates/consensus/debug-client/src/providers/etherscan.rs +++ b/crates/consensus/debug-client/src/providers/etherscan.rs @@ -55,7 +55,7 @@ where block_number_or_tag: BlockNumberOrTag, ) -> eyre::Result { let tag = match block_number_or_tag { - BlockNumberOrTag::Number(num) => format!("{num:#02x}"), + BlockNumberOrTag::Number(num) => format!("{num:#x}"), tag => tag.to_string(), }; diff --git a/crates/storage/provider/src/test_utils/mock.rs b/crates/storage/provider/src/test_utils/mock.rs index 72fa695a0a2..04f1bab12ff 100644 --- a/crates/storage/provider/src/test_utils/mock.rs +++ b/crates/storage/provider/src/test_utils/mock.rs @@ -441,7 +441,7 @@ impl TransactionsProvider ) -> ProviderResult>> { // init btreemap so we can return in order let mut map = BTreeMap::new(); - for (_, block) in self.blocks.lock().iter() { + for block in self.blocks.lock().values() { if range.contains(&block.header().number()) { map.insert(block.header().number(), block.body().clone_transactions()); } diff --git a/crates/transaction-pool/src/blobstore/mem.rs b/crates/transaction-pool/src/blobstore/mem.rs index dc7672e9859..a7a98a574f2 100644 --- a/crates/transaction-pool/src/blobstore/mem.rs +++ b/crates/transaction-pool/src/blobstore/mem.rs @@ -25,7 +25,7 @@ impl InMemoryBlobStore { ) -> Vec> { let mut result = vec![None; versioned_hashes.len()]; let mut missing_count = result.len(); - for (_tx_hash, blob_sidecar) in self.inner.store.read().iter() { + for blob_sidecar in self.inner.store.read().values() { if let Some(blob_sidecar) = blob_sidecar.as_eip7594() { for (hash_idx, match_result) in blob_sidecar.match_versioned_hashes(versioned_hashes) @@ -181,7 +181,7 @@ impl BlobStore for InMemoryBlobStore { versioned_hashes: &[B256], ) -> Result>, BlobStoreError> { let mut result = vec![None; versioned_hashes.len()]; - for (_tx_hash, blob_sidecar) in self.inner.store.read().iter() { + for blob_sidecar in self.inner.store.read().values() { if let Some(blob_sidecar) = blob_sidecar.as_eip4844() { for (hash_idx, match_result) in blob_sidecar.match_versioned_hashes(versioned_hashes) diff --git a/examples/beacon-api-sidecar-fetcher/src/mined_sidecar.rs b/examples/beacon-api-sidecar-fetcher/src/mined_sidecar.rs index 47c8b8bd30a..5ebf273a404 100644 --- a/examples/beacon-api-sidecar-fetcher/src/mined_sidecar.rs +++ b/examples/beacon-api-sidecar-fetcher/src/mined_sidecar.rs @@ -188,13 +188,13 @@ where { match notification { CanonStateNotification::Commit { new } => { - for (_, block) in new.blocks().iter() { + for block in new.blocks().values() { this.process_block(block); } } CanonStateNotification::Reorg { old, new } => { // handle reorged blocks - for (_, block) in old.blocks().iter() { + for block in old.blocks().values() { let txs: Vec = block .body() .transactions() @@ -215,7 +215,7 @@ where this.queued_actions.extend(txs); } - for (_, block) in new.blocks().iter() { + for block in new.blocks().values() { this.process_block(block); } } From 8940f2f0d66757ac35dd7d8d632c67d701c4c0ea Mon Sep 17 00:00:00 2001 From: Karl Yu <43113774+0xKarl98@users.noreply.github.com> Date: Mon, 4 May 2026 17:21:41 +0800 Subject: [PATCH 020/335] feat(storage): add BAL notification stream (#23918) Co-authored-by: Matthias Seitz --- Cargo.lock | 5 + crates/net/network/tests/it/requests.rs | 27 +++-- crates/storage/provider/Cargo.toml | 6 +- crates/storage/provider/src/bal.rs | 143 ++++++++++++++++++++---- crates/storage/provider/src/lib.rs | 6 +- crates/storage/storage-api/Cargo.toml | 7 ++ crates/storage/storage-api/src/bal.rs | 86 ++++++++++---- crates/trie/sparse/src/parallel.rs | 4 +- 8 files changed, 225 insertions(+), 59 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4dfd3c8b791..cfb2bc8b0d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9591,6 +9591,7 @@ dependencies = [ "reth-storage-errors", "reth-tasks", "reth-testing-utils", + "reth-tokio-util", "reth-tracing", "reth-trie", "reth-trie-db", @@ -9601,6 +9602,7 @@ dependencies = [ "strum", "tempfile", "tokio", + "tokio-stream", "tracing", ] @@ -10251,9 +10253,12 @@ dependencies = [ "reth-prune-types", "reth-stages-types", "reth-storage-errors", + "reth-tokio-util", "reth-trie-common", "revm-database", "serde_json", + "tokio", + "tokio-stream", ] [[package]] diff --git a/crates/net/network/tests/it/requests.rs b/crates/net/network/tests/it/requests.rs index ce5d57cfe4b..a209537c9af 100644 --- a/crates/net/network/tests/it/requests.rs +++ b/crates/net/network/tests/it/requests.rs @@ -2,7 +2,8 @@ //! Tests for eth related requests use alloy_consensus::Header; -use alloy_primitives::{Bytes, B256}; +use alloy_eips::NumHash; +use alloy_primitives::{keccak256, Bytes, Sealed, B256}; use rand::Rng; use reth_eth_wire::{BlockAccessLists, EthVersion, GetBlockAccessLists, HeadersDirection}; use reth_ethereum_primitives::Block; @@ -18,7 +19,7 @@ use reth_network_p2p::{ headers::client::{HeadersClient, HeadersRequest}, BalRequirement, BlockAccessListsClient, }; -use reth_provider::{test_utils::MockEthProvider, BalStoreHandle, InMemoryBalStore}; +use reth_provider::{test_utils::MockEthProvider, BalStoreHandle, InMemoryBalStore, SealedBal}; use reth_transaction_pool::test_utils::{TestPool, TransactionGenerator}; use std::sync::Arc; use tokio::sync::oneshot; @@ -544,8 +545,8 @@ async fn test_eth71_get_block_access_lists() { let bal0 = Bytes::from_static(&[0xc1, 0x01]); let bal2 = Bytes::from_static(&[0xc1, 0x02]); - bal_store.insert(hash0, 1, bal0.clone()).unwrap(); - bal_store.insert(hash2, 3, bal2.clone()).unwrap(); + bal_store.insert(NumHash::new(1, hash0), sealed_bal(bal0.clone())).unwrap(); + bal_store.insert(NumHash::new(3, hash2), sealed_bal(bal2.clone())).unwrap(); let response = request_block_access_lists(&net, vec![hash0, hash1, hash2]).await; assert_eq!( @@ -568,9 +569,9 @@ async fn test_eth71_get_block_access_lists_respects_response_soft_limit() { let bal2 = raw_bal_with_len(2); assert!(bal0.len() + bal1.len() > SOFT_RESPONSE_LIMIT); - bal_store.insert(hash0, 1, bal0.clone()).unwrap(); - bal_store.insert(hash1, 2, bal1.clone()).unwrap(); - bal_store.insert(hash2, 3, bal2).unwrap(); + bal_store.insert(NumHash::new(1, hash0), sealed_bal(bal0.clone())).unwrap(); + bal_store.insert(NumHash::new(2, hash1), sealed_bal(bal1.clone())).unwrap(); + bal_store.insert(NumHash::new(3, hash2), sealed_bal(bal2)).unwrap(); let response = request_block_access_lists(&net, vec![hash0, hash1, hash2]).await; @@ -588,8 +589,8 @@ async fn test_eth71_get_block_access_lists_returns_single_oversized_bal() { let bal0 = raw_bal_with_len(SOFT_RESPONSE_LIMIT + 1); let bal1 = raw_bal_with_len(2); - bal_store.insert(hash0, 1, bal0.clone()).unwrap(); - bal_store.insert(hash1, 2, bal1).unwrap(); + bal_store.insert(NumHash::new(1, hash0), sealed_bal(bal0.clone())).unwrap(); + bal_store.insert(NumHash::new(2, hash1), sealed_bal(bal1)).unwrap(); let response = request_block_access_lists(&net, vec![hash0, hash1]).await; @@ -620,7 +621,7 @@ async fn test_eth71_get_block_access_lists_caps_count() { // Insert one BAL so the store isn't entirely empty (not strictly needed, // but keeps the test path closer to real usage). let bal = Bytes::from_static(&[0xc1, 0x01]); - bal_store.insert(hashes[0], 1, bal).unwrap(); + bal_store.insert(NumHash::new(1, hashes[0]), sealed_bal(bal)).unwrap(); let response = request_block_access_lists(&net, hashes).await; @@ -637,7 +638,7 @@ async fn test_eth71_fetch_client_get_block_access_lists() { let hash1 = B256::random(); let bal0 = Bytes::from_static(&[0xc1, 0x01]); - bal_store.insert(hash0, 1, bal0.clone()).unwrap(); + bal_store.insert(NumHash::new(1, hash0), sealed_bal(bal0.clone())).unwrap(); let fetch = net.peers()[0].network().fetch_client().await.unwrap(); let response = fetch.get_block_access_lists(vec![hash0, hash1]).await.unwrap().into_data(); @@ -727,3 +728,7 @@ fn raw_bal_with_len(len: usize) -> Bytes { out.resize(len, alloy_rlp::EMPTY_LIST_CODE); Bytes::from(out) } + +fn sealed_bal(bal: Bytes) -> SealedBal { + Sealed::new_unchecked(bal.clone(), keccak256(&bal)) +} diff --git a/crates/storage/provider/Cargo.toml b/crates/storage/provider/Cargo.toml index 40c4f13268d..052ba6c3e9c 100644 --- a/crates/storage/provider/Cargo.toml +++ b/crates/storage/provider/Cargo.toml @@ -57,10 +57,11 @@ notify = { workspace = true, default-features = false, features = ["macos_fseven parking_lot.workspace = true strum.workspace = true eyre.workspace = true +reth-tokio-util.workspace = true +tokio = { workspace = true, features = ["sync"], optional = true } # test-utils reth-ethereum-engine-primitives = { workspace = true, optional = true } -tokio = { workspace = true, features = ["sync"], optional = true } # parallel utils rayon.workspace = true @@ -85,6 +86,7 @@ assert_matches.workspace = true rand.workspace = true tokio = { workspace = true, features = ["sync", "macros", "rt-multi-thread"] } +tokio-stream = { workspace = true, features = ["sync"] } [features] jemalloc = ["rocksdb/jemalloc"] @@ -103,6 +105,6 @@ test-utils = [ "reth-prune-types/test-utils", "reth-stages-types/test-utils", "revm-state", - "tokio", "reth-tasks/test-utils", + "dep:tokio", ] diff --git a/crates/storage/provider/src/bal.rs b/crates/storage/provider/src/bal.rs index 2f7e6013709..fa68bbc3446 100644 --- a/crates/storage/provider/src/bal.rs +++ b/crates/storage/provider/src/bal.rs @@ -1,9 +1,13 @@ use alloy_eip7928::BAL_RETENTION_PERIOD_SLOTS; +use alloy_eips::NumHash; use alloy_primitives::{BlockHash, BlockNumber, Bytes}; use parking_lot::RwLock; use reth_prune_types::PruneMode; -use reth_storage_api::{BalStore, GetBlockAccessListLimit}; +use reth_storage_api::{ + BalNotification, BalNotificationStream, BalStore, GetBlockAccessListLimit, SealedBal, +}; use reth_storage_errors::provider::ProviderResult; +use reth_tokio_util::EventSender; use std::{ collections::{BTreeMap, HashMap}, sync::Arc, @@ -14,15 +18,25 @@ use std::{ pub struct InMemoryBalStore { config: BalConfig, inner: Arc>, + notifications: EventSender, } impl InMemoryBalStore { /// Creates a new in-memory BAL store with the given config. pub fn new(config: BalConfig) -> Self { - Self { config, inner: Arc::new(RwLock::new(InMemoryBalStoreInner::default())) } + let notifications = EventSender::new(DEFAULT_BAL_NOTIFICATION_CHANNEL_SIZE); + Self { + config, + inner: Arc::new(RwLock::new(InMemoryBalStoreInner::default())), + notifications, + } } } +// Match the canonical state broadcast buffer so BAL subscriptions behave like the existing +// in-memory notification path. This is a bounded best-effort channel, not a durability boundary. +const DEFAULT_BAL_NOTIFICATION_CHANNEL_SIZE: usize = 256; + impl Default for InMemoryBalStore { fn default() -> Self { Self::new(BalConfig::default()) @@ -106,15 +120,11 @@ struct BalEntry { } impl BalStore for InMemoryBalStore { - fn insert( - &self, - block_hash: BlockHash, - block_number: BlockNumber, - bal: Bytes, - ) -> ProviderResult<()> { + fn insert(&self, num_hash: NumHash, bal: SealedBal) -> ProviderResult<()> { let mut inner = self.inner.write(); - inner.insert(block_hash, block_number, bal); + inner.insert(num_hash.hash, num_hash.number, bal.clone_inner()); inner.prune(self.config.in_memory_retention); + self.notifications.notify(BalNotification::new(num_hash, bal)); Ok(()) } @@ -158,12 +168,21 @@ impl BalStore for InMemoryBalStore { fn get_by_range(&self, _start: BlockNumber, _count: u64) -> ProviderResult> { Ok(Vec::new()) } + + fn bal_stream(&self) -> BalNotificationStream { + self.notifications.new_listener() + } } #[cfg(test)] mod tests { use super::*; - use alloy_primitives::B256; + use alloy_primitives::{keccak256, Sealed, B256}; + use tokio_stream::StreamExt; + + fn sealed_bal(bal: Bytes) -> SealedBal { + Sealed::new_unchecked(bal.clone(), keccak256(&bal)) + } #[test] fn insert_and_lookup_by_hash() { @@ -172,7 +191,7 @@ mod tests { let missing = B256::random(); let bal = Bytes::from_static(b"bal"); - store.insert(hash, 1, bal.clone()).unwrap(); + store.insert(NumHash::new(1, hash), sealed_bal(bal.clone())).unwrap(); assert_eq!(store.get_by_hashes(&[hash, missing]).unwrap(), vec![Some(bal), None]); } @@ -194,9 +213,9 @@ mod tests { let bal1 = Bytes::from_static(&[0xc1, 0x02]); let bal2 = Bytes::from_static(&[0xc1, 0x03]); - store.insert(hash0, 1, bal0.clone()).unwrap(); - store.insert(hash1, 2, bal1.clone()).unwrap(); - store.insert(hash2, 3, bal2).unwrap(); + store.insert(NumHash::new(1, hash0), sealed_bal(bal0.clone())).unwrap(); + store.insert(NumHash::new(2, hash1), sealed_bal(bal1.clone())).unwrap(); + store.insert(NumHash::new(3, hash2), sealed_bal(bal2)).unwrap(); let limited = store .get_by_hashes_with_limit( @@ -218,9 +237,19 @@ mod tests { let retained_bal = Bytes::from_static(b"retained"); let tip_bal = Bytes::from_static(b"tip"); - store.insert(old_hash, 1, old_bal).unwrap(); - store.insert(retained_hash, BAL_RETENTION_PERIOD_SLOTS, retained_bal.clone()).unwrap(); - store.insert(tip_hash, BAL_RETENTION_PERIOD_SLOTS + 2, tip_bal.clone()).unwrap(); + store.insert(NumHash::new(1, old_hash), sealed_bal(old_bal)).unwrap(); + store + .insert( + NumHash::new(BAL_RETENTION_PERIOD_SLOTS, retained_hash), + sealed_bal(retained_bal.clone()), + ) + .unwrap(); + store + .insert( + NumHash::new(BAL_RETENTION_PERIOD_SLOTS + 2, tip_hash), + sealed_bal(tip_bal.clone()), + ) + .unwrap(); assert_eq!( store.get_by_hashes(&[old_hash, retained_hash, tip_hash]).unwrap(), @@ -236,8 +265,13 @@ mod tests { let old_bal = Bytes::from_static(b"old"); let tip_bal = Bytes::from_static(b"tip"); - store.insert(old_hash, 1, old_bal.clone()).unwrap(); - store.insert(tip_hash, BAL_RETENTION_PERIOD_SLOTS + 1, tip_bal.clone()).unwrap(); + store.insert(NumHash::new(1, old_hash), sealed_bal(old_bal.clone())).unwrap(); + store + .insert( + NumHash::new(BAL_RETENTION_PERIOD_SLOTS + 1, tip_hash), + sealed_bal(tip_bal.clone()), + ) + .unwrap(); assert_eq!( store.get_by_hashes(&[old_hash, tip_hash]).unwrap(), @@ -252,9 +286,76 @@ mod tests { let hash = B256::random(); let bal = Bytes::from_static(b"bal"); - store.insert(hash, 1, Bytes::from_static(b"old")).unwrap(); - store.insert(hash, 2, bal.clone()).unwrap(); + store.insert(NumHash::new(1, hash), sealed_bal(Bytes::from_static(b"old"))).unwrap(); + store.insert(NumHash::new(2, hash), sealed_bal(bal.clone())).unwrap(); assert_eq!(store.get_by_hashes(&[hash]).unwrap(), vec![Some(bal)]); } + + #[tokio::test] + async fn insert_notifies_subscribers() { + let store = InMemoryBalStore::default(); + let hash = B256::random(); + let block_number = 7; + let bal = Bytes::from_static(b"bal"); + let mut stream = store.bal_stream(); + + let sealed_bal = sealed_bal(bal); + + store.insert(NumHash::new(block_number, hash), sealed_bal.clone()).unwrap(); + + assert_eq!( + stream.next().await.unwrap(), + BalNotification::new(NumHash::new(block_number, hash), sealed_bal) + ); + } + + #[test] + fn insert_without_subscribers_still_succeeds() { + let store = InMemoryBalStore::default(); + + assert!(store + .insert(NumHash::new(1, B256::random()), sealed_bal(Bytes::from_static(b"bal"))) + .is_ok()); + } + + #[tokio::test] + async fn bal_stream_skips_lagged_notifications() { + let store = InMemoryBalStore::new(BalConfig::unbounded()); + let mut stream = store.bal_stream(); + + for number in 0..=DEFAULT_BAL_NOTIFICATION_CHANNEL_SIZE as u64 { + store + .insert( + NumHash::new(number, B256::random()), + sealed_bal(Bytes::from(vec![number as u8])), + ) + .unwrap(); + } + + let first = stream.next().await.unwrap(); + let second = stream.next().await.unwrap(); + + assert_eq!(first.num_hash.number, 1); + assert_eq!(second.num_hash.number, 2); + } + + #[tokio::test] + async fn cloned_store_shares_notification_channel() { + let store = InMemoryBalStore::default(); + let clone = store.clone(); + let hash = B256::random(); + let block_number = 9; + let bal = Bytes::from_static(b"bal"); + let mut stream = clone.bal_stream(); + + let sealed_bal = sealed_bal(bal); + + store.insert(NumHash::new(block_number, hash), sealed_bal.clone()).unwrap(); + + assert_eq!( + stream.next().await.unwrap(), + BalNotification::new(NumHash::new(block_number, hash), sealed_bal) + ); + } } diff --git a/crates/storage/provider/src/lib.rs b/crates/storage/provider/src/lib.rs index dfadfbc0ae1..909a9c24b38 100644 --- a/crates/storage/provider/src/lib.rs +++ b/crates/storage/provider/src/lib.rs @@ -51,9 +51,9 @@ pub use revm_database::states::OriginalValuesKnown; // reexport traits to avoid breaking changes pub use reth_static_file_types as static_file; pub use reth_storage_api::{ - BalProvider, BalStore, BalStoreHandle, GetBlockAccessListLimit, HistoryWriter, - MetadataProvider, MetadataWriter, NoopBalStore, StateWriteConfig, StatsReader, StorageSettings, - StorageSettingsCache, + BalNotification, BalNotificationStream, BalProvider, BalStore, BalStoreHandle, + GetBlockAccessListLimit, HistoryWriter, MetadataProvider, MetadataWriter, NoopBalStore, + SealedBal, StateWriteConfig, StatsReader, StorageSettings, StorageSettingsCache, }; /// Re-export provider error. pub use reth_storage_errors::provider::{ProviderError, ProviderResult}; diff --git a/crates/storage/storage-api/Cargo.toml b/crates/storage/storage-api/Cargo.toml index 591b3205016..d00ad0e5dcd 100644 --- a/crates/storage/storage-api/Cargo.toml +++ b/crates/storage/storage-api/Cargo.toml @@ -33,6 +33,12 @@ alloy-rpc-types-engine.workspace = true auto_impl.workspace = true serde_json = { workspace = true, optional = true } +reth-tokio-util = { workspace = true, optional = true } + +[dev-dependencies] +alloy-primitives = { workspace = true, features = ["getrandom"] } +tokio = { workspace = true, features = ["macros", "rt"] } +tokio-stream.workspace = true [features] default = ["std"] @@ -52,6 +58,7 @@ std = [ "reth-db-models/std", "reth-trie-common/std", "serde_json?/std", + "dep:reth-tokio-util", ] db-api = [ diff --git a/crates/storage/storage-api/src/bal.rs b/crates/storage/storage-api/src/bal.rs index 4e82595d3d2..c6dead0bb36 100644 --- a/crates/storage/storage-api/src/bal.rs +++ b/crates/storage/storage-api/src/bal.rs @@ -1,7 +1,38 @@ use alloc::{sync::Arc, vec::Vec}; -use alloy_primitives::{BlockHash, BlockNumber, Bytes}; +use alloy_eips::NumHash; +use alloy_primitives::{BlockHash, BlockNumber, Bytes, Sealed}; use reth_storage_errors::provider::ProviderResult; +/// Raw BAL RLP bytes sealed by the BAL hash. +pub type SealedBal = Sealed; + +/// Notification emitted when a new BAL is inserted into the store. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct BalNotification { + /// Number and hash of the block the BAL belongs to. + pub num_hash: NumHash, + /// Raw BAL RLP payload sealed by the BAL hash. + pub bal: SealedBal, +} + +impl BalNotification { + /// Creates a new [`BalNotification`]. + pub const fn new(num_hash: NumHash, bal: SealedBal) -> Self { + Self { num_hash, bal } + } +} + +#[cfg(feature = "std")] +pub use self::subscriptions::BalNotificationStream; + +#[cfg(feature = "std")] +mod subscriptions { + use super::BalNotification; + + /// A stream of [`BalNotification`]s. + pub type BalNotificationStream = reth_tokio_util::EventStream; +} + /// Store for Block Access Lists (BALs). /// /// This abstraction intentionally does not prescribe where BALs live. Implementations may keep @@ -10,12 +41,7 @@ use reth_storage_errors::provider::ProviderResult; #[auto_impl::auto_impl(&, Arc, Box)] pub trait BalStore: Send + Sync + 'static { /// Insert the BAL for the given block. - fn insert( - &self, - block_hash: BlockHash, - block_number: BlockNumber, - bal: Bytes, - ) -> ProviderResult<()>; + fn insert(&self, num_hash: NumHash, bal: SealedBal) -> ProviderResult<()>; /// Fetch BALs for the given block hashes. /// @@ -64,6 +90,13 @@ pub trait BalStore: Send + Sync + 'static { /// /// Implementations may stop at the first gap and return the contiguous prefix. fn get_by_range(&self, start: BlockNumber, count: u64) -> ProviderResult>; + + /// Returns a stream of BAL insert notifications. + /// + /// Notifications are emitted only after a BAL has been successfully inserted into the store. + /// They do not imply canonicality. + #[cfg(feature = "std")] + fn bal_stream(&self) -> BalNotificationStream; } /// The limit to enforce for [`BalStore::get_by_hashes_with_limit`]. @@ -105,13 +138,8 @@ impl BalStoreHandle { /// Insert the BAL for the given block. #[inline] - pub fn insert( - &self, - block_hash: BlockHash, - block_number: BlockNumber, - bal: Bytes, - ) -> ProviderResult<()> { - self.inner.insert(block_hash, block_number, bal) + pub fn insert(&self, num_hash: NumHash, bal: SealedBal) -> ProviderResult<()> { + self.inner.insert(num_hash, bal) } /// Fetch BALs for the given block hashes. @@ -147,6 +175,13 @@ impl BalStoreHandle { pub fn get_by_range(&self, start: BlockNumber, count: u64) -> ProviderResult> { self.inner.get_by_range(start, count) } + + /// Returns a stream of BAL insert notifications. + #[cfg(feature = "std")] + #[inline] + pub fn bal_stream(&self) -> BalNotificationStream { + self.inner.bal_stream() + } } impl Default for BalStoreHandle { @@ -173,12 +208,7 @@ pub trait BalProvider { pub struct NoopBalStore; impl BalStore for NoopBalStore { - fn insert( - &self, - _block_hash: BlockHash, - _block_number: BlockNumber, - _bal: Bytes, - ) -> ProviderResult<()> { + fn insert(&self, _num_hash: NumHash, _bal: SealedBal) -> ProviderResult<()> { Ok(()) } @@ -208,12 +238,19 @@ impl BalStore for NoopBalStore { fn get_by_range(&self, _start: BlockNumber, _count: u64) -> ProviderResult> { Ok(Vec::new()) } + + #[cfg(feature = "std")] + fn bal_stream(&self) -> BalNotificationStream { + reth_tokio_util::EventSender::new(1).new_listener() + } } #[cfg(test)] mod tests { use super::*; use alloy_primitives::B256; + #[cfg(feature = "std")] + use tokio_stream::StreamExt; #[test] fn noop_store_returns_empty_results() { @@ -249,4 +286,13 @@ mod tests { assert!(!size_limit_2mb.exceeds(2 * 1024 * 1024)); assert!(size_limit_2mb.exceeds(3 * 1024 * 1024)); } + + #[cfg(feature = "std")] + #[tokio::test] + async fn noop_store_stream_is_empty() { + let store = BalStoreHandle::default(); + let mut stream = store.bal_stream(); + + assert!(stream.next().await.is_none()); + } } diff --git a/crates/trie/sparse/src/parallel.rs b/crates/trie/sparse/src/parallel.rs index 6e17e1d01f2..71f5c88e215 100644 --- a/crates/trie/sparse/src/parallel.rs +++ b/crates/trie/sparse/src/parallel.rs @@ -956,7 +956,7 @@ impl ParallelSparseTrie { #[cfg(not(feature = "std"))] { let _ = num_nodes; - return false; + false } #[cfg(feature = "std")] @@ -971,7 +971,7 @@ impl ParallelSparseTrie { #[cfg(not(feature = "std"))] { let _ = num_changed_keys; - return false; + false } #[cfg(feature = "std")] From 0b55b2c5db1f3058937f989751c289d3ff0987ed Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 4 May 2026 13:22:28 +0200 Subject: [PATCH 021/335] refactor(payload): require built payload conversion (#23928) --- crates/e2e-test-utils/src/node.rs | 6 +----- crates/engine/local/src/miner.rs | 3 +-- crates/ethereum/engine-primitives/src/lib.rs | 16 +++++++++------- crates/node/builder/src/components/payload.rs | 5 ++++- crates/payload/primitives/src/lib.rs | 2 +- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/crates/e2e-test-utils/src/node.rs b/crates/e2e-test-utils/src/node.rs index d83443c7db0..df4dbe95c9c 100644 --- a/crates/e2e-test-utils/src/node.rs +++ b/crates/e2e-test-utils/src/node.rs @@ -309,11 +309,7 @@ where /// Submits a payload to the engine. pub async fn submit_payload(&self, payload: Payload::BuiltPayload) -> eyre::Result { let block_hash = payload.block().hash(); - self.inner - .add_ons_handle - .beacon_engine_handle - .new_payload(Payload::block_to_payload(payload.block().clone())) - .await?; + self.inner.add_ons_handle.beacon_engine_handle.new_payload(payload.into()).await?; Ok(block_hash) } diff --git a/crates/engine/local/src/miner.rs b/crates/engine/local/src/miner.rs index 882c4b0828b..ff131b35152 100644 --- a/crates/engine/local/src/miner.rs +++ b/crates/engine/local/src/miner.rs @@ -261,8 +261,7 @@ where }; let header = payload.block().sealed_header().clone(); - let payload = T::block_to_payload(payload.block().clone()); - let res = self.to_engine.new_payload(payload).await?; + let res = self.to_engine.new_payload(payload.into()).await?; if !res.is_valid() { eyre::bail!("Invalid payload") diff --git a/crates/ethereum/engine-primitives/src/lib.rs b/crates/ethereum/engine-primitives/src/lib.rs index 962f6241f4e..e08dbc22b42 100644 --- a/crates/ethereum/engine-primitives/src/lib.rs +++ b/crates/ethereum/engine-primitives/src/lib.rs @@ -34,14 +34,15 @@ pub struct EthEngineTypes { _marker: core::marker::PhantomData, } -impl< - T: PayloadTypes< - ExecutionData = ExecutionData, - BuiltPayload: BuiltPayload< - Primitives: NodePrimitives, - >, +impl PayloadTypes for EthEngineTypes +where + T: PayloadTypes< + ExecutionData = ExecutionData, + BuiltPayload: BuiltPayload< + Primitives: NodePrimitives, >, - > PayloadTypes for EthEngineTypes + >, + ExecutionData: From, { type ExecutionData = T::ExecutionData; type BuiltPayload = T::BuiltPayload; @@ -59,6 +60,7 @@ impl< impl EngineTypes for EthEngineTypes where T: PayloadTypes, + ExecutionData: From, T::BuiltPayload: BuiltPayload> + TryInto + TryInto diff --git a/crates/node/builder/src/components/payload.rs b/crates/node/builder/src/components/payload.rs index 5afe08ee5e9..f245aff5c60 100644 --- a/crates/node/builder/src/components/payload.rs +++ b/crates/node/builder/src/components/payload.rs @@ -105,7 +105,10 @@ where payload_builder, ); let (payload_service, payload_service_handle) = - PayloadBuilderService::new(payload_generator, ctx.provider().canonical_state_stream()); + PayloadBuilderService::<_, _, ::Payload>::new( + payload_generator, + ctx.provider().canonical_state_stream(), + ); ctx.task_executor().spawn_critical_task("payload builder service", payload_service); diff --git a/crates/payload/primitives/src/lib.rs b/crates/payload/primitives/src/lib.rs index 4123f4ea168..f2db62acfd1 100644 --- a/crates/payload/primitives/src/lib.rs +++ b/crates/payload/primitives/src/lib.rs @@ -38,7 +38,7 @@ pub trait PayloadTypes: Send + Sync + Unpin + core::fmt::Debug + Clone + 'static /// /// This type represents the canonical format for block data that includes /// all necessary information for execution and validation. - type ExecutionData: ExecutionPayload; + type ExecutionData: ExecutionPayload + From; /// The type representing a successfully built payload/block. type BuiltPayload: BuiltPayload + Clone + Unpin; From d50fed35cb8f9ecb967d72c6c636bcfe26180d27 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Mon, 4 May 2026 13:29:31 +0200 Subject: [PATCH 022/335] perf(engine): acknowledge save_blocks before prune (#23904) Co-authored-by: Amp Co-authored-by: Amp --- crates/engine/tree/src/persistence.rs | 32 ++++++++++++++------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/crates/engine/tree/src/persistence.rs b/crates/engine/tree/src/persistence.rs index da778e1a30f..e526a41c7ee 100644 --- a/crates/engine/tree/src/persistence.rs +++ b/crates/engine/tree/src/persistence.rs @@ -113,6 +113,7 @@ where let _ = self .sync_metrics_tx .send(MetricEvent::SyncHeight { height: block_number }); + self.maybe_run_pruner(block_number)?; } } PersistenceAction::SaveFinalizedBlock(finalized_block) => { @@ -179,21 +180,6 @@ where provider_rw.commit()?; debug!(target: "engine::persistence", first=?first_block, last=?last_block, "Saved range of blocks"); - - // Run the pruner in a separate provider so it reads committed RocksDB state - // that includes the history entries written by save_blocks above. - // - // The pruner reads the indices from rocksdb, filters it, and writes to indices, so it - // must be able to read anything written by save_blocks. - if self.pruner.is_pruning_needed(last.number) { - debug!(target: "engine::persistence", block_num=?last.number, "Running pruner"); - let prune_start = Instant::now(); - let provider_rw = self.provider.database_provider_rw()?; - let _ = self.pruner.run_with_provider(&provider_rw, last.number)?; - provider_rw.commit()?; - debug!(target: "engine::persistence", tip=?last.number, "Finished pruning after saving blocks"); - self.metrics.prune_before_duration_seconds.record(prune_start.elapsed()); - } } let elapsed = start_time.elapsed(); @@ -202,6 +188,22 @@ where Ok(PersistenceResult { last_block, commit_duration: Some(elapsed) }) } + + fn maybe_run_pruner(&mut self, block_number: u64) -> Result<(), PersistenceError> { + // The durable save is already committed at this point, so pruning can happen after we + // acknowledge the save without extending the synchronous persistence wait. + if self.pruner.is_pruning_needed(block_number) { + debug!(target: "engine::persistence", block_num=?block_number, "Running pruner"); + let prune_start = Instant::now(); + let provider_rw = self.provider.database_provider_rw()?; + let _ = self.pruner.run_with_provider(&provider_rw, block_number)?; + provider_rw.commit()?; + debug!(target: "engine::persistence", tip=?block_number, "Finished pruning after saving blocks"); + self.metrics.prune_before_duration_seconds.record(prune_start.elapsed()); + } + + Ok(()) + } } /// One of the errors that can happen when using the persistence service. From e2ad1e44c2ca3470f3b5612d70aecb7e4ddc8e96 Mon Sep 17 00:00:00 2001 From: Soubhik Singha Mahapatra <160333583+Soubhik-10@users.noreply.github.com> Date: Mon, 4 May 2026 17:08:30 +0530 Subject: [PATCH 023/335] feat: add bal validation in post execution (#23496) Co-authored-by: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> --- crates/cli/commands/src/re_execute.rs | 2 +- crates/consensus/consensus/Cargo.toml | 5 +---- crates/consensus/consensus/src/lib.rs | 4 ++++ crates/consensus/consensus/src/noop.rs | 2 ++ crates/consensus/consensus/src/test_utils.rs | 2 ++ crates/engine/tree/src/tree/payload_validator.rs | 2 +- crates/ethereum/consensus/src/lib.rs | 11 +++++++++-- crates/ethereum/consensus/src/validation.rs | 15 +++++++++++++++ crates/rpc/rpc/src/validation.rs | 2 +- crates/stages/stages/src/stages/execution/mod.rs | 4 +++- testing/ef-tests/src/cases/blockchain_test.rs | 2 +- 11 files changed, 40 insertions(+), 11 deletions(-) diff --git a/crates/cli/commands/src/re_execute.rs b/crates/cli/commands/src/re_execute.rs index 5923fde9f3c..04e2f6dfc25 100644 --- a/crates/cli/commands/src/re_execute.rs +++ b/crates/cli/commands/src/re_execute.rs @@ -195,7 +195,7 @@ impl }; if let Err(err) = consensus - .validate_block_post_execution(&block, &result, None) + .validate_block_post_execution(&block, &result, None,None) .wrap_err_with(|| { format!( "Failed to validate block {} {}", diff --git a/crates/consensus/consensus/Cargo.toml b/crates/consensus/consensus/Cargo.toml index 4d2c0ac0768..b5c394ac014 100644 --- a/crates/consensus/consensus/Cargo.toml +++ b/crates/consensus/consensus/Cargo.toml @@ -29,10 +29,7 @@ std = [ "reth-primitives-traits/std", "alloy-primitives/std", "alloy-consensus/std", - "reth-primitives-traits/std", "reth-execution-types/std", "thiserror/std", ] -test-utils = [ - "reth-primitives-traits/test-utils", -] +test-utils = ["reth-primitives-traits/test-utils"] diff --git a/crates/consensus/consensus/src/lib.rs b/crates/consensus/consensus/src/lib.rs index 6040d5ca79d..6e9ed1950dc 100644 --- a/crates/consensus/consensus/src/lib.rs +++ b/crates/consensus/consensus/src/lib.rs @@ -85,6 +85,7 @@ pub trait FullConsensus: Consensus { block: &RecoveredBlock, result: &BlockExecutionResult, receipt_root_bloom: Option, + block_access_list_hash: Option, ) -> Result<(), ConsensusError>; } @@ -474,6 +475,9 @@ pub enum ConsensusError { /// EIP-7825: Transaction gas limit exceeds maximum allowed #[error(transparent)] TransactionGasLimitTooHigh(Box), + /// Error when the block access list hash doesn't match the expected value. + #[error("block access list hash mismatch: {0}")] + BlockAccessListHashMismatch(GotExpectedBoxed), /// Any additional consensus error, for example L2-specific errors. #[error(transparent)] Other(#[from] Arc), diff --git a/crates/consensus/consensus/src/noop.rs b/crates/consensus/consensus/src/noop.rs index 08fe08e96e3..8f7520cf374 100644 --- a/crates/consensus/consensus/src/noop.rs +++ b/crates/consensus/consensus/src/noop.rs @@ -20,6 +20,7 @@ use crate::{Consensus, ConsensusError, FullConsensus, HeaderValidator, ReceiptRootBloom}; use alloc::sync::Arc; +use alloy_primitives::B256; use reth_execution_types::BlockExecutionResult; use reth_primitives_traits::{Block, NodePrimitives, RecoveredBlock, SealedBlock, SealedHeader}; @@ -77,6 +78,7 @@ impl FullConsensus for NoopConsensus { _block: &RecoveredBlock, _result: &BlockExecutionResult, _receipt_root_bloom: Option, + _block_access_list_hash: Option, ) -> Result<(), ConsensusError> { Ok(()) } diff --git a/crates/consensus/consensus/src/test_utils.rs b/crates/consensus/consensus/src/test_utils.rs index b2a1fc71f0e..fd7644c479f 100644 --- a/crates/consensus/consensus/src/test_utils.rs +++ b/crates/consensus/consensus/src/test_utils.rs @@ -1,4 +1,5 @@ use crate::{Consensus, ConsensusError, FullConsensus, HeaderValidator, ReceiptRootBloom}; +use alloy_primitives::B256; use core::sync::atomic::{AtomicBool, Ordering}; use reth_execution_types::BlockExecutionResult; use reth_primitives_traits::{Block, NodePrimitives, RecoveredBlock, SealedBlock, SealedHeader}; @@ -52,6 +53,7 @@ impl FullConsensus for TestConsensus { _block: &RecoveredBlock, _result: &BlockExecutionResult, _receipt_root_bloom: Option, + _block_access_list_hash: Option, ) -> Result<(), ConsensusError> { if self.fail_validation() { Err(ConsensusError::BaseFeeMissing) diff --git a/crates/engine/tree/src/tree/payload_validator.rs b/crates/engine/tree/src/tree/payload_validator.rs index d661bf02784..c7f603cfc20 100644 --- a/crates/engine/tree/src/tree/payload_validator.rs +++ b/crates/engine/tree/src/tree/payload_validator.rs @@ -1389,7 +1389,7 @@ where debug_span!(target: "engine::tree::payload_validator", "validate_block_post_execution") .entered(); if let Err(err) = - self.consensus.validate_block_post_execution(block, output, receipt_root_bloom) + self.consensus.validate_block_post_execution(block, output, receipt_root_bloom, None) { // call post-block hook self.on_invalid_block(parent_block, block, output, None, ctx.state_mut()); diff --git a/crates/ethereum/consensus/src/lib.rs b/crates/ethereum/consensus/src/lib.rs index f1113898c0d..316b2e43589 100644 --- a/crates/ethereum/consensus/src/lib.rs +++ b/crates/ethereum/consensus/src/lib.rs @@ -14,6 +14,7 @@ extern crate alloc; use alloc::{fmt::Debug, sync::Arc}; use alloy_consensus::{constants::MAXIMUM_EXTRA_DATA_SIZE, EMPTY_OMMER_ROOT_HASH}; use alloy_eips::eip7840::BlobParams; +use alloy_primitives::B256; use reth_chainspec::{EthChainSpec, EthereumHardforks}; use reth_consensus::{ Consensus, ConsensusError, FullConsensus, HeaderValidator, ReceiptRootBloom, TransactionRoot, @@ -108,9 +109,15 @@ where block: &RecoveredBlock, result: &BlockExecutionResult, receipt_root_bloom: Option, + block_access_list_hash: Option, ) -> Result<(), ConsensusError> { - let res = - validate_block_post_execution(block, &self.chain_spec, result, receipt_root_bloom); + let res = validate_block_post_execution( + block, + &self.chain_spec, + result, + receipt_root_bloom, + block_access_list_hash, + ); if self.skip_requests_hash_check && let Err(ConsensusError::BodyRequestsHashDiff(_)) = &res diff --git a/crates/ethereum/consensus/src/validation.rs b/crates/ethereum/consensus/src/validation.rs index 56ba60c5860..2d44c1cf660 100644 --- a/crates/ethereum/consensus/src/validation.rs +++ b/crates/ethereum/consensus/src/validation.rs @@ -13,6 +13,7 @@ use reth_primitives_traits::{ /// /// - Compares the receipts root in the block header to the block body /// - Compares the gas used in the block header to the actual gas usage after execution +/// - Compares the computed Block Access List Hash to the value in the header if Amsterdam is active /// /// If `receipt_root_bloom` is provided, the pre-computed receipt root and logs bloom are used /// instead of computing them from the receipts. @@ -21,6 +22,7 @@ pub fn validate_block_post_execution( chain_spec: &ChainSpec, result: &BlockExecutionResult, receipt_root_bloom: Option<(B256, Bloom)>, + block_access_list_hash: Option, ) -> Result<(), ConsensusError> where B: Block, @@ -79,6 +81,19 @@ where } } + // Validate that the header block access list hash matches the calculated block access list hash + if chain_spec.is_amsterdam_active_at_timestamp(block.header().timestamp()) && + let Some(block_access_list_hash) = block_access_list_hash + { + let block_bal_hash = block.header().block_access_list_hash().unwrap_or_default(); + + if block_access_list_hash != block_bal_hash { + return Err(ConsensusError::BlockAccessListHashMismatch( + GotExpected::new(block_access_list_hash, block_bal_hash).into(), + )) + } + } + Ok(()) } diff --git a/crates/rpc/rpc/src/validation.rs b/crates/rpc/rpc/src/validation.rs index e1b3171d628..b5ab60a1c7f 100644 --- a/crates/rpc/rpc/src/validation.rs +++ b/crates/rpc/rpc/src/validation.rs @@ -202,7 +202,7 @@ where // update the cached reads self.update_cached_reads(parent_header_hash, request_cache).await; - self.consensus.validate_block_post_execution(&block, &output, None)?; + self.consensus.validate_block_post_execution(&block, &output, None, None)?; self.ensure_payment(&block, &output, &message)?; diff --git a/crates/stages/stages/src/stages/execution/mod.rs b/crates/stages/stages/src/stages/execution/mod.rs index bfd3b87104a..a2154fe54a7 100644 --- a/crates/stages/stages/src/stages/execution/mod.rs +++ b/crates/stages/stages/src/stages/execution/mod.rs @@ -357,7 +357,9 @@ where }) })?; - if let Err(err) = self.consensus.validate_block_post_execution(&block, &result, None) { + if let Err(err) = + self.consensus.validate_block_post_execution(&block, &result, None, None) + { return Err(StageError::Block { block: Box::new(block.block_with_parent()), error: BlockErrorKind::Validation(err), diff --git a/testing/ef-tests/src/cases/blockchain_test.rs b/testing/ef-tests/src/cases/blockchain_test.rs index 338e70cf60b..5ec5b9cdbc2 100644 --- a/testing/ef-tests/src/cases/blockchain_test.rs +++ b/testing/ef-tests/src/cases/blockchain_test.rs @@ -252,7 +252,7 @@ fn run_case(case: &BlockchainTest) -> Result<(), Error> { .map_err(|err| Error::block_failed(block_number, err))?; // Consensus checks after block execution - validate_block_post_execution(block, &chain_spec, &output, None) + validate_block_post_execution(block, &chain_spec, &output, None, None) .map_err(|err| Error::block_failed(block_number, err))?; // Compute and check the post state root From b45e99b8e7b6a890d8731a47cfde0f763f6084d3 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Mon, 4 May 2026 17:03:03 +0400 Subject: [PATCH 024/335] refactor(generate-big-blocks): don't recompute receipts root (#23930) --- .../src/bench/generate_big_block.rs | 44 ++----------------- 1 file changed, 4 insertions(+), 40 deletions(-) diff --git a/bin/reth-bench/src/bench/generate_big_block.rs b/bin/reth-bench/src/bench/generate_big_block.rs index 6b1bbad54e6..77774363558 100644 --- a/bin/reth-bench/src/bench/generate_big_block.rs +++ b/bin/reth-bench/src/bench/generate_big_block.rs @@ -5,14 +5,14 @@ //! and saves the result to disk as a [`BigBlockPayload`] JSON file containing the merged //! [`ExecutionData`] and environment switches at each block boundary. -use alloy_consensus::{TxEnvelope, TxReceipt}; +use alloy_consensus::TxEnvelope; use alloy_eips::{ eip1559::BaseFeeParams, eip7840::BlobParams, eip7928::{AccountChanges, BlockAccessList, SlotChanges}, Typed2718, }; -use alloy_primitives::{Bloom, Bytes, B256}; +use alloy_primitives::{Bytes, B256}; use alloy_provider::{network::AnyNetwork, Provider, RootProvider}; use alloy_rpc_client::ClientBuilder; use alloy_rpc_types_engine::{ @@ -28,7 +28,6 @@ use reth_cli_runner::CliContext; use reth_engine_primitives::BigBlockData; use reth_ethereum_cli::chainspec::EthereumChainSpecParser; use reth_ethereum_primitives::Receipt; -use reth_primitives_traits::proofs; use serde::{Deserialize, Serialize}; use std::{ collections::{HashMap, HashSet}, @@ -354,7 +353,6 @@ impl Command { // Drain the prefetch stream until the gas target is reached for this big block. let mut blocks = Vec::new(); - let mut block_receipts: Vec> = Vec::new(); let mut block_access_lists: Vec> = Vec::new(); let mut accumulated_block_gas: u64 = 0; @@ -401,7 +399,6 @@ impl Command { accumulated_block_gas += block_gas; blocks.push(execution_data); - block_receipts.push(consensus_receipts); block_access_lists.push(block_access_list); next_block += 1; } @@ -419,25 +416,9 @@ impl Command { // Block 0 is the base let mut base = blocks.remove(0); - let base_receipts = block_receipts.remove(0); let mut merged_block_access_list = block_access_lists.remove(0); let mut env_switches = Vec::new(); - // Accumulate all receipts with corrected cumulative_gas_used. - // Each block's receipts have cumulative gas relative to that block; - // we add the prior blocks' total gas to make them globally correct. - let mut all_receipts: Vec = Vec::new(); - let mut cumulative_gas_offset: u64 = 0; - { - // Base block receipts (block 0) — no offset needed - let base_block_gas = base.payload.as_v1().gas_used; - all_receipts.extend(base_receipts.into_iter().map(|mut r| { - r.cumulative_gas_used += cumulative_gas_offset; - r - })); - cumulative_gas_offset += base_block_gas; - } - if !blocks.is_empty() { // Store the original unmutated base block as env_switch at index 0. // This preserves the real gas_limit, basefee, etc. for segment 0's @@ -456,8 +437,8 @@ impl Command { let mut total_gas_limit = base.payload.as_v1().gas_limit; // Concatenate transactions from subsequent blocks and build env_switches - for (block_idx, ((block_data, receipts), block_access_list)) in - blocks.into_iter().zip(block_receipts).zip(block_access_lists).enumerate() + for (block_idx, (block_data, block_access_list)) in + blocks.into_iter().zip(block_access_lists).enumerate() { // Segment index in the merged big block. The base block is // segment 0; subsequent blocks are segments 1, 2, ... @@ -476,13 +457,6 @@ impl Command { ); } - // Accumulate receipts with corrected cumulative_gas_used - all_receipts.extend(receipts.into_iter().map(|mut r| { - r.cumulative_gas_used += cumulative_gas_offset; - r - })); - cumulative_gas_offset += block_gas; - // Record environment switch at this block boundary env_switches.push((cumulative_tx_count, block_data.clone())); @@ -492,21 +466,11 @@ impl Command { base.payload.transactions_mut().extend(txs); } - // Compute merged receipts_root and logs_bloom from all accumulated - // receipts (with globally-correct cumulative_gas_used). - let receipts_with_bloom: Vec<_> = - all_receipts.iter().map(|r| r.with_bloom_ref()).collect(); - let merged_receipts_root = proofs::calculate_receipt_root(&receipts_with_bloom); - let merged_logs_bloom = - receipts_with_bloom.iter().fold(Bloom::ZERO, |bloom, r| bloom | *r.bloom_ref()); - // Mutate the base payload header let base_v1 = base.payload.as_v1_mut(); base_v1.state_root = final_state_root; base_v1.gas_used = total_gas_used; base_v1.gas_limit = total_gas_limit; - base_v1.receipts_root = merged_receipts_root; - base_v1.logs_bloom = merged_logs_bloom; } // Chain sequential big blocks: set parent_hash, block_number, basefee, From d5c5b0b63308eb6ff3e3391a79ef4dbcfc4f8b52 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Mon, 4 May 2026 18:36:14 +0400 Subject: [PATCH 025/335] feat: spawn deferred trie work for directly inserted payloads (#23935) --- Cargo.lock | 1 - crates/engine/tree/src/engine.rs | 9 ++-- crates/engine/tree/src/tree/mod.rs | 16 ++++-- .../engine/tree/src/tree/payload_validator.rs | 50 +++++++++++++------ crates/node/builder/src/launch/engine.rs | 2 +- crates/payload/builder-primitives/Cargo.toml | 2 +- crates/payload/primitives/Cargo.toml | 1 - crates/payload/primitives/src/traits.rs | 48 ++---------------- 8 files changed, 60 insertions(+), 69 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cfb2bc8b0d2..0da98b255a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9488,7 +9488,6 @@ dependencies = [ "assert_matches", "auto_impl", "either", - "reth-chain-state", "reth-chainspec", "reth-errors", "reth-execution-types", diff --git a/crates/engine/tree/src/engine.rs b/crates/engine/tree/src/engine.rs index 154b3b01e4f..c15acfc227d 100644 --- a/crates/engine/tree/src/engine.rs +++ b/crates/engine/tree/src/engine.rs @@ -8,10 +8,9 @@ use crate::{ use alloy_primitives::{map::B256Set, B256}; use crossbeam_channel::Sender; use futures::{Stream, StreamExt}; -use reth_chain_state::ExecutedBlock; use reth_engine_primitives::{BeaconEngineMessage, ConsensusEngineEvent}; use reth_ethereum_primitives::EthPrimitives; -use reth_payload_primitives::PayloadTypes; +use reth_payload_primitives::{BuiltPayloadExecutedBlock, PayloadTypes}; use reth_primitives_traits::{Block, NodePrimitives, SealedBlock}; use std::{ fmt::Display, @@ -245,15 +244,15 @@ pub enum EngineApiRequest { /// A request received from the consensus engine. Beacon(BeaconEngineMessage), /// Request to insert an already executed block, e.g. via payload building. - InsertExecutedBlock(ExecutedBlock), + InsertExecutedBlock(BuiltPayloadExecutedBlock), } impl Display for EngineApiRequest { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Beacon(msg) => msg.fmt(f), - Self::InsertExecutedBlock(block) => { - write!(f, "InsertExecutedBlock({:?})", block.recovered_block().num_hash()) + Self::InsertExecutedBlock(payload) => { + write!(f, "InsertExecutedBlock({:?})", payload.recovered_block.num_hash()) } } } diff --git a/crates/engine/tree/src/tree/mod.rs b/crates/engine/tree/src/tree/mod.rs index 1edf1496e23..41bd429373d 100644 --- a/crates/engine/tree/src/tree/mod.rs +++ b/crates/engine/tree/src/tree/mod.rs @@ -1548,8 +1548,8 @@ where }, FromEngine::Request(request) => { match request { - EngineApiRequest::InsertExecutedBlock(block) => { - let block_num_hash = block.recovered_block().num_hash(); + EngineApiRequest::InsertExecutedBlock(payload) => { + let block_num_hash = payload.recovered_block.num_hash(); if block_num_hash.number <= self.state.tree_state.canonical_block_number() { // outdated block that can be skipped return Ok(ops::ControlFlow::Continue(())) @@ -1558,6 +1558,17 @@ where debug!(target: "engine::tree", block=?block_num_hash, "inserting already executed block"); let now = Instant::now(); + let block = match self + .payload_validator + .on_inserted_executed_block(payload, &self.state) + { + Ok(block) => block, + Err(err) => { + warn!(target: "engine::tree", %err, block=?block_num_hash, "Failed to insert already executed block"); + return Ok(ops::ControlFlow::Continue(())) + } + }; + // if the parent is the canonical head, we can insert the block as the // pending block if self.state.tree_state.canonical_block_hash() == @@ -1568,7 +1579,6 @@ where } self.state.tree_state.insert_executed(block.clone()); - self.payload_validator.on_inserted_executed_block(block.clone()); self.metrics.engine.inserted_already_executed_blocks.increment(1); self.emit_event(EngineApiEvent::BeaconConsensus( ConsensusEngineEvent::CanonicalBlockAdded(block, now.elapsed()), diff --git a/crates/engine/tree/src/tree/payload_validator.rs b/crates/engine/tree/src/tree/payload_validator.rs index c7f603cfc20..43e1d5a5703 100644 --- a/crates/engine/tree/src/tree/payload_validator.rs +++ b/crates/engine/tree/src/tree/payload_validator.rs @@ -73,7 +73,8 @@ use reth_evm::{ }; use reth_execution_cache::{CacheStats, SavedCache}; use reth_payload_primitives::{ - BuiltPayload, InvalidPayloadAttributesError, NewPayloadError, PayloadTypes, + BuiltPayload, BuiltPayloadExecutedBlock, InvalidPayloadAttributesError, NewPayloadError, + PayloadTypes, }; use reth_primitives_traits::{ AlloyBlockHeader, BlockBody, BlockTy, FastInstant as Instant, GotExpected, NodePrimitives, @@ -837,9 +838,9 @@ where ensure_ok_post_block!(overlay_factory.database_provider_ro(), block); let executed_block = self.spawn_deferred_trie_task( - block, + Arc::new(block), output, - &ctx, + ctx.state(), hashed_state, trie_output, changeset_provider, @@ -1616,16 +1617,15 @@ where /// from the completed task or via fallback computation. fn spawn_deferred_trie_task( &self, - block: RecoveredBlock, + block: Arc>, execution_outcome: Arc>, - ctx: &TreeCtx<'_, N>, + state: &EngineApiTreeState, hashed_state: LazyHashedPostState, trie_output: Arc, changeset_provider: impl TrieCursorFactory + Send + 'static, ) -> ExecutedBlock { // Capture parent hash and ancestor overlays for deferred trie input construction. - let (anchor_hash, overlay_blocks) = ctx - .state() + let (anchor_hash, overlay_blocks) = state .tree_state .blocks_by_hash(block.parent_hash()) .unwrap_or_else(|| (block.parent_hash(), Vec::new())); @@ -1732,11 +1732,7 @@ where .executor() .spawn_blocking_named("trie-input", compute_trie_input_task); - ExecutedBlock::with_deferred_trie_data( - Arc::new(block), - execution_outcome, - deferred_trie_data, - ) + ExecutedBlock::with_deferred_trie_data(block, execution_outcome, deferred_trie_data) } fn calculate_timing_stats( @@ -1940,7 +1936,11 @@ pub trait EngineValidator< /// /// This is invoked when blocks are inserted via `InsertExecutedBlock` (e.g., locally built /// blocks by sequencers) to allow implementations to update internal state such as caches. - fn on_inserted_executed_block(&self, block: ExecutedBlock); + fn on_inserted_executed_block( + &self, + block: BuiltPayloadExecutedBlock, + state: &EngineApiTreeState, + ) -> ProviderResult>; /// Returns [`SavedCache`] for the given block hash. fn cache_for(&self, _block_hash: B256) -> Option; @@ -2009,11 +2009,33 @@ where self.validate_block_with_state(BlockOrPayload::Block(block), ctx) } - fn on_inserted_executed_block(&self, block: ExecutedBlock) { + fn on_inserted_executed_block( + &self, + block: BuiltPayloadExecutedBlock, + state: &EngineApiTreeState, + ) -> ProviderResult> { self.payload_processor.on_inserted_executed_block( block.recovered_block.block_with_parent(), &block.execution_output.state, ); + + let (lazy_overlay, anchor_hash) = + Self::get_parent_lazy_overlay(block.recovered_block.parent_hash(), state); + let overlay_factory = OverlayStateProviderFactory::new( + self.provider.clone(), + OverlayBuilder::::new(anchor_hash, self.changeset_cache.clone()) + .with_lazy_overlay(lazy_overlay), + ); + let changeset_provider = overlay_factory.database_provider_ro()?; + + Ok(self.spawn_deferred_trie_task( + block.recovered_block, + block.execution_output, + state, + LazyHashedPostState::ready(Arc::unwrap_or_clone(block.hashed_state)), + block.trie_updates, + changeset_provider, + )) } fn cache_for(&self, block_hash: B256) -> Option { diff --git a/crates/node/builder/src/launch/engine.rs b/crates/node/builder/src/launch/engine.rs index 0b568564930..7e39de7f538 100644 --- a/crates/node/builder/src/launch/engine.rs +++ b/crates/node/builder/src/launch/engine.rs @@ -363,7 +363,7 @@ impl EngineNodeLauncher { payload = built_payloads.select_next_some(), if !built_payloads.is_terminated() => { if let Some(executed_block) = payload.executed_block() { debug!(target: "reth::cli", block=?executed_block.recovered_block.num_hash(), "inserting built payload"); - orchestrator.handler_mut().handler_mut().on_event(EngineApiRequest::InsertExecutedBlock(executed_block.into_executed_payload()).into()); + orchestrator.handler_mut().handler_mut().on_event(EngineApiRequest::InsertExecutedBlock(executed_block).into()); } } shutdown_req = &mut shutdown_rx => { diff --git a/crates/payload/builder-primitives/Cargo.toml b/crates/payload/builder-primitives/Cargo.toml index 70ff2d58833..4d16ce9c002 100644 --- a/crates/payload/builder-primitives/Cargo.toml +++ b/crates/payload/builder-primitives/Cargo.toml @@ -18,7 +18,7 @@ reth-payload-primitives.workspace = true # async pin-project.workspace = true tokio = { workspace = true, features = ["sync"] } -tokio-stream.workspace = true +tokio-stream = { workspace = true, features = ["sync"] } # misc tracing.workspace = true diff --git a/crates/payload/primitives/Cargo.toml b/crates/payload/primitives/Cargo.toml index 4cacba280cc..e47b5afad98 100644 --- a/crates/payload/primitives/Cargo.toml +++ b/crates/payload/primitives/Cargo.toml @@ -16,7 +16,6 @@ workspace = true reth-primitives-traits.workspace = true reth-chainspec.workspace = true reth-errors.workspace = true -reth-chain-state.workspace = true reth-execution-types.workspace = true reth-trie-common.workspace = true diff --git a/crates/payload/primitives/src/traits.rs b/crates/payload/primitives/src/traits.rs index 3bc340922ed..4345cefea99 100644 --- a/crates/payload/primitives/src/traits.rs +++ b/crates/payload/primitives/src/traits.rs @@ -8,13 +8,9 @@ use alloy_rlp::Encodable; use alloy_rpc_types_engine::{PayloadAttributes as EthPayloadAttributes, PayloadId}; use core::fmt; use either::Either; -use reth_chain_state::ComputedTrieData; use reth_execution_types::BlockExecutionOutput; use reth_primitives_traits::{NodePrimitives, RecoveredBlock, SealedBlock, SealedHeader}; -use reth_trie_common::{ - updates::{TrieUpdates, TrieUpdatesSorted}, - HashedPostState, HashedPostStateSorted, -}; +use reth_trie_common::{updates::TrieUpdates, HashedPostState}; /// Represents an executed block for payload building purposes. /// @@ -26,44 +22,10 @@ pub struct BuiltPayloadExecutedBlock { pub recovered_block: Arc>, /// Block's execution outcome. pub execution_output: Arc>, - /// Block's hashed state. - /// - /// Supports both unsorted and sorted variants so payload builders can avoid cloning in order - /// to convert from one to the other when it's not necessary. - pub hashed_state: Either, Arc>, - /// Trie updates that result from calculating the state root for the block. - /// - /// Supports both unsorted and sorted variants so payload builders can avoid cloning in order - /// to convert from one to the other when it's not necessary. - pub trie_updates: Either, Arc>, -} - -impl BuiltPayloadExecutedBlock { - /// Converts this into an [`reth_chain_state::ExecutedBlock`]. - /// - /// Ensures hashed state and trie updates are in their sorted representations - /// as required by `reth_chain_state::ExecutedBlock`. - pub fn into_executed_payload(self) -> reth_chain_state::ExecutedBlock { - let hashed_state = match self.hashed_state { - // Convert unsorted to sorted - Either::Left(unsorted) => Arc::new(Arc::unwrap_or_clone(unsorted).into_sorted()), - // Already sorted - Either::Right(sorted) => sorted, - }; - - let trie_updates = match self.trie_updates { - // Convert unsorted to sorted - Either::Left(unsorted) => Arc::new(Arc::unwrap_or_clone(unsorted).into_sorted()), - // Already sorted - Either::Right(sorted) => sorted, - }; - - reth_chain_state::ExecutedBlock::new( - self.recovered_block, - self.execution_output, - ComputedTrieData::without_trie_input(hashed_state, trie_updates), - ) - } + /// Block's hashed state (unsorted). + pub hashed_state: Arc, + /// Trie updates that result from calculating the state root for the block (unsorted). + pub trie_updates: Arc, } /// Represents a successfully built execution payload (block). From a60c3ee3404cbb11be79f3fb250d354eccf7b489 Mon Sep 17 00:00:00 2001 From: sashass1315 Date: Mon, 4 May 2026 20:38:04 +0300 Subject: [PATCH 026/335] fix(net): validate EIP-1459 hash labels before caching entries (#22582) Co-authored-by: Matthias Seitz --- crates/net/dns/src/error.rs | 4 ++ crates/net/dns/src/lib.rs | 64 ++++++++++++++++++++++++++---- crates/net/dns/src/query.rs | 77 ++++++++++++++++++++++++++++++++++++- 3 files changed, 137 insertions(+), 8 deletions(-) diff --git a/crates/net/dns/src/error.rs b/crates/net/dns/src/error.rs index 6246e1c4176..60aa3d3322b 100644 --- a/crates/net/dns/src/error.rs +++ b/crates/net/dns/src/error.rs @@ -54,6 +54,10 @@ pub(crate) enum LookupError { #[error("request timed out")] /// Indicates a timeout occurred during the request. RequestTimedOut, + /// Entry hash mismatch error. + #[error("entry content does not match hash {0}")] + /// Indicates that resolved TXT content does not match the requested hash label. + HashMismatch(String), /// Entry not found error. #[error("entry not found")] /// Indicates the requested entry was not found. diff --git a/crates/net/dns/src/lib.rs b/crates/net/dns/src/lib.rs index 0fd5d9590fc..31b147ed321 100644 --- a/crates/net/dns/src/lib.rs +++ b/crates/net/dns/src/lib.rs @@ -409,13 +409,19 @@ mod tests { use super::*; use crate::tree::TreeRootEntry; use alloy_chains::Chain; + use alloy_primitives::keccak256; use alloy_rlp::{Decodable, Encodable}; + use data_encoding::BASE32_NOPAD; use enr::EnrKey; use reth_chainspec::MAINNET; use reth_ethereum_forks::{EthereumHardfork, ForkHash}; use secp256k1::rand::thread_rng; use std::{future::poll_fn, net::Ipv4Addr}; + fn entry_hash(entry_txt: &str) -> String { + BASE32_NOPAD.encode(&keccak256(entry_txt.as_bytes()).as_slice()[..16]) + } + #[test] fn test_convert_enr_node_record() { // rig @@ -503,11 +509,9 @@ mod tests { let resolver = MapResolver::default(); let s = "enrtree-root:v1 e=QFT4PBCRX4XQCV3VUYJ6BTCEPU l=JGUFMSAGI7KZYB3P7IZW4S5Y3A seq=3 sig=3FmXuVwpa8Y7OstZTx9PIb1mt8FrW7VpDOFv4AaGCsZ2EIHmhraWhe4NxYhQDlw5MjeFXYMbJjsPeKlHzmJREQE"; let mut root: TreeRootEntry = s.parse().unwrap(); - root.sign(&secret_key).unwrap(); let link = LinkEntry { domain: "nodes.example.org".to_string(), pubkey: secret_key.public() }; - resolver.insert(link.domain.clone(), root.to_string()); let mut builder = Enr::builder(); let fork_id = MAINNET.hardfork_fork_id(EthereumHardfork::Frontier).unwrap(); @@ -517,8 +521,13 @@ mod tests { .tcp4(30303) .add_value(b"eth", &EnrForkIdEntry::from(fork_id)); let enr = builder.build(&secret_key).unwrap(); + let enr_txt = enr.to_base64(); - resolver.insert(format!("{}.{}", root.enr_root.clone(), link.domain), enr.to_base64()); + root.enr_root = entry_hash(&enr_txt); + root.sign(&secret_key).unwrap(); + + resolver.insert(link.domain.clone(), root.to_string()); + resolver.insert(format!("{}.{}", root.enr_root.clone(), link.domain), enr_txt); let mut service = DnsDiscoveryService::new(Arc::new(resolver), Default::default()); @@ -582,12 +591,13 @@ mod tests { let mut new_root = root.clone(); new_root.sequence_number = new_root.sequence_number.saturating_add(1); - new_root.enr_root = "NEW_ENR_ROOT".to_string(); - new_root.sign(&secret_key).unwrap(); - resolver.insert(link.domain.clone(), new_root.to_string()); let enr = Enr::empty(&secret_key).unwrap(); - resolver.insert(format!("{}.{}", new_root.enr_root.clone(), link.domain), enr.to_base64()); + let enr_txt = enr.to_base64(); + new_root.enr_root = entry_hash(&enr_txt); + new_root.sign(&secret_key).unwrap(); + resolver.insert(link.domain.clone(), new_root.to_string()); + resolver.insert(format!("{}.{}", new_root.enr_root.clone(), link.domain), enr_txt); let event = poll_fn(|cx| service.poll(cx)).await; @@ -604,6 +614,46 @@ mod tests { .await; } + #[tokio::test] + async fn test_hash_mismatch_is_not_cached_and_does_not_poison_same_hash() { + let secret_key = SecretKey::new(&mut thread_rng()); + let resolver = MapResolver::default(); + + let invalid_entry = "enrtree-branch:AAAAAAAAAAAAAAAAAAAA".to_string(); + let valid_entry = "enrtree-branch:YNEGZIWHOM7TOOSUATAPTM".to_string(); + + let hash = entry_hash(&valid_entry); + + let bad_link = + LinkEntry { domain: "bad.example.org".to_string(), pubkey: secret_key.public() }; + let good_link = + LinkEntry { domain: "good.example.org".to_string(), pubkey: secret_key.public() }; + + resolver.insert(format!("{}.{}", hash, bad_link.domain), invalid_entry); + resolver.insert(format!("{}.{}", hash, good_link.domain), valid_entry.clone()); + + let mut service = DnsDiscoveryService::new(Arc::new(resolver), Default::default()); + + service.resolve_entry(bad_link, hash.clone(), ResolveKind::Enr); + poll_fn(|cx| { + let _ = service.poll(cx); + Poll::Ready(()) + }) + .await; + + assert!(service.dns_record_cache.get(&hash).is_none()); + + service.resolve_entry(good_link, hash.clone(), ResolveKind::Enr); + poll_fn(|cx| { + let _ = service.poll(cx); + Poll::Ready(()) + }) + .await; + + let cached = service.dns_record_cache.get(&hash).cloned(); + assert_eq!(cached.map(|entry| entry.to_string()), Some(valid_entry)); + } + #[tokio::test] #[ignore] async fn test_dns_resolver() { diff --git a/crates/net/dns/src/query.rs b/crates/net/dns/src/query.rs index f64551f42f1..81f27a2fdc3 100644 --- a/crates/net/dns/src/query.rs +++ b/crates/net/dns/src/query.rs @@ -6,6 +6,8 @@ use crate::{ sync::ResolveKind, tree::{DnsEntry, LinkEntry, TreeRootEntry}, }; +use alloy_primitives::keccak256; +use data_encoding::BASE32_NOPAD; use enr::EnrKeyUnambiguous; use reth_tokio_util::ratelimit::{Rate, RateLimit}; use std::{ @@ -18,6 +20,11 @@ use std::{ time::Duration, }; +/// Minimum number of bytes an abbreviated EIP-1459 content hash may contain. +const MIN_HASH_BYTES: usize = 12; +/// Maximum number of bytes an abbreviated EIP-1459 content hash may contain. +const MAX_HASH_BYTES: usize = 32; + /// The `QueryPool` provides an aggregate state machine for driving queries to completion. pub(crate) struct QueryPool { /// The [Resolver] that's used to lookup queries. @@ -165,7 +172,10 @@ async fn resolve_entry( let mut resp = ResolveEntryResult { entry: None, link, hash, kind }; match lookup_with_timeout::(&resolver, &fqn, timeout).await { Ok(Some(entry)) => { - resp.entry = Some(entry.parse::>().map_err(|err| err.into())) + resp.entry = Some(match verify_entry_hash(&resp.hash, &entry) { + Ok(()) => entry.parse::>().map_err(Into::into), + Err(err) => Err(err), + }) } Err(err) => resp.entry = Some(Err(err)), Ok(None) => {} @@ -173,6 +183,28 @@ async fn resolve_entry( resp } +/// Verifies that `entry_txt` belongs under the queried +/// [EIP-1459](https://eips.ethereum.org/EIPS/eip-1459) hash label. +/// +/// Entries resolved through `.` are stored below the base32 encoding of an +/// abbreviated `keccak256` digest of their TXT content. The protocol accepts any prefix length in +/// the valid hash range. +fn verify_entry_hash(hash: &str, entry_txt: &str) -> LookupResult<()> { + let expected = + BASE32_NOPAD.decode(hash.as_bytes()).map_err(|_| LookupError::HashMismatch(hash.into()))?; + let actual = keccak256(entry_txt.as_bytes()); + + if !(MIN_HASH_BYTES..=MAX_HASH_BYTES).contains(&expected.len()) { + return Err(LookupError::HashMismatch(hash.into())) + } + + if actual.as_slice().starts_with(&expected) { + Ok(()) + } else { + Err(LookupError::HashMismatch(hash.into())) + } +} + /// Retrieves the root entry the link points to and returns the verified entry /// /// Returns an error if the record could be retrieved but is not a root entry or failed to be @@ -216,6 +248,10 @@ mod tests { use crate::{resolver::TimeoutResolver, DnsDiscoveryConfig, MapResolver}; use std::future::poll_fn; + fn entry_hash(entry_txt: &str) -> String { + BASE32_NOPAD.encode(&keccak256(entry_txt.as_bytes()).as_slice()[..16]) + } + #[tokio::test] async fn test_rate_limit() { let resolver = Arc::new(MapResolver::default()); @@ -274,4 +310,43 @@ mod tests { } } } + + #[test] + fn verify_entry_hash_accepts_eip_1459_vectors() { + let entries = [ + ( + "C7HRFPF3BLGF3YR4DY5KX3SMBE", + "enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org", + ), + ( + "JWXYDBPXYWG6FX3GMDIBFA6CJ4", + "enrtree-branch:2XS2367YHAXJFGLZHVAWLQD4ZY,H4FHT4B454P6UXFD7JCYQ5PWDY,MHTDO6TMUBRIA2XWG5LUDACK24", + ), + ( + "2XS2367YHAXJFGLZHVAWLQD4ZY", + "enr:-HW4QOFzoVLaFJnNhbgMoDXPnOvcdVuj7pDpqRvh6BRDO68aVi5ZcjB3vzQRZH2IcLBGHzo8uUN3snqmgTiE56CH3AMBgmlkgnY0iXNlY3AyNTZrMaECC2_24YYkYHEgdzxlSNKQEnHhuNAbNlMlWJxrJxbAFvA", + ), + ]; + + for (hash, entry) in entries { + verify_entry_hash(hash, entry).unwrap(); + } + } + + #[test] + fn verify_entry_hash_rejects_mismatched_or_invalid_hashes() { + let entry = "enrtree-branch:YNEGZIWHOM7TOOSUATAPTM"; + let hash = entry_hash(entry); + verify_entry_hash(&hash, entry).unwrap(); + + assert!(matches!( + verify_entry_hash(&hash, "enrtree-branch:AAAAAAAAAAAAAAAAAAAA"), + Err(LookupError::HashMismatch(_)) + )); + assert!(matches!( + verify_entry_hash("NOT_BASE32!", entry), + Err(LookupError::HashMismatch(_)) + )); + assert!(matches!(verify_entry_hash("AAAA", entry), Err(LookupError::HashMismatch(_)))); + } } From a8cab01133fed37e99624e7b114131a882c26740 Mon Sep 17 00:00:00 2001 From: Karl Yu <43113774+0xKarl98@users.noreply.github.com> Date: Tue, 5 May 2026 15:03:22 +0800 Subject: [PATCH 027/335] feat(debug-client): pass execution data from providers (#23969) --- Cargo.lock | 2 - crates/consensus/debug-client/Cargo.toml | 2 - crates/consensus/debug-client/src/client.rs | 59 +++++++++---------- crates/consensus/debug-client/src/lib.rs | 4 +- .../debug-client/src/providers/etherscan.rs | 36 +++++------ .../debug-client/src/providers/rpc.rs | 25 ++++---- crates/ethereum/node/src/node.rs | 8 ++- crates/node/builder/src/launch/debug.rs | 52 ++++++++-------- 8 files changed, 95 insertions(+), 93 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0da98b255a1..619b2419c61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7891,7 +7891,6 @@ dependencies = [ name = "reth-consensus-debug-client" version = "2.2.0" dependencies = [ - "alloy-consensus", "alloy-eips 2.0.4", "alloy-json-rpc", "alloy-primitives", @@ -7904,7 +7903,6 @@ dependencies = [ "futures", "reqwest 0.13.2", "reth-node-api", - "reth-primitives-traits", "reth-tracing", "ringbuffer", "serde", diff --git a/crates/consensus/debug-client/Cargo.toml b/crates/consensus/debug-client/Cargo.toml index 86d3145a12f..64ed9f7ebf9 100644 --- a/crates/consensus/debug-client/Cargo.toml +++ b/crates/consensus/debug-client/Cargo.toml @@ -14,10 +14,8 @@ workspace = true # reth reth-node-api.workspace = true reth-tracing.workspace = true -reth-primitives-traits.workspace = true # ethereum -alloy-consensus = { workspace = true, features = ["serde"] } alloy-eips.workspace = true alloy-provider = { workspace = true, features = ["ws"] } alloy-transport.workspace = true diff --git a/crates/consensus/debug-client/src/client.rs b/crates/consensus/debug-client/src/client.rs index cb50708494f..00acd4b0922 100644 --- a/crates/consensus/debug-client/src/client.rs +++ b/crates/consensus/debug-client/src/client.rs @@ -1,35 +1,34 @@ -use alloy_consensus::Sealable; use alloy_primitives::B256; -use reth_node_api::{ - BuiltPayload, ConsensusEngineHandle, ExecutionPayload, NodePrimitives, PayloadTypes, -}; -use reth_primitives_traits::{Block, SealedBlock}; +use reth_node_api::{ConsensusEngineHandle, ExecutionPayload, PayloadTypes}; use reth_tracing::tracing::warn; use ringbuffer::{AllocRingBuffer, RingBuffer}; use std::future::Future; use tokio::sync::mpsc; -/// Supplies consensus client with new blocks sent in `tx` and a callback to find specific blocks -/// by number to fetch past finalized and safe blocks. +/// Supplies consensus client with new execution payloads sent in `tx` and a callback to find +/// specific payloads by number to fetch past finalized and safe block hashes. #[auto_impl::auto_impl(&, Arc, Box)] -pub trait BlockProvider: Send + Sync + 'static { - /// The block type. - type Block: Block; +pub trait PayloadProvider: Send + Sync + 'static { + /// The execution payload data type. + type ExecutionData: ExecutionPayload; - /// Runs a block provider to send new blocks to the given sender. + /// Runs a provider to send new execution payloads to the given sender. /// /// Note: This is expected to be spawned in a separate task, and as such it should ignore /// errors. - fn subscribe_blocks(&self, tx: mpsc::Sender) -> impl Future + Send; + fn subscribe_payloads( + &self, + tx: mpsc::Sender, + ) -> impl Future + Send; - /// Get a past block by number. - fn get_block( + /// Get a past execution payload by block number. + fn get_payload( &self, block_number: u64, - ) -> impl Future> + Send; + ) -> impl Future> + Send; /// Get previous block hash using previous block hash buffer. If it isn't available (buffer - /// started more recently than `offset`), fetch it using `get_block`. + /// started more recently than `offset`), fetch it using `get_payload`. fn get_or_fetch_previous_block( &self, previous_block_hashes: &AllocRingBuffer, @@ -46,23 +45,23 @@ pub trait BlockProvider: Send + Sync + 'static { Some(number) => number, None => return Ok(B256::default()), }; - let block = self.get_block(previous_block_number).await?; - Ok(block.header().hash_slow()) + let payload = self.get_payload(previous_block_number).await?; + Ok(payload.block_hash()) } } } -/// Debug consensus client that sends FCUs and new payloads using recent blocks from an external +/// Debug consensus client that sends FCUs and new payloads using recent payloads from an external /// provider like Etherscan or an RPC endpoint. #[derive(Debug)] -pub struct DebugConsensusClient { +pub struct DebugConsensusClient { /// Handle to execution client. engine_handle: ConsensusEngineHandle, - /// Provider to get consensus blocks from. + /// Provider to get consensus payloads from. block_provider: P, } -impl DebugConsensusClient { +impl DebugConsensusClient { /// Create a new debug consensus client with the given handle to execution /// client and block provider. pub const fn new(engine_handle: ConsensusEngineHandle, block_provider: P) -> Self { @@ -72,25 +71,23 @@ impl DebugConsensusClient { impl DebugConsensusClient where - P: BlockProvider + Clone, - T: PayloadTypes>>, + P: PayloadProvider + Clone, + T: PayloadTypes, { /// Spawn the client to start sending FCUs and new payloads by periodically fetching recent - /// blocks. + /// payloads. pub async fn run(self) { let mut previous_block_hashes = AllocRingBuffer::new(65); - let mut block_stream = { - let (tx, rx) = mpsc::channel::(64); + let mut payload_stream = { + let (tx, rx) = mpsc::channel::(64); let block_provider = self.block_provider.clone(); tokio::spawn(async move { - block_provider.subscribe_blocks(tx).await; + block_provider.subscribe_payloads(tx).await; }); rx }; - while let Some(block) = block_stream.recv().await { - let payload = T::block_to_payload(SealedBlock::new_unhashed(block)); - + while let Some(payload) = payload_stream.recv().await { let block_hash = payload.block_hash(); let block_number = payload.block_number(); diff --git a/crates/consensus/debug-client/src/lib.rs b/crates/consensus/debug-client/src/lib.rs index 16dc9d34578..971fc4aa0e7 100644 --- a/crates/consensus/debug-client/src/lib.rs +++ b/crates/consensus/debug-client/src/lib.rs @@ -1,6 +1,6 @@ //! Debug consensus client. //! -//! This is a worker that sends FCUs and new payloads by fetching recent blocks from an external +//! This is a worker that sends FCUs and new payloads by fetching recent payloads from an external //! provider like Etherscan or an RPC endpoint. This allows to quickly test the execution client //! without running a consensus node. @@ -15,5 +15,5 @@ mod client; mod providers; -pub use client::{BlockProvider, DebugConsensusClient}; +pub use client::{DebugConsensusClient, PayloadProvider}; pub use providers::{EtherscanBlockProvider, RpcBlockProvider}; diff --git a/crates/consensus/debug-client/src/providers/etherscan.rs b/crates/consensus/debug-client/src/providers/etherscan.rs index 92dab80bf7f..fba239d7dda 100644 --- a/crates/consensus/debug-client/src/providers/etherscan.rs +++ b/crates/consensus/debug-client/src/providers/etherscan.rs @@ -1,8 +1,8 @@ -use crate::BlockProvider; -use alloy_consensus::BlockHeader; +use crate::PayloadProvider; use alloy_eips::BlockNumberOrTag; use alloy_json_rpc::{Response, ResponsePayload}; use reqwest::Client; +use reth_node_api::ExecutionPayload; use reth_tracing::tracing::{debug, warn}; use serde::{de::DeserializeOwned, Serialize}; use std::{sync::Arc, time::Duration}; @@ -10,17 +10,17 @@ use tokio::{sync::mpsc, time::interval}; /// Block provider that fetches new blocks from Etherscan API. #[derive(derive_more::Debug, Clone)] -pub struct EtherscanBlockProvider { +pub struct EtherscanBlockProvider { http_client: Client, base_url: String, api_key: String, chain_id: u64, interval: Duration, #[debug(skip)] - convert: Arc PrimitiveBlock + Send + Sync>, + convert: Arc ExecutionData + Send + Sync>, } -impl EtherscanBlockProvider +impl EtherscanBlockProvider where RpcBlock: Serialize + DeserializeOwned, { @@ -29,7 +29,7 @@ where base_url: String, api_key: String, chain_id: u64, - convert: impl Fn(RpcBlock) -> PrimitiveBlock + Send + Sync + 'static, + convert: impl Fn(RpcBlock) -> ExecutionData + Send + Sync + 'static, ) -> Self { Self { http_client: Client::new(), @@ -50,10 +50,10 @@ where /// Load block using Etherscan API. Note: only `BlockNumberOrTag::Latest`, /// `BlockNumberOrTag::Earliest`, `BlockNumberOrTag::Pending`, `BlockNumberOrTag::Number(u64)` /// are supported. - pub async fn load_block( + pub async fn load_payload( &self, block_number_or_tag: BlockNumberOrTag, - ) -> eyre::Result { + ) -> eyre::Result { let tag = match block_number_or_tag { BlockNumberOrTag::Number(num) => format!("{num:#x}"), tag => tag.to_string(), @@ -88,20 +88,20 @@ where } } -impl BlockProvider for EtherscanBlockProvider +impl PayloadProvider for EtherscanBlockProvider where RpcBlock: Serialize + DeserializeOwned + 'static, - PrimitiveBlock: reth_primitives_traits::Block + 'static, + ExecutionData: ExecutionPayload, { - type Block = PrimitiveBlock; + type ExecutionData = ExecutionData; - async fn subscribe_blocks(&self, tx: mpsc::Sender) { + async fn subscribe_payloads(&self, tx: mpsc::Sender) { let mut last_block_number: Option = None; let mut interval = interval(self.interval); loop { interval.tick().await; - let block = match self.load_block(BlockNumberOrTag::Latest).await { - Ok(block) => block, + let payload = match self.load_payload(BlockNumberOrTag::Latest).await { + Ok(payload) => payload, Err(err) => { warn!( target: "consensus::debug-client", @@ -111,12 +111,12 @@ where continue } }; - let block_number = block.header().number(); + let block_number = payload.block_number(); if Some(block_number) == last_block_number { continue; } - if tx.send(block).await.is_err() { + if tx.send(payload).await.is_err() { // Channel closed. break; } @@ -125,7 +125,7 @@ where } } - async fn get_block(&self, block_number: u64) -> eyre::Result { - self.load_block(BlockNumberOrTag::Number(block_number)).await + async fn get_payload(&self, block_number: u64) -> eyre::Result { + self.load_payload(BlockNumberOrTag::Number(block_number)).await } } diff --git a/crates/consensus/debug-client/src/providers/rpc.rs b/crates/consensus/debug-client/src/providers/rpc.rs index 91a3aef1f3e..03680d3efa2 100644 --- a/crates/consensus/debug-client/src/providers/rpc.rs +++ b/crates/consensus/debug-client/src/providers/rpc.rs @@ -1,8 +1,8 @@ -use crate::BlockProvider; +use crate::PayloadProvider; use alloy_provider::{ConnectionConfig, Network, Provider, ProviderBuilder, WebSocketConfig}; use alloy_transport::TransportResult; use futures::{Stream, StreamExt}; -use reth_node_api::Block; +use reth_node_api::ExecutionPayload; use reth_tracing::tracing::{debug, warn}; use std::sync::Arc; use tokio::sync::mpsc::Sender; @@ -10,19 +10,19 @@ use tokio::sync::mpsc::Sender; /// Block provider that fetches new blocks from an RPC endpoint using a connection that supports /// RPC subscriptions. #[derive(derive_more::Debug, Clone)] -pub struct RpcBlockProvider { +pub struct RpcBlockProvider { #[debug(skip)] provider: Arc>, url: String, #[debug(skip)] - convert: Arc PrimitiveBlock + Send + Sync>, + convert: Arc ExecutionData + Send + Sync>, } -impl RpcBlockProvider { +impl RpcBlockProvider { /// Create a new RPC block provider with the given RPC URL. pub async fn new( rpc_url: &str, - convert: impl Fn(N::BlockResponse) -> PrimitiveBlock + Send + Sync + 'static, + convert: impl Fn(N::BlockResponse) -> ExecutionData + Send + Sync + 'static, ) -> eyre::Result { Ok(Self { provider: Arc::new( @@ -66,13 +66,13 @@ impl RpcBlockProvider { } } -impl BlockProvider for RpcBlockProvider +impl PayloadProvider for RpcBlockProvider where - PrimitiveBlock: Block + 'static, + ExecutionData: ExecutionPayload, { - type Block = PrimitiveBlock; + type ExecutionData = ExecutionData; - async fn subscribe_blocks(&self, tx: Sender) { + async fn subscribe_payloads(&self, tx: Sender) { loop { let Ok(mut stream) = self.full_block_stream().await.inspect_err(|err| { warn!( @@ -94,7 +94,8 @@ where while let Some(res) = stream.next().await { match res { Ok(block) => { - if tx.send((self.convert)(block)).await.is_err() { + let payload = (self.convert)(block); + if tx.send(payload).await.is_err() { // Channel closed - receiver dropped, exit completely. return; } @@ -118,7 +119,7 @@ where } } - async fn get_block(&self, block_number: u64) -> eyre::Result { + async fn get_payload(&self, block_number: u64) -> eyre::Result { let block = self .provider .get_block_by_number(block_number.into()) diff --git a/crates/ethereum/node/src/node.rs b/crates/ethereum/node/src/node.rs index eca9d36fbdc..be1219982e2 100644 --- a/crates/ethereum/node/src/node.rs +++ b/crates/ethereum/node/src/node.rs @@ -452,8 +452,12 @@ where impl> DebugNode for EthereumNode { type RpcBlock = alloy_rpc_types_eth::Block; - fn rpc_to_primitive_block(rpc_block: Self::RpcBlock) -> reth_ethereum_primitives::Block { - rpc_block.into_consensus().convert_transactions() + fn rpc_to_execution_data(rpc_block: Self::RpcBlock) -> ExecutionData { + let (block, hash) = rpc_block.into_consensus_sealed().into_parts(); + ExecutionData::from_block_unchecked( + hash, + &block.convert_transactions::(), + ) } fn local_payload_attributes_builder( diff --git a/crates/node/builder/src/launch/debug.rs b/crates/node/builder/src/launch/debug.rs index 3a5644d83ae..c8e1207fafe 100644 --- a/crates/node/builder/src/launch/debug.rs +++ b/crates/node/builder/src/launch/debug.rs @@ -5,12 +5,12 @@ use alloy_provider::network::AnyNetwork; use jsonrpsee::core::{DeserializeOwned, Serialize}; use reth_chainspec::EthChainSpec; use reth_consensus_debug_client::{ - BlockProvider, DebugConsensusClient, EtherscanBlockProvider, RpcBlockProvider, + DebugConsensusClient, EtherscanBlockProvider, PayloadProvider, RpcBlockProvider, }; use reth_engine_local::{LocalMiner, MiningMode}; use reth_node_api::{ - BlockTy, FullNodeComponents, FullNodeTypes, HeaderTy, PayloadAttrTy, PayloadAttributesBuilder, - PayloadTypes, + FullNodeComponents, FullNodeTypes, HeaderTy, NodeTypes, PayloadAttrTy, + PayloadAttributesBuilder, PayloadTypes, }; use std::{ future::{Future, IntoFuture}, @@ -19,11 +19,14 @@ use std::{ }; use tracing::info; +/// Helper adapter type for accessing [`PayloadTypes::ExecutionData`] on [`NodeTypes`]. +pub(crate) type PayloadDataTy = <::Payload as PayloadTypes>::ExecutionData; + /// [`Node`] extension with support for debugging utilities. /// /// This trait provides additional necessary conversion from RPC block type to the node's -/// primitive block type, e.g. `alloy_rpc_types_eth::Block` to the node's internal block -/// representation. +/// execution payload data type, e.g. `alloy_rpc_types_eth::Block` to the node's Engine API +/// payload representation. /// /// This is used in conjunction with the [`DebugNodeLauncher`] to enable debugging features such as: /// @@ -38,7 +41,7 @@ use tracing::info; /// /// To implement this trait, you need to: /// 1. Define the RPC block type (typically `alloy_rpc_types_eth::Block`) -/// 2. Implement the conversion from RPC format to your primitive block type +/// 2. Implement the conversion from RPC format to your execution payload data type /// /// # Example /// @@ -46,27 +49,28 @@ use tracing::info; /// impl> DebugNode for MyNode { /// type RpcBlock = alloy_rpc_types_eth::Block; /// -/// fn rpc_to_primitive_block(rpc_block: Self::RpcBlock) -> BlockTy { -/// // Convert from RPC format to primitive format by converting the transactions -/// rpc_block.into_consensus().convert_transactions() +/// fn rpc_to_execution_data( +/// rpc_block: Self::RpcBlock, +/// ) -> PayloadDataTy { +/// // Convert from RPC format to the Engine API payload data expected by the node. /// } /// } /// ``` pub trait DebugNode: Node { - /// RPC block type. Used by [`DebugConsensusClient`] to fetch blocks and submit them to the - /// engine. This is intended to match the block format returned by the external RPC endpoint. + /// RPC block type. This is intended to match the block format returned by the external RPC + /// endpoint. type RpcBlock: Serialize + DeserializeOwned + 'static; - /// Converts an RPC block to a primitive block. + /// Converts an RPC block to execution payload data. /// - /// This method handles the conversion between the RPC block format and the internal primitive - /// block format used by the node's consensus engine. + /// This method handles the conversion between the RPC block format and the internal Engine API + /// payload data used by the node's consensus engine. /// /// # Example /// /// For Ethereum nodes, this typically converts from `alloy_rpc_types_eth::Block` - /// to the node's internal block representation. - fn rpc_to_primitive_block(rpc_block: Self::RpcBlock) -> BlockTy; + /// to the node's internal execution payload data representation. + fn rpc_to_execution_data(rpc_block: Self::RpcBlock) -> PayloadDataTy; /// Creates a payload attributes builder for local mining in dev mode. /// @@ -116,7 +120,7 @@ impl DebugNodeLauncher { /// bounds. pub type DefaultDebugBlockProvider = EtherscanBlockProvider< <::Types as DebugNode>::RpcBlock, - BlockTy<::Types>, + PayloadDataTy<::Types>, >; /// Future for the [`DebugNodeLauncher`]. @@ -140,7 +144,7 @@ where N: FullNodeComponents>, AddOns: RethRpcAddOns, L: LaunchNode>, - B: BlockProvider> + Clone, + B: PayloadProvider> + Clone, { /// Sets a custom payload attributes builder for local mining in dev mode. pub fn with_payload_attributes_builder( @@ -181,7 +185,7 @@ where self } - /// Sets a custom block provider for the debug consensus client. + /// Sets a custom payload provider for the debug consensus client. /// /// When set, this provider will be used instead of creating an `EtherscanBlockProvider` /// or `RpcBlockProvider` from CLI arguments. @@ -190,7 +194,7 @@ where provider: B2, ) -> DebugNodeLauncherFuture where - B2: BlockProvider> + Clone, + B2: PayloadProvider> + Clone, { DebugNodeLauncherFuture { inner: self.inner, @@ -239,7 +243,7 @@ where .expect("Block serialization cannot fail"); let rpc_block = serde_json::from_value(json).expect("Block deserialization cannot fail"); - N::Types::rpc_to_primitive_block(rpc_block) + N::Types::rpc_to_execution_data(rpc_block) }) .await?; @@ -270,7 +274,7 @@ where ) })?, chain.id(), - N::Types::rpc_to_primitive_block, + N::Types::rpc_to_execution_data, ); let rpc_consensus_client = DebugConsensusClient::new( handle.node.add_ons_handle.beacon_engine_handle.clone(), @@ -339,7 +343,7 @@ where N: FullNodeComponents>, AddOns: RethRpcAddOns + 'static, L: LaunchNode> + 'static, - B: BlockProvider> + Clone + 'static, + B: PayloadProvider> + Clone + 'static, { type Output = eyre::Result>; type IntoFuture = Pin>> + Send>>; @@ -355,7 +359,7 @@ where N: FullNodeComponents>, AddOns: RethRpcAddOns + 'static, L: LaunchNode> + 'static, - DefaultDebugBlockProvider: BlockProvider> + Clone, + DefaultDebugBlockProvider: PayloadProvider> + Clone, { type Node = NodeHandle; type Future = DebugNodeLauncherFuture; From dc9b33b9b4c0d9d3acb6bfbaccb2ef3256a3ff05 Mon Sep 17 00:00:00 2001 From: figtracer Date: Tue, 5 May 2026 13:15:42 +0100 Subject: [PATCH 028/335] fix(rpc): fill `maxUsedGas` in simulate results (#23983) --- crates/ethereum/node/tests/e2e/simulate.rs | 6 ++++-- crates/rpc/rpc-eth-types/src/simulate.rs | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/crates/ethereum/node/tests/e2e/simulate.rs b/crates/ethereum/node/tests/e2e/simulate.rs index 32405511f08..d30b58c7e4b 100644 --- a/crates/ethereum/node/tests/e2e/simulate.rs +++ b/crates/ethereum/node/tests/e2e/simulate.rs @@ -73,8 +73,10 @@ async fn test_simulate_v1_with_max_fee_per_blob_gas_only() -> eyre::Result<()> { assert_eq!(result.len(), 1, "expected exactly 1 simulated block"); assert_eq!(result[0].calls.len(), 1, "expected exactly 1 call result"); - assert!(result[0].calls[0].status, "expected call to succeed"); - assert!(result[0].calls[0].error.is_none(), "expected no error"); + let call = &result[0].calls[0]; + assert!(call.status, "expected call to succeed"); + assert!(call.error.is_none(), "expected no error"); + assert_eq!(call.max_used_gas, Some(call.gas_used), "expected maxUsedGas in call result"); Ok(()) } diff --git a/crates/rpc/rpc-eth-types/src/simulate.rs b/crates/rpc/rpc-eth-types/src/simulate.rs index 7946459cfe3..79b4889780e 100644 --- a/crates/rpc/rpc-eth-types/src/simulate.rs +++ b/crates/rpc/rpc-eth-types/src/simulate.rs @@ -315,9 +315,9 @@ where ..SimulateError::invalid_params() }), gas_used: gas.tx_gas_used(), + max_used_gas: Some(gas.total_gas_spent()), logs: Vec::new(), status: false, - ..Default::default() } } ExecutionResult::Revert { output, gas, .. } => { @@ -330,15 +330,16 @@ where ..SimulateError::invalid_params() }), gas_used: gas.tx_gas_used(), + max_used_gas: Some(gas.total_gas_spent()), status: false, logs: Vec::new(), - ..Default::default() } } ExecutionResult::Success { output, gas, logs, .. } => SimCallResult { return_data: output.into_data(), error: None, gas_used: gas.tx_gas_used(), + max_used_gas: Some(gas.total_gas_spent()), logs: logs .into_iter() .map(|log| { @@ -356,7 +357,6 @@ where }) .collect(), status: true, - ..Default::default() }, }; From 12ec7c57281c611355a02a2032ea7330ed9d3c68 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Tue, 5 May 2026 16:25:37 +0400 Subject: [PATCH 029/335] fix: skip already-known executed blocks (#23987) --- crates/engine/tree/src/tree/mod.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/engine/tree/src/tree/mod.rs b/crates/engine/tree/src/tree/mod.rs index 41bd429373d..624501bc7bf 100644 --- a/crates/engine/tree/src/tree/mod.rs +++ b/crates/engine/tree/src/tree/mod.rs @@ -1555,6 +1555,11 @@ where return Ok(ops::ControlFlow::Continue(())) } + if self.state.tree_state.contains_hash(&block_num_hash.hash) { + // block already known to the tree (e.g. delivered via newPayload first) + return Ok(ops::ControlFlow::Continue(())) + } + debug!(target: "engine::tree", block=?block_num_hash, "inserting already executed block"); let now = Instant::now(); From 83287322d5d15a7a72bd9419f3ee766f7ae1426a Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Tue, 5 May 2026 08:52:41 -0400 Subject: [PATCH 030/335] fix(engine): increase state root task timeout to 4s (#23949) --- crates/node/core/src/args/engine.rs | 6 +++--- docs/vocs/docs/pages/cli/reth/node.mdx | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/node/core/src/args/engine.rs b/crates/node/core/src/args/engine.rs index 5dedc68747b..382df972b71 100644 --- a/crates/node/core/src/args/engine.rs +++ b/crates/node/core/src/args/engine.rs @@ -285,7 +285,7 @@ impl Default for DefaultEngineValues { sparse_trie_max_hot_accounts: DEFAULT_SPARSE_TRIE_MAX_HOT_ACCOUNTS, slow_block_threshold: None, disable_sparse_trie_cache_pruning: false, - state_root_task_timeout: Some("1s".to_string()), + state_root_task_timeout: Some("4s".to_string()), share_execution_cache_with_payload_builder: false, share_sparse_trie_with_payload_builder: false, suppress_persistence_during_build: false, @@ -459,14 +459,14 @@ pub struct EngineArgs { /// If the state root task takes longer than this, a sequential computation starts in /// parallel and whichever finishes first is used. /// - /// --engine.state-root-task-timeout 1s + /// --engine.state-root-task-timeout 4s /// --engine.state-root-task-timeout 400ms /// /// Set to 0s to disable. #[arg( long = "engine.state-root-task-timeout", value_parser = humantime::parse_duration, - default_value = DefaultEngineValues::get_global().state_root_task_timeout.as_deref().unwrap_or("1s"), + default_value = DefaultEngineValues::get_global().state_root_task_timeout.as_deref().unwrap_or("4s"), )] pub state_root_task_timeout: Option, diff --git a/docs/vocs/docs/pages/cli/reth/node.mdx b/docs/vocs/docs/pages/cli/reth/node.mdx index 3d5a60beb57..0b687232dcb 100644 --- a/docs/vocs/docs/pages/cli/reth/node.mdx +++ b/docs/vocs/docs/pages/cli/reth/node.mdx @@ -1058,11 +1058,11 @@ Engine: --engine.state-root-task-timeout Configure the timeout for the state root task before spawning a sequential fallback. If the state root task takes longer than this, a sequential computation starts in parallel and whichever finishes first is used. - --engine.state-root-task-timeout 1s --engine.state-root-task-timeout 400ms + --engine.state-root-task-timeout 4s --engine.state-root-task-timeout 400ms Set to 0s to disable. - [default: 1s] + [default: 4s] --engine.share-execution-cache-with-payload-builder Whether to share execution cache with the payload builder. From 1497ee78aaf9f2807f234433f7e106caac14a845 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 5 May 2026 16:42:37 +0200 Subject: [PATCH 031/335] fix(rpc): relax forkchoice update error match (#23941) --- crates/rpc/rpc-engine-api/src/error.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/rpc/rpc-engine-api/src/error.rs b/crates/rpc/rpc-engine-api/src/error.rs index c521cd14c56..f6070b0515a 100644 --- a/crates/rpc/rpc-engine-api/src/error.rs +++ b/crates/rpc/rpc-engine-api/src/error.rs @@ -195,6 +195,15 @@ impl From for jsonrpsee_types::error::ErrorObject<'static> { None::<()>, ) } + // Map future alloy forkchoice errors as internal until handled. + #[allow(unreachable_patterns, clippy::needless_return)] + _ => { + return jsonrpsee_types::error::ErrorObject::owned( + INTERNAL_ERROR_CODE, + SERVER_ERROR_MSG, + Some(ErrorData::new(error)), + ); + } }, BeaconForkChoiceUpdateError::EngineUnavailable | BeaconForkChoiceUpdateError::Internal(_) => { From 883505692f84d8ac4388f6c2753ab23041cf643a Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Tue, 5 May 2026 15:41:10 +0100 Subject: [PATCH 032/335] ci(bench): update snapshots with `reth download` (#23984) --- .github/scripts/bench-reth-local.sh | 18 ++-- .github/scripts/bench-reth-run.sh | 7 +- .github/scripts/bench-reth-snapshot.sh | 132 ++++++++++++++++++++----- .github/workflows/bench-scheduled.yml | 13 ++- .github/workflows/bench.yml | 15 +-- 5 files changed, 135 insertions(+), 50 deletions(-) diff --git a/.github/scripts/bench-reth-local.sh b/.github/scripts/bench-reth-local.sh index 4d2565592d8..2e8a1cc8dcb 100755 --- a/.github/scripts/bench-reth-local.sh +++ b/.github/scripts/bench-reth-local.sh @@ -18,7 +18,8 @@ # --tracy-filter F Tracy tracing filter (default: debug) # --no-tune Skip system tuning (useful on dev machines / macOS) # -# Requires: the reth repo at RETH_REPO (default: ~/reth) +# Requires: the reth repo at RETH_REPO (default: ~/reth) and +# BENCH_SNAPSHOT_MANIFEST_URL pointing at the benchmark snapshot manifest. # # Dependencies (install before first run): # schelk, cpupower, taskset, stdbuf, python3, curl, @@ -240,14 +241,7 @@ echo " Baseline src : $BASELINE_SRC" echo " Feature src : $FEATURE_SRC" echo -# ── Step 3: Validate local snapshot ────────────────────────────────── -echo "▸ Validating local snapshot..." -cd "$RETH_REPO" -"${SCRIPTS_DIR}/bench-reth-snapshot.sh" -echo " Snapshot is ready." -echo - -# ── Step 4: Build binaries in parallel ─────────────────────────────── +# ── Step 3: Build binaries in parallel ─────────────────────────────── echo "▸ Building binaries (parallel)..." cd "$RETH_REPO" @@ -269,6 +263,12 @@ fi echo " Binaries built successfully." echo +# ── Step 4: Sync snapshot ──────────────────────────────────────────── +echo "▸ Syncing snapshot..." +BENCH_RETH_BINARY="${FEATURE_SRC}/target/profiling/reth" "${SCRIPTS_DIR}/bench-reth-snapshot.sh" +echo " Snapshot is ready." +echo + # ── Step 5: System tuning (optional) ──────────────────────────────── if [ "$TUNE" = "true" ]; then echo "▸ Applying system tuning..." diff --git a/.github/scripts/bench-reth-run.sh b/.github/scripts/bench-reth-run.sh index 7c272ad74ea..37dbb848af5 100755 --- a/.github/scripts/bench-reth-run.sh +++ b/.github/scripts/bench-reth-run.sh @@ -130,8 +130,8 @@ RETH_ARGS=( --no-persist-peers ) -# Gate flag on binary support (older baselines may not have it). -# Uses --help which exits immediately via clap without node init. +# Keep the engine in startup sync mode until the pipeline is idle. Older +# baselines may not have this flag, so gate it on clap help output. SYNC_STATE_IDLE=false if "$BINARY" node --help 2>/dev/null | grep -qF -- '--debug.startup-sync-state-idle'; then RETH_ARGS+=(--debug.startup-sync-state-idle) @@ -220,9 +220,6 @@ for i in $(seq 1 60); do sleep 1 done -# Wait for the pipeline to finish (eth_syncing returns false) so the -# engine is in live mode and can accept newPayload calls. -# Only possible when --debug.startup-sync-state-idle is supported. if [ "$SYNC_STATE_IDLE" = "true" ]; then for i in $(seq 1 300); do SYNC_RESULT=$(curl -sf http://127.0.0.1:8545 -X POST \ diff --git a/.github/scripts/bench-reth-snapshot.sh b/.github/scripts/bench-reth-snapshot.sh index 5d8b883e544..7672c2a1980 100755 --- a/.github/scripts/bench-reth-snapshot.sh +++ b/.github/scripts/bench-reth-snapshot.sh @@ -1,56 +1,138 @@ #!/usr/bin/env bash # -# Validates that the benchmark snapshot has already been populated into the -# local schelk volume. +# Ensures the benchmark snapshot in the schelk volume matches the configured +# manifest. If the local manifest marker differs from the remote manifest, the +# snapshot is replaced via `reth download` and promoted as the new schelk +# baseline. # # Usage: bench-reth-snapshot.sh [--check] -# --check Exit 0 if the local snapshot is ready, 10 if it is missing. +# --check Exit 0 if the local snapshot is current, 10 if it needs refresh. # # Required env: -# SCHELK_MOUNT – schelk mount point (e.g. /reth-bench) +# SCHELK_MOUNT - schelk mount point (e.g. /reth-bench) +# BENCH_SNAPSHOT_MANIFEST_URL - exact manifest URL to sync from +# BENCH_RETH_BINARY - path to the reth-compatible binary (required to refresh) +# # Optional env: -# BENCH_BIG_BLOCKS – true when validating the big-blocks snapshot datadir -# BENCH_SNAPSHOT_NAME – expected snapshot label for log/error output -set -euxo pipefail +# BENCH_BIG_BLOCKS - true when syncing the big-blocks datadir +set -euo pipefail : "${SCHELK_MOUNT:?SCHELK_MOUNT must be set}" +: "${BENCH_SNAPSHOT_MANIFEST_URL:?BENCH_SNAPSHOT_MANIFEST_URL must be set}" + +if [ "${1:-}" != "" ] && [ "${1:-}" != "--check" ]; then + echo "Usage: $0 [--check]" + exit 1 +fi + +CHECK_ONLY=false +if [ "${1:-}" = "--check" ]; then + CHECK_ONLY=true +fi DATADIR_NAME="datadir" if [ "${BENCH_BIG_BLOCKS:-false}" = "true" ]; then DATADIR_NAME="datadir-big-blocks" fi DATADIR="$SCHELK_MOUNT/$DATADIR_NAME" +LOCAL_MANIFEST="$DATADIR/manifest.json" -describe_snapshot() { - if [ -n "${BENCH_SNAPSHOT_NAME:-}" ]; then - printf '%s' "${BENCH_SNAPSHOT_NAME}" - elif [ "${BENCH_BIG_BLOCKS:-false}" = "true" ]; then - printf '%s' 'big-block weekly snapshot' - else - printf '%s' 'benchmark snapshot' - fi -} +MANIFEST_URL="$BENCH_SNAPSHOT_MANIFEST_URL" +MANIFEST_BASE_URL="${MANIFEST_URL%/*}" + +REMOTE_MANIFEST="$(mktemp "${TMPDIR:-/tmp}/reth-bench-remote-manifest.XXXXXX")" +REMOTE_CANONICAL="$(mktemp "${TMPDIR:-/tmp}/reth-bench-remote-canonical.XXXXXX")" +LOCAL_CANONICAL="$(mktemp "${TMPDIR:-/tmp}/reth-bench-local-canonical.XXXXXX")" +DOWNLOAD_MANIFEST="$(mktemp "${TMPDIR:-/tmp}/reth-bench-download-manifest.XXXXXX")" +trap 'rm -f "$REMOTE_MANIFEST" "$REMOTE_CANONICAL" "$LOCAL_CANONICAL" "$DOWNLOAD_MANIFEST"' EXIT snapshot_ready() { [ -d "$DATADIR/db" ] && [ -d "$DATADIR/static_files" ] } -EXPECTED_SNAPSHOT="$(describe_snapshot)" +sha256_file() { + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$1" | awk '{print $1}' + else + shasum -a 256 "$1" | awk '{print $1}' + fi +} + +echo "Using snapshot manifest from BENCH_SNAPSHOT_MANIFEST_URL" + +if ! curl -fsSL --retry 3 --retry-delay 5 --connect-timeout 10 "$MANIFEST_URL" -o "$REMOTE_MANIFEST"; then + echo "::error::Failed to fetch snapshot manifest from BENCH_SNAPSHOT_MANIFEST_URL" + exit 2 +fi + +if ! jq -S . "$REMOTE_MANIFEST" > "$REMOTE_CANONICAL"; then + echo "::error::Snapshot manifest is not valid JSON" + exit 2 +fi +REMOTE_HASH="$(sha256_file "$REMOTE_CANONICAL")" sudo schelk recover -y --kill || sudo schelk full-recover -y || true -sudo schelk mount -y || true +sudo schelk mount -y + +LOCAL_HASH="" +LOCAL_MATCH=false +if [ -f "$LOCAL_MANIFEST" ] && jq -S . "$LOCAL_MANIFEST" > "$LOCAL_CANONICAL"; then + LOCAL_HASH="$(sha256_file "$LOCAL_CANONICAL")" + if cmp -s "$REMOTE_CANONICAL" "$LOCAL_CANONICAL"; then + LOCAL_MATCH=true + fi +fi -if snapshot_ready; then - echo "Found local ${EXPECTED_SNAPSHOT} at ${DATADIR}" +if [ "$LOCAL_MATCH" = true ] && snapshot_ready; then + echo "Snapshot is up-to-date (manifest hash: ${REMOTE_HASH:0:16})" exit 0 fi -echo "::error::Missing local ${EXPECTED_SNAPSHOT} at ${DATADIR}. Benchmarks no longer download snapshots; pre-populate the local schelk data first." -ls -la "$SCHELK_MOUNT" || true -ls -la "$DATADIR" || true +if ! snapshot_ready; then + echo "Snapshot needs refresh: missing expected db/ or static_files/ under ${DATADIR}" +elif [ ! -f "$LOCAL_MANIFEST" ]; then + echo "Snapshot needs refresh: missing local manifest marker at ${LOCAL_MANIFEST}" +elif [ -z "$LOCAL_HASH" ]; then + echo "Snapshot needs refresh: local manifest marker is not valid JSON" +else + echo "Snapshot needs refresh (local: ${LOCAL_HASH:0:16}, remote: ${REMOTE_HASH:0:16})" +fi -if [ "${1:-}" = "--check" ]; then +if [ "$CHECK_ONLY" = true ]; then exit 10 fi -exit 1 +RETH="${BENCH_RETH_BINARY:?BENCH_RETH_BINARY must be set when refreshing the snapshot}" +if [ ! -x "$RETH" ]; then + echo "::error::reth binary not found or not executable at ${RETH}" + exit 1 +fi + +# Force archive URLs to resolve relative to the configured manifest endpoint. +# Some published manifests carry a base_url that is valid for publishers but +# not for benchmark runners. +jq --arg base "$MANIFEST_BASE_URL" '.base_url = $base' "$REMOTE_MANIFEST" > "$DOWNLOAD_MANIFEST" + +sudo rm -rf "$DATADIR" +sudo mkdir -p "$DATADIR" +# reth download runs as the current user and needs write access. +sudo chown -R "$(id -u):$(id -g)" "$DATADIR" + +"$RETH" download \ + --manifest-path "$DOWNLOAD_MANIFEST" \ + -y \ + --minimal \ + --datadir "$DATADIR" + +if ! snapshot_ready; then + echo "::error::Snapshot download did not produce expected directory layout (missing db/ or static_files/)" + ls -la "$DATADIR" || true + exit 1 +fi + +cp "$REMOTE_MANIFEST" "$LOCAL_MANIFEST" + +sync +sudo schelk promote -y + +echo "Snapshot promoted to schelk baseline (manifest hash: ${REMOTE_HASH:0:16})" diff --git a/.github/workflows/bench-scheduled.yml b/.github/workflows/bench-scheduled.yml index 3996a18e6c0..f849b301e93 100644 --- a/.github/workflows/bench-scheduled.yml +++ b/.github/workflows/bench-scheduled.yml @@ -272,6 +272,7 @@ jobs: BENCH_ABBA: "true" BENCH_COMMENT_ID: "" BENCH_SLACK: ${{ github.event_name == 'workflow_dispatch' && inputs.slack || 'always' }} + BENCH_SNAPSHOT_MANIFEST_URL: ${{ secrets.BENCH_SNAPSHOT_MANIFEST_URL }} BENCH_METRICS_ADDR: "127.0.0.1:9100" BENCH_OTLP_DISABLED: "true" BASELINE_REF: ${{ needs.resolve-refs.outputs.baseline-ref }} @@ -372,10 +373,6 @@ jobs: echo "feature-name=${BENCH_MODE}-${FEATURE_SHORT}" >> "$GITHUB_OUTPUT" echo "feature-ref=$FEATURE_REF" >> "$GITHUB_OUTPUT" - - name: Validate local snapshot - id: snapshot-check - run: .github/scripts/bench-reth-snapshot.sh - - name: Prepare source dirs run: | prepare_source_dir() { @@ -419,6 +416,12 @@ jobs: exit 1 fi + - name: Sync snapshot + id: snapshot-check + run: | + BENCH_RETH_BINARY="$(pwd)/../reth-feature/target/profiling/reth" \ + .github/scripts/bench-reth-snapshot.sh + # System tuning for reproducible benchmarks - name: System setup run: | @@ -919,8 +922,8 @@ jobs: if (!token || !channel) return; const steps_status = [ - ['validating local snapshot', '${{ steps.snapshot-check.outcome }}'], ['building binaries', '${{ steps.build.outcome }}'], + ['syncing snapshot', '${{ steps.snapshot-check.outcome }}'], ['running baseline benchmark (1/2)', '${{ steps.run-baseline-1.outcome }}'], ['running feature benchmark (1/2)', '${{ steps.run-feature-1.outcome }}'], ['running feature benchmark (2/2)', '${{ steps.run-feature-2.outcome }}'], diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 3274d26d471..0088bad730c 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -555,6 +555,7 @@ jobs: BENCH_COMMENT_ID: ${{ needs.reth-bench-ack.outputs.comment-id }} BENCH_SLACK: ${{ needs.reth-bench-ack.outputs.slack }} BENCH_NODE_BIN: ${{ needs.reth-bench-ack.outputs.big-blocks == 'true' && 'reth-bb' || 'reth' }} + BENCH_SNAPSHOT_MANIFEST_URL: ${{ secrets.BENCH_SNAPSHOT_MANIFEST_URL }} BENCH_METRICS_ADDR: "127.0.0.1:9100" BENCH_OTLP_TRACES_ENDPOINT: ${{ needs.reth-bench-ack.outputs.otlp != 'false' && secrets.BENCH_OTLP_TRACES_ENDPOINT || '' }} BENCH_OTLP_LOGS_ENDPOINT: ${{ needs.reth-bench-ack.outputs.otlp != 'false' && secrets.BENCH_OTLP_LOGS_ENDPOINT || '' }} @@ -802,10 +803,6 @@ jobs: echo "Payload files: $PAYLOAD_COUNT" echo "BENCH_SNAPSHOT_NAME=${BASE_SNAPSHOT}" >> "$GITHUB_ENV" - - name: Validate local snapshot - id: snapshot-check - run: .github/scripts/bench-reth-snapshot.sh - - name: Prepare source dirs run: | prepare_source_dir() { @@ -851,6 +848,12 @@ jobs: exit 1 fi + - name: Sync snapshot + id: snapshot-check + run: | + BENCH_RETH_BINARY="$(pwd)/../reth-feature/target/profiling/${BENCH_NODE_BIN}" \ + .github/scripts/bench-reth-snapshot.sh + # System tuning for reproducible benchmarks - name: System setup run: | @@ -1340,8 +1343,8 @@ jobs: const bigBlocks = process.env.BENCH_BIG_BLOCKS === 'true'; const steps_status = [ ...(bigBlocks ? [['validating local big-block data', '${{ steps.big-blocks-check.outcome }}']] : []), - ['validating local snapshot', '${{ steps.snapshot-check.outcome }}'], ['building binaries', '${{ steps.build.outcome }}'], + ['syncing snapshot', '${{ steps.snapshot-check.outcome }}'], ['running feature benchmark (1/2)', '${{ steps.run-feature-1.outcome }}'], ['running baseline benchmark (1/2)', '${{ steps.run-baseline-1.outcome }}'], ...(abba ? [['running baseline benchmark (2/2)', '${{ steps.run-baseline-2.outcome }}']] : []), @@ -1378,8 +1381,8 @@ jobs: const bigBlocks = process.env.BENCH_BIG_BLOCKS === 'true'; const steps_status = [ ...(bigBlocks ? [['validating local big-block data', '${{ steps.big-blocks-check.outcome }}']] : []), - ['validating local snapshot', '${{ steps.snapshot-check.outcome }}'], ['building binaries', '${{ steps.build.outcome }}'], + ['syncing snapshot', '${{ steps.snapshot-check.outcome }}'], ['running feature benchmark (1/2)', '${{ steps.run-feature-1.outcome }}'], ['running baseline benchmark (1/2)', '${{ steps.run-baseline-1.outcome }}'], ...(abba ? [['running baseline benchmark (2/2)', '${{ steps.run-baseline-2.outcome }}']] : []), From b5f32d4d6600ee93b6d6d253ac7f4a21a243126c Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Tue, 5 May 2026 17:31:13 +0200 Subject: [PATCH 033/335] fix(bench): skip big-block snapshot downloads (#23992) Co-authored-by: Brian Picciano <933154+mediocregopher@users.noreply.github.com> Co-authored-by: Amp --- .github/scripts/bench-reth-snapshot.sh | 39 +++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/.github/scripts/bench-reth-snapshot.sh b/.github/scripts/bench-reth-snapshot.sh index 7672c2a1980..ca29801e1a9 100755 --- a/.github/scripts/bench-reth-snapshot.sh +++ b/.github/scripts/bench-reth-snapshot.sh @@ -15,10 +15,10 @@ # # Optional env: # BENCH_BIG_BLOCKS - true when syncing the big-blocks datadir +# BENCH_SNAPSHOT_NAME - expected snapshot label for log/error output set -euo pipefail : "${SCHELK_MOUNT:?SCHELK_MOUNT must be set}" -: "${BENCH_SNAPSHOT_MANIFEST_URL:?BENCH_SNAPSHOT_MANIFEST_URL must be set}" if [ "${1:-}" != "" ] && [ "${1:-}" != "--check" ]; then echo "Usage: $0 [--check]" @@ -37,6 +37,40 @@ fi DATADIR="$SCHELK_MOUNT/$DATADIR_NAME" LOCAL_MANIFEST="$DATADIR/manifest.json" +describe_snapshot() { + if [ -n "${BENCH_SNAPSHOT_NAME:-}" ]; then + printf '%s' "${BENCH_SNAPSHOT_NAME}" + elif [ "${BENCH_BIG_BLOCKS:-false}" = "true" ]; then + printf '%s' 'big-block weekly snapshot' + else + printf '%s' 'benchmark snapshot' + fi +} + +EXPECTED_SNAPSHOT="$(describe_snapshot)" + +sudo schelk recover -y --kill || sudo schelk full-recover -y || true +sudo schelk mount -y + +if [ "${BENCH_BIG_BLOCKS:-false}" = "true" ]; then + if [ -d "$DATADIR/db" ] && [ -d "$DATADIR/static_files" ]; then + echo "Found local ${EXPECTED_SNAPSHOT} at ${DATADIR}; skipping manifest sync for big-block benchmarks" + exit 0 + fi + + echo "::error::Missing local ${EXPECTED_SNAPSHOT} at ${DATADIR}. Big-block benchmarks require a pre-populated schelk data volume." + ls -la "$SCHELK_MOUNT" || true + ls -la "$DATADIR" || true + + if [ "$CHECK_ONLY" = true ]; then + exit 10 + fi + + exit 1 +fi + +: "${BENCH_SNAPSHOT_MANIFEST_URL:?BENCH_SNAPSHOT_MANIFEST_URL must be set}" + MANIFEST_URL="$BENCH_SNAPSHOT_MANIFEST_URL" MANIFEST_BASE_URL="${MANIFEST_URL%/*}" @@ -71,9 +105,6 @@ if ! jq -S . "$REMOTE_MANIFEST" > "$REMOTE_CANONICAL"; then fi REMOTE_HASH="$(sha256_file "$REMOTE_CANONICAL")" -sudo schelk recover -y --kill || sudo schelk full-recover -y || true -sudo schelk mount -y - LOCAL_HASH="" LOCAL_MATCH=false if [ -f "$LOCAL_MANIFEST" ] && jq -S . "$LOCAL_MANIFEST" > "$LOCAL_CANONICAL"; then From a9c85ad97ccd4b6cc630f4f5b68dea08bea5bb25 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Tue, 5 May 2026 17:40:54 +0200 Subject: [PATCH 034/335] fix: disable OTLP by default in bench workflow (#23994) Co-authored-by: Brian Picciano <933154+mediocregopher@users.noreply.github.com> Co-authored-by: Amp --- .github/workflows/bench.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 0088bad730c..df9ec9ee49e 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -89,7 +89,7 @@ on: otlp: description: "Export OTLP traces and logs" required: false - default: "true" + default: "false" type: boolean env: @@ -176,7 +176,7 @@ jobs: bigBlocks = '${{ github.event.inputs.big_blocks }}' === 'true' ? 'true' : 'false'; bal = '${{ github.event.inputs.bal }}' || 'false'; var abba = '${{ github.event.inputs.abba }}' !== 'false' ? 'true' : 'false'; - var otlp = '${{ github.event.inputs.otlp }}' !== 'false' ? 'true' : 'false'; + var otlp = '${{ github.event.inputs.otlp }}' === 'true' ? 'true' : 'false'; var waitTime = '${{ github.event.inputs.wait_time }}' || ''; var baselineNodeArgs = '${{ github.event.inputs.baseline_args }}' || ''; var featureNodeArgs = '${{ github.event.inputs.feature_args }}' || ''; @@ -202,11 +202,11 @@ jobs: const intArgs = new Set(['warmup', 'cores', 'blocks']); const refArgs = new Set(['baseline', 'feature']); const boolArgs = new Set(['samply', 'big-blocks']); - const boolDefaultTrue = new Set(['abba', 'otlp']); + const boolDefaultTrue = new Set(['abba']); const enumArgs = new Map([['bal', validBalModes], ['slack', validSlackModes]]); const durationArgs = new Set(['wait-time']); const stringArgs = new Set(['baseline-args', 'feature-args']); - const defaults = { blocks: '500', warmup: '200', baseline: '', feature: '', samply: 'false', slack: 'always', 'big-blocks': 'false', bal: 'false', cores: '0', abba: 'true', otlp: 'true', 'wait-time': '', 'baseline-args': '', 'feature-args': '' }; + const defaults = { blocks: '500', warmup: '200', baseline: '', feature: '', samply: 'false', slack: 'always', 'big-blocks': 'false', bal: 'false', cores: '0', abba: 'true', otlp: 'false', 'wait-time': '', 'baseline-args': '', 'feature-args': '' }; const unknown = []; const invalid = []; const args = body.replace(/^(?:@decofe|derek) bench\s*/, ''); @@ -620,7 +620,7 @@ jobs: const coresNote = cores && cores !== '0' ? `, cores: \`${cores}\`` : ''; const abbaEnabled = (process.env.BENCH_ABBA || 'true') !== 'false'; const abbaNote = !abbaEnabled ? ', abba: `disabled`' : ''; - const otlpEnabled = (process.env.BENCH_OTLP || 'true') !== 'false'; + const otlpEnabled = (process.env.BENCH_OTLP || 'false') !== 'false'; const otlpNote = !otlpEnabled ? ', otlp: `disabled`' : ''; const waitTimeVal = process.env.BENCH_WAIT_TIME || ''; const waitTimeNote = waitTimeVal ? `, wait-time: \`${waitTimeVal}\`` : ''; From b3262d466fa204667835330af2744372ef646f81 Mon Sep 17 00:00:00 2001 From: figtracer Date: Tue, 5 May 2026 17:27:57 +0100 Subject: [PATCH 035/335] fix(rpc): support block overrides in estimateGas (#23990) Co-authored-by: Matthias Seitz --- crates/rpc/rpc-builder/tests/it/http.rs | 1 + crates/rpc/rpc-eth-api/src/core.rs | 4 +- crates/rpc/rpc-eth-api/src/helpers/call.rs | 4 +- .../rpc/rpc-eth-api/src/helpers/estimate.rs | 40 ++++++++++++------- .../rpc-eth-api/src/helpers/transaction.rs | 12 +++--- 5 files changed, 39 insertions(+), 22 deletions(-) diff --git a/crates/rpc/rpc-builder/tests/it/http.rs b/crates/rpc/rpc-builder/tests/it/http.rs index 120721a82f6..dc904bb98fa 100644 --- a/crates/rpc/rpc-builder/tests/it/http.rs +++ b/crates/rpc/rpc-builder/tests/it/http.rs @@ -319,6 +319,7 @@ where call_request.clone(), Some(block_number.into()), None, + None, ) .await .unwrap_err(); diff --git a/crates/rpc/rpc-eth-api/src/core.rs b/crates/rpc/rpc-eth-api/src/core.rs index e7fde53e5a4..6c884279b63 100644 --- a/crates/rpc/rpc-eth-api/src/core.rs +++ b/crates/rpc/rpc-eth-api/src/core.rs @@ -295,6 +295,7 @@ pub trait EthApi< request: TxReq, block_number: Option, state_override: Option, + block_overrides: Option>, ) -> RpcResult; /// Returns the current price per gas in wei. @@ -773,13 +774,14 @@ where request: RpcTxReq, block_number: Option, state_override: Option, + block_overrides: Option>, ) -> RpcResult { trace!(target: "rpc::eth", ?request, ?block_number, "Serving eth_estimateGas"); Ok(EthCall::estimate_gas_at( self, request, block_number.unwrap_or_default(), - state_override, + EvmOverrides::new(state_override, block_overrides), ) .await?) } diff --git a/crates/rpc/rpc-eth-api/src/helpers/call.rs b/crates/rpc/rpc-eth-api/src/helpers/call.rs index e417943fea8..2ab5bf2d074 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/call.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/call.rs @@ -57,9 +57,9 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA &self, request: RpcTxReq<::Network>, at: BlockId, - state_override: Option, + overrides: EvmOverrides, ) -> impl Future> + Send { - EstimateCall::estimate_gas_at(self, request, at, state_override) + EstimateCall::estimate_gas_at(self, request, at, overrides) } /// `eth_simulateV1` executes an arbitrary number of transactions on top of the requested state. diff --git a/crates/rpc/rpc-eth-api/src/helpers/estimate.rs b/crates/rpc/rpc-eth-api/src/helpers/estimate.rs index 44c573728b4..fccd6572961 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/estimate.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/estimate.rs @@ -2,14 +2,17 @@ use super::{Call, LoadPendingBlock}; use crate::{AsEthApiError, FromEthApiError, IntoEthApiError}; -use alloy_evm::overrides::apply_state_overrides; +use alloy_evm::overrides::{apply_block_overrides, apply_state_overrides}; use alloy_network::TransactionBuilder; use alloy_primitives::{TxKind, U256}; -use alloy_rpc_types_eth::{state::StateOverride, BlockId}; +use alloy_rpc_types_eth::{state::EvmOverrides, BlockId}; use futures::Future; use reth_chainspec::MIN_TRANSACTION_GAS; use reth_errors::ProviderError; -use reth_evm::{ConfigureEvm, Database, Evm, EvmEnvFor, EvmFor, TransactionEnvMut, TxEnvFor}; +use reth_evm::{ + env::BlockEnvironment, ConfigureEvm, Database, Evm, EvmEnvFor, EvmFor, TransactionEnvMut, + TxEnvFor, +}; use reth_revm::{ database::{EvmStateProvider, StateProviderDatabase}, db::{bal::EvmDatabaseError, State}, @@ -49,7 +52,7 @@ pub trait EstimateCall: Call { mut evm_env: EvmEnvFor, mut request: RpcTxReq<::Network>, state: S, - state_override: Option, + overrides: EvmOverrides, ) -> Result where S: EvmStateProvider, @@ -73,6 +76,23 @@ pub trait EstimateCall: Call { // Keep a copy of gas related request values let tx_request_gas_limit = request.as_ref().gas_limit(); let tx_request_gas_price = request.as_ref().gas_price(); + + // Configure the evm env + let mut db = State::builder().with_database(StateProviderDatabase::new(state)).build(); + + // Apply any block overrides before deriving block-derived limits and the tx env so + // overrides for `gasLimit`, `baseFee` and `blobBaseFee` are visible to estimation. + // Mirrors geth's behavior, see: + // + if let Some(block_overrides) = overrides.block { + apply_block_overrides(*block_overrides, &mut db, evm_env.block_env.inner_mut()); + } + + // Apply any state overrides if specified. + if let Some(state_override) = overrides.state { + apply_state_overrides(state_override, &mut db).map_err(Self::Error::from_eth_err)?; + } + // the gas limit of the corresponding block let max_gas_limit = evm_env .cfg_env @@ -96,14 +116,6 @@ pub trait EstimateCall: Call { }) .unwrap_or(max_gas_limit); - // Configure the evm env - let mut db = State::builder().with_database(StateProviderDatabase::new(state)).build(); - - // Apply any state overrides if specified. - if let Some(state_override) = state_override { - apply_state_overrides(state_override, &mut db).map_err(Self::Error::from_eth_err)?; - } - let mut tx_env = self.create_txn_env(&evm_env, request, &mut db)?; // Check if this is a basic transfer (no input data to account with no code) @@ -298,7 +310,7 @@ pub trait EstimateCall: Call { &self, request: RpcTxReq<::Network>, at: BlockId, - state_override: Option, + overrides: EvmOverrides, ) -> impl Future> + Send where Self: LoadPendingBlock, @@ -308,7 +320,7 @@ pub trait EstimateCall: Call { self.spawn_blocking_io_fut(async move |this| { let state = this.state_at_block_id(at).await?; - EstimateCall::estimate_gas_with(&this, evm_env, request, state, state_override) + EstimateCall::estimate_gas_with(&this, evm_env, request, state, overrides) }) .await } diff --git a/crates/rpc/rpc-eth-api/src/helpers/transaction.rs b/crates/rpc/rpc-eth-api/src/helpers/transaction.rs index 77f5e4699c8..d451cfece29 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/transaction.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/transaction.rs @@ -15,7 +15,7 @@ use alloy_dyn_abi::TypedData; use alloy_eips::{eip2718::Encodable2718, BlockId}; use alloy_network::{TransactionBuilder, TransactionBuilder4844}; use alloy_primitives::{Address, Bytes, TxHash, B256, U256}; -use alloy_rpc_types_eth::TransactionInfo; +use alloy_rpc_types_eth::{state::EvmOverrides, TransactionInfo}; use futures::{Future, StreamExt}; use reth_chain_state::CanonStateSubscriptions; use reth_primitives_traits::{ @@ -470,8 +470,9 @@ pub trait EthTransactions: LoadTransaction { let chain_id = self.chain_id(); request.as_mut().set_chain_id(chain_id.to()); - let estimated_gas = - self.estimate_gas_at(request.clone(), BlockId::pending(), None).await?; + let estimated_gas = self + .estimate_gas_at(request.clone(), BlockId::pending(), EvmOverrides::default()) + .await?; let gas_limit = estimated_gas; request.as_mut().set_gas_limit(gas_limit.to()); @@ -533,8 +534,9 @@ pub trait EthTransactions: LoadTransaction { } if request.as_ref().gas_limit().is_none() { - let estimated_gas = - self.estimate_gas_at(request.clone(), BlockId::pending(), None).await?; + let estimated_gas = self + .estimate_gas_at(request.clone(), BlockId::pending(), EvmOverrides::default()) + .await?; request.as_mut().set_gas_limit(estimated_gas.to()); } From b41250350ea9d4c34af519b2dfffea55863838b7 Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 6 May 2026 10:52:24 +0200 Subject: [PATCH 036/335] fix: reth-bench: use per-run bench metrics labels file (#24005) --- .github/scripts/bench-reth-local.sh | 2 +- .github/workflows/bench-scheduled.yml | 2 +- .github/workflows/bench.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/scripts/bench-reth-local.sh b/.github/scripts/bench-reth-local.sh index 2e8a1cc8dcb..1895770c3d1 100755 --- a/.github/scripts/bench-reth-local.sh +++ b/.github/scripts/bench-reth-local.sh @@ -399,7 +399,7 @@ BASELINE_BIN="${BASELINE_SRC}/target/profiling/reth" FEATURE_BIN="${FEATURE_SRC}/target/profiling/reth" # Start metrics proxy (reth → label injection → Prometheus) -LABELS_FILE="/tmp/bench-metrics-labels.json" +LABELS_FILE="$(mktemp "${TMPDIR:-/tmp}/bench-metrics-labels.XXXXXX")" echo '{}' > "$LABELS_FILE" METRICS_SUBNET="${METRICS_SUBNET:-10.10.0.0/24}" METRICS_PORT="${METRICS_PORT:-9090}" diff --git a/.github/workflows/bench-scheduled.yml b/.github/workflows/bench-scheduled.yml index f849b301e93..9261c776194 100644 --- a/.github/workflows/bench-scheduled.yml +++ b/.github/workflows/bench-scheduled.yml @@ -475,7 +475,7 @@ jobs: echo "BENCH_ID=${BENCH_ID}" >> "$GITHUB_ENV" echo "BENCH_REFERENCE_EPOCH=${BENCH_REFERENCE_EPOCH}" >> "$GITHUB_ENV" - LABELS_FILE="/tmp/bench-metrics-labels.json" + LABELS_FILE="$(mktemp "${RUNNER_TEMP:-/tmp}/bench-metrics-labels.XXXXXX")" echo '{}' > "$LABELS_FILE" echo "BENCH_LABELS_FILE=${LABELS_FILE}" >> "$GITHUB_ENV" diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index df9ec9ee49e..8ec4bf8bbc3 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -931,7 +931,7 @@ jobs: echo "BENCH_ID=${BENCH_ID}" >> "$GITHUB_ENV" echo "BENCH_REFERENCE_EPOCH=${BENCH_REFERENCE_EPOCH}" >> "$GITHUB_ENV" - LABELS_FILE="/tmp/bench-metrics-labels.json" + LABELS_FILE="$(mktemp "${RUNNER_TEMP:-/tmp}/bench-metrics-labels.XXXXXX")" echo '{}' > "$LABELS_FILE" echo "BENCH_LABELS_FILE=${LABELS_FILE}" >> "$GITHUB_ENV" From ea31b9ca99dbbfff4d918bd034e525a5bb7beee6 Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 6 May 2026 10:59:09 +0200 Subject: [PATCH 037/335] chore: fix grafana-fetch gh setup-git (#23999) --- .github/workflows/fetch-grafana-dashboard.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/fetch-grafana-dashboard.yml b/.github/workflows/fetch-grafana-dashboard.yml index d6e1bd9ee5d..ffd5b9fd5c9 100644 --- a/.github/workflows/fetch-grafana-dashboard.yml +++ b/.github/workflows/fetch-grafana-dashboard.yml @@ -66,7 +66,9 @@ jobs: git checkout -b "$BRANCH" git add "$TARGET" git commit -m "chore: update Grafana dashboard ${FILENAME}" - git push origin "$BRANCH" + gh auth setup-git + git push --set-upstream origin "$BRANCH" gh pr create \ + --head "$BRANCH" \ --title "chore: update Grafana dashboard ${FILENAME}" \ --body "Automated export from Grafana (dashboard UID: \`${DASHBOARD_UID}\`, target: \`${TARGET}\`)." From 360332258ad5f8fbbdba2d1c400c4b8dc039d96c Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Wed, 6 May 2026 10:01:47 +0100 Subject: [PATCH 038/335] ci(bench): reenable minio binaries cache (#23993) --- .github/scripts/bench-reth-build.sh | 107 +++++++++++++++++++++++--- .github/workflows/bench-scheduled.yml | 8 +- .github/workflows/bench.yml | 8 +- 3 files changed, 109 insertions(+), 14 deletions(-) diff --git a/.github/scripts/bench-reth-build.sh b/.github/scripts/bench-reth-build.sh index 6127f23b765..13e5c171e42 100755 --- a/.github/scripts/bench-reth-build.sh +++ b/.github/scripts/bench-reth-build.sh @@ -1,24 +1,30 @@ #!/usr/bin/env bash # -# Builds reth binaries for benchmarking from local source only. +# Builds (or fetches from cache) reth binaries for benchmarking. # -# Usage: bench-reth-build.sh +# Usage: bench-reth-build.sh [cache-sha] # -# baseline — build the baseline binary at (merge-base) +# baseline — build/fetch the baseline binary at (merge-base) # source-dir must be checked out at -# feature — build the candidate binary + reth-bench at +# feature — build/fetch the candidate binary + reth-bench at # source-dir must be checked out at +# optional cache-sha is the PR head commit for cache key # # Outputs: # baseline: /target/profiling/reth (or reth-bb if BENCH_BIG_BLOCKS=true) # feature: /target/profiling/reth (or reth-bb), reth-bench installed to cargo bin # -# Optional env: BENCH_BIG_BLOCKS (true/false) — build reth-bb instead of reth +# Optional env: +# BENCH_BIG_BLOCKS (true/false) — build reth-bb instead of reth +# BENCH_BINARY_CACHE_ROOT — MinIO cache root (default: minio/reth-binaries) set -euxo pipefail +MC="${MC:-mc}" MODE="$1" SOURCE_DIR="$2" COMMIT="$3" +CACHE_ROOT="${BENCH_BINARY_CACHE_ROOT:-minio/reth-binaries}" +RETH_BENCH_BIN="${CARGO_HOME:-$HOME/.cargo}/bin/reth-bench" BIG_BLOCKS="${BENCH_BIG_BLOCKS:-false}" # The node binary to build: reth-bb for big blocks, reth otherwise @@ -39,6 +45,46 @@ if [ "${BENCH_TRACY:-off}" != "off" ]; then EXTRA_RUSTFLAGS=" -C force-frame-pointers=yes" fi +hash_build_config() { + if command -v sha256sum &>/dev/null; then + sha256sum | cut -c1-12 + else + shasum -a 256 | cut -c1-12 + fi +} + +# Cache suffix: hash of features+rustflags so different build configs get separate cache entries. +if [ -n "$EXTRA_FEATURES" ] || [ -n "$EXTRA_RUSTFLAGS" ]; then + BUILD_SUFFIX="-$(printf '%s' "${EXTRA_FEATURES}${EXTRA_RUSTFLAGS}" | hash_build_config)" +else + BUILD_SUFFIX="" +fi + +# Verify a cached reth binary was built from the expected commit. +# `reth --version` outputs "Commit SHA: " on its own line. +verify_binary() { + local binary="$1" expected_commit="$2" + local version binary_sha + version=$("$binary" --version 2>/dev/null) || return 1 + binary_sha=$(echo "$version" | sed -n 's/^Commit SHA: *//p') + if [ -z "$binary_sha" ]; then + echo "Warning: could not extract commit SHA from version output" + return 1 + fi + if [ "$binary_sha" = "$expected_commit" ]; then + return 0 + fi + echo "Cache mismatch: binary built from ${binary_sha} but expected ${expected_commit}" + return 1 +} + +upload_cache_artifact() { + local source="$1" destination="$2" + if ! $MC cp "$source" "$destination"; then + echo "::warning::Failed to upload binary cache artifact to ${destination}; continuing without remote cache write" + fi +} + # Build the requested node binary with the benchmark profile. build_node_binary() { local features_arg="" @@ -58,19 +104,56 @@ build_node_binary() { case "$MODE" in baseline|main) - echo "Building baseline ${NODE_BIN} (${COMMIT}) from source..." - build_node_binary + BUCKET="${CACHE_ROOT}/${COMMIT}${BUILD_SUFFIX}" + mkdir -p "${SOURCE_DIR}/target/profiling" + + CACHE_VALID=false + if $MC stat --no-list "${BUCKET}/${NODE_BIN}" &>/dev/null; then + echo "Cache hit for baseline (${COMMIT}), downloading ${NODE_BIN}..." + if $MC cp "${BUCKET}/${NODE_BIN}" "${SOURCE_DIR}/target/profiling/${NODE_BIN}" && \ + chmod +x "${SOURCE_DIR}/target/profiling/${NODE_BIN}" && \ + verify_binary "${SOURCE_DIR}/target/profiling/${NODE_BIN}" "${COMMIT}"; then + CACHE_VALID=true + else + echo "Cached baseline binary is stale or download failed, rebuilding..." + fi + fi + if [ "$CACHE_VALID" = false ]; then + echo "Building baseline ${NODE_BIN} (${COMMIT}) from source..." + build_node_binary + upload_cache_artifact "${SOURCE_DIR}/target/profiling/${NODE_BIN}" "${BUCKET}/${NODE_BIN}" + fi ;; feature|branch) - echo "Building feature ${NODE_BIN} (${COMMIT}) from source..." - rustup show active-toolchain || rustup default stable - build_node_binary - make -C "$SOURCE_DIR" install-reth-bench + CACHE_SHA="${4:-$COMMIT}" + BUCKET="${CACHE_ROOT}/${CACHE_SHA}${BUILD_SUFFIX}" + + CACHE_VALID=false + if $MC stat --no-list "${BUCKET}/${NODE_BIN}" &>/dev/null && $MC stat --no-list "${BUCKET}/reth-bench" &>/dev/null; then + echo "Cache hit for ${CACHE_SHA}, downloading binaries..." + mkdir -p "${SOURCE_DIR}/target/profiling" "$(dirname "$RETH_BENCH_BIN")" + if $MC cp "${BUCKET}/${NODE_BIN}" "${SOURCE_DIR}/target/profiling/${NODE_BIN}" && \ + $MC cp "${BUCKET}/reth-bench" "$RETH_BENCH_BIN" && \ + chmod +x "${SOURCE_DIR}/target/profiling/${NODE_BIN}" "$RETH_BENCH_BIN" && \ + verify_binary "${SOURCE_DIR}/target/profiling/${NODE_BIN}" "${COMMIT}"; then + CACHE_VALID=true + else + echo "Cached feature binary is stale or download failed, rebuilding..." + fi + fi + if [ "$CACHE_VALID" = false ]; then + echo "Building feature ${NODE_BIN} (${COMMIT}) from source..." + rustup show active-toolchain || rustup default stable + build_node_binary + make -C "$SOURCE_DIR" install-reth-bench + upload_cache_artifact "${SOURCE_DIR}/target/profiling/${NODE_BIN}" "${BUCKET}/${NODE_BIN}" + upload_cache_artifact "$RETH_BENCH_BIN" "${BUCKET}/reth-bench" + fi ;; *) - echo "Usage: $0 " + echo "Usage: $0 [cache-sha]" exit 1 ;; esac diff --git a/.github/workflows/bench-scheduled.yml b/.github/workflows/bench-scheduled.yml index 9261c776194..2055fe952c1 100644 --- a/.github/workflows/bench-scheduled.yml +++ b/.github/workflows/bench-scheduled.yml @@ -320,6 +320,12 @@ jobs: linux-tools-"$(uname -r)" || \ sudo apt-get install -y --no-install-recommends linux-tools-generic + # mc (MinIO client) + if ! command -v mc &>/dev/null; then + curl -sSfL https://dl.min.io/client/mc/release/linux-amd64/mc -o "$HOME/.local/bin/mc" + chmod +x "$HOME/.local/bin/mc" + fi + # uv (Python package manager) if ! command -v uv &>/dev/null; then curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR="$HOME/.local/bin" sh @@ -347,7 +353,7 @@ jobs: echo "$HOME/.local/bin" >> "$GITHUB_PATH" echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" missing=() - for cmd in schelk cpupower taskset stdbuf python3 curl make uv jq; do + for cmd in mc schelk cpupower taskset stdbuf python3 curl make uv jq; do command -v "$cmd" &>/dev/null || missing+=("$cmd") done if [ ${#missing[@]} -gt 0 ]; then diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 8ec4bf8bbc3..d33adae12be 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -657,6 +657,12 @@ jobs: linux-tools-"$(uname -r)" || \ sudo apt-get install -y --no-install-recommends linux-tools-generic + # mc (MinIO client) + if ! command -v mc &>/dev/null; then + curl -sSfL https://dl.min.io/client/mc/release/linux-amd64/mc -o "$HOME/.local/bin/mc" + chmod +x "$HOME/.local/bin/mc" + fi + # uv (Python package manager) if ! command -v uv &>/dev/null; then curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR="$HOME/.local/bin" sh @@ -691,7 +697,7 @@ jobs: echo "$HOME/.local/bin" >> "$GITHUB_PATH" echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" missing=() - for cmd in schelk cpupower taskset stdbuf python3 curl make uv jq; do + for cmd in mc schelk cpupower taskset stdbuf python3 curl make uv jq; do command -v "$cmd" &>/dev/null || missing+=("$cmd") done if [ ${#missing[@]} -gt 0 ]; then From 39b2c738a84d661a1f09750a582dde951da122d1 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Wed, 6 May 2026 11:10:14 +0100 Subject: [PATCH 039/335] deps: bump libmdbx to v0.13.12 (#24007) --- .../mdbx-sys/libmdbx/CMakeLists.txt | 404 ++----- .../libmdbx-rs/mdbx-sys/libmdbx/COPYRIGHT | 159 +++ .../libmdbx-rs/mdbx-sys/libmdbx/GNUmakefile | 108 +- .../libmdbx-rs/mdbx-sys/libmdbx/NOTICE | 22 +- .../libmdbx-rs/mdbx-sys/libmdbx/VERSION.json | 2 +- .../mdbx-sys/libmdbx/cmake/compiler.cmake | 2 +- .../mdbx-sys/libmdbx/cmake/profile.cmake | 2 +- .../mdbx-sys/libmdbx/cmake/utils.cmake | 212 +--- .../libmdbx-rs/mdbx-sys/libmdbx/config.h.in | 14 +- .../mdbx-sys/libmdbx/man1/mdbx_chk.1 | 2 +- .../mdbx-sys/libmdbx/man1/mdbx_copy.1 | 2 +- .../mdbx-sys/libmdbx/man1/mdbx_drop.1 | 2 +- .../mdbx-sys/libmdbx/man1/mdbx_dump.1 | 2 +- .../mdbx-sys/libmdbx/man1/mdbx_load.1 | 2 +- .../mdbx-sys/libmdbx/man1/mdbx_stat.1 | 2 +- .../libmdbx-rs/mdbx-sys/libmdbx/mdbx.c | 999 +++++++++++------- .../libmdbx-rs/mdbx-sys/libmdbx/mdbx.c++ | 96 +- .../libmdbx-rs/mdbx-sys/libmdbx/mdbx.h | 99 +- .../libmdbx-rs/mdbx-sys/libmdbx/mdbx.h++ | 682 +++++++----- .../libmdbx-rs/mdbx-sys/libmdbx/mdbx_chk.c | 41 +- .../libmdbx-rs/mdbx-sys/libmdbx/mdbx_copy.c | 41 +- .../libmdbx-rs/mdbx-sys/libmdbx/mdbx_drop.c | 41 +- .../libmdbx-rs/mdbx-sys/libmdbx/mdbx_dump.c | 47 +- .../libmdbx-rs/mdbx-sys/libmdbx/mdbx_load.c | 84 +- .../libmdbx-rs/mdbx-sys/libmdbx/mdbx_stat.c | 42 +- .../libmdbx/ut_and_examples/CMakeLists.txt | 39 + .../libmdbx/ut_and_examples/README.md | 11 + .../libmdbx/ut_and_examples/example-mdbx.c | 167 +++ .../libmdbx/ut_and_examples/example-mdbx.c++ | 99 ++ .../ut_and_examples/pcrf/pcrf_simulator.c | 367 +++++++ 30 files changed, 2446 insertions(+), 1346 deletions(-) create mode 100644 crates/storage/libmdbx-rs/mdbx-sys/libmdbx/COPYRIGHT create mode 100644 crates/storage/libmdbx-rs/mdbx-sys/libmdbx/ut_and_examples/CMakeLists.txt create mode 100644 crates/storage/libmdbx-rs/mdbx-sys/libmdbx/ut_and_examples/README.md create mode 100644 crates/storage/libmdbx-rs/mdbx-sys/libmdbx/ut_and_examples/example-mdbx.c create mode 100644 crates/storage/libmdbx-rs/mdbx-sys/libmdbx/ut_and_examples/example-mdbx.c++ create mode 100644 crates/storage/libmdbx-rs/mdbx-sys/libmdbx/ut_and_examples/pcrf/pcrf_simulator.c diff --git a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/CMakeLists.txt b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/CMakeLists.txt index 7d0e3f434cf..9f0a7457555 100644 --- a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/CMakeLists.txt +++ b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/CMakeLists.txt @@ -1,10 +1,12 @@ -# Copyright (c) 2020-2025 Леонид Юрьев aka Leonid Yuriev ############################################### +# Copyright (c) 2020-2026 Леонид Юрьев aka Leonid Yuriev ############################################### # SPDX-License-Identifier: Apache-2.0 # -# Donations are welcome to ETH `0xD104d8f8B2dC312aaD74899F83EBf3EEBDC1EA3A`. Всё будет хорошо! +# Donations are welcome to ETH `0xD104d8f8B2dC312aaD74899F83EBf3EEBDC1EA3A`, +# BTC `bc1qzvl9uegf2ea6cwlytnanrscyv8snwsvrc0xfsu`, SOL `FTCTgbHajoLVZGr8aEFWMzx3NDMyS5wXJgfeMTmJznRi`. +# Всё будет хорошо! -# libmdbx = { Revised and extended descendant of Symas LMDB. } Please see README.md at -# https://gitflic.ru/project/erthink/libmdbx +# libmdbx = { Revised and extended descendant of Symas LMDB. } +# Please see README.md at https://sourcecraft.dev/dqdkfa/libmdbx # # Libmdbx is superior to LMDB in terms of features and reliability, not inferior in performance. libmdbx works on Linux, # FreeBSD, MacOS X and other systems compliant with POSIX.1-2008, but also support Windows as a complementary platform. @@ -12,10 +14,8 @@ # The next version is under active non-public development and will be released as MithrilDB and libmithrildb for # libraries & packages. Admittedly mythical Mithril is resembling silver but being stronger and lighter than steel. # Therefore MithrilDB is rightly relevant name. -# -# MithrilDB will be radically different from libmdbx by the new database format and API based on C++17, as well as the -# Apache 2.0 License. The goal of this revolution is to provide a clearer and robust API, add more features and new -# valuable properties of database. +# MithrilDB will be radically different from libmdbx by the new database format and API based on C++20. +# The goal of this revolution is to provide a clearer and robust API, add more features and new valuable properties. if(CMAKE_VERSION VERSION_LESS 3.8.2) cmake_minimum_required(VERSION 3.0.2) @@ -53,122 +53,12 @@ endif() cmake_policy(SET CMP0054 NEW) -if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/COPYRIGHT" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/NOTICE" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/README.md" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/mdbx.h" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/mdbx.h++" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/alloy.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/api-cold.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/api-copy.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/api-cursor.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/api-dbi.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/api-env.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/api-extra.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/api-key-transform.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/api-misc.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/api-opts.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/api-range-estimate.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/api-txn-data.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/api-txn.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/atomics-ops.h" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/atomics-types.h" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/audit.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/chk.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/cogs.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/cogs.h" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/coherency.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/config.h.in" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/cursor.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/cursor.h" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/dbi.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/dbi.h" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/debug_begin.h" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/debug_end.h" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/dpl.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/dpl.h" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/dxb.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/env.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/essentials.h" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/gc-get.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/gc-put.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/gc.h" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/global.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/internals.h" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/layout-dxb.h" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/layout-lck.h" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/lck-posix.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/lck-windows.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/lck.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/lck.h" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/logging_and_debug.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/logging_and_debug.h" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/man1/mdbx_chk.1" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/man1/mdbx_copy.1" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/man1/mdbx_drop.1" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/man1/mdbx_dump.1" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/man1/mdbx_load.1" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/man1/mdbx_stat.1" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/mdbx.c++" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/meta.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/meta.h" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/mvcc-readers.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/node.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/node.h" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/ntdll.def" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/options.h" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/osal.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/osal.h" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/page-get.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/page-iov.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/page-iov.h" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/page-ops.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/page-ops.h" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/tree-search.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/pnl.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/pnl.h" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/preface.h" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/proto.h" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/refund.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/sort.h" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/spill.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/spill.h" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/table.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/tls.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/tls.h" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/tools/chk.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/tools/copy.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/tools/drop.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/tools/dump.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/tools/load.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/tools/stat.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/tools/wingetopt.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/tools/wingetopt.h" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/tree-ops.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/txl.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/txl.h" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/txn.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/unaligned.h" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/utils.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/utils.h" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/version.c.in" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/walk.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/walk.h" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/windows-import.c" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/windows-import.h" - AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test/CMakeLists.txt") - set(MDBX_AMALGAMATED_SOURCE FALSE) - find_program(GIT git) - if(NOT GIT) - message(SEND_ERROR "Git command-line tool not found") - endif() - set(MDBX_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src") -elseif( - EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/VERSION.json" +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/VERSION.json" + AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/COPYRIGHT" AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE" AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/NOTICE" + AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/README.md" + AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/GNUmakefile" AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/mdbx.c" AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/mdbx.c++" AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/mdbx.h" @@ -186,8 +76,9 @@ elseif( else() message( FATAL_ERROR - "\nThe set of libmdbx source code files is incomplete! " - "Instead just follow the https://libmdbx.dqdkfa.ru/usage.html " "PLEASE, AVOID USING ANY OTHER TECHNIQUES.") + "\nThe set of libmdbx source code files is incomplete!" + " Instead just follow the https://libmdbx.dqdkfa.ru/usage.html" + " PLEASE, AVOID USING ANY OTHER TECHNIQUES.") endif() # Provide version @@ -206,9 +97,6 @@ endif() if(DEFINED PROJECT_NAME AND NOT MDBX_FORCE_BUILD_AS_MAIN_PROJECT) set(SUBPROJECT ON) set(NOT_SUBPROJECT OFF) - if(NOT MDBX_AMALGAMATED_SOURCE AND NOT DEFINED BUILD_TESTING) - set(BUILD_TESTING OFF) - endif() enable_language(C) else() set(SUBPROJECT OFF) @@ -228,25 +116,18 @@ else() endif() project(libmdbx C) - if(NOT MDBX_AMALGAMATED_SOURCE AND NOT DEFINED BUILD_TESTING) - set(BUILD_TESTING ON) - endif() endif() -if(NOT MDBX_AMALGAMATED_SOURCE) +if(NOT ((DEFINED BUILD_TESTING AND NOT BUILD_TESTING) OR (DEFINED MDBX_ENABLE_TESTS AND NOT MDBX_ENABLE_TESTS))) include(CTest) - option(MDBX_ENABLE_TESTS "Build libmdbx tests." ${BUILD_TESTING}) -elseif(DEFINED MDBX_ENABLE_TESTS AND MDBX_ENABLE_TESTS) - message(WARNING "MDBX_ENABLE_TESTS=${MDBX_ENABLE_TESTS}: But amalgamated source code don't includes tests.") - set(MDBX_ENABLE_TESTS OFF) + option(MDBX_ENABLE_TESTS "Build MDBX tests" ${BUILD_TESTING}) endif() # Try to find a C++ compiler unless sure that this is unnecessary. if(NOT CMAKE_CXX_COMPILER_LOADED) include(CheckLanguage) - if(NOT DEFINED MDBX_BUILD_CXX - OR MDBX_BUILD_CXX - OR (NOT MDBX_AMALGAMATED_SOURCE AND (NOT DEFINED MDBX_ENABLE_TESTS OR MDBX_ENABLE_TESTS))) + if(NOT (DEFINED MDBX_BUILD_CXX AND NOT MDBX_BUILD_CXX) + OR NOT (DEFINED MDBX_ENABLE_TESTS AND NOT MDBX_ENABLE_TESTS)) check_language(CXX) if(CMAKE_CXX_COMPILER) enable_language(CXX) @@ -443,68 +324,6 @@ else() set(LTO_ENABLED FALSE) endif() - if(NOT MDBX_AMALGAMATED_SOURCE) - find_program(VALGRIND valgrind) - if(VALGRIND) - # (LY) cmake is ugly and nasty. Therefore memcheck-options should be defined before including ctest. Otherwise - # ctest may ignore it. - set(MEMORYCHECK_SUPPRESSIONS_FILE - "${CMAKE_CURRENT_SOURCE_DIR}/test/valgrind_suppress.txt" - CACHE FILEPATH "Suppressions file for Valgrind" FORCE) - set(MEMORYCHECK_COMMAND_OPTIONS - "--trace-children=yes --leak-check=full --track-origins=yes --track-origins=yes --error-exitcode=42 --error-markers=@ --errors-for-leak-kinds=definite --fair-sched=yes --suppressions=${MEMORYCHECK_SUPPRESSIONS_FILE}" - CACHE STRING "Valgrind options" FORCE) - set(VALGRIND_COMMAND_OPTIONS - "${MEMORYCHECK_COMMAND_OPTIONS}" - CACHE STRING "Valgrind options" FORCE) - endif() - - # Enable 'make tags' target. - find_program(CTAGS ctags) - if(CTAGS) - add_custom_target( - tags - COMMAND ${CTAGS} -R -f tags - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) - add_custom_target(ctags DEPENDS tags) - endif(CTAGS) - - if(UNIX) - find_program(CLANG_FORMAT NAMES clang-format-13 clang-format) - if(CLANG_FORMAT) - execute_process(COMMAND ${CLANG_FORMAT} "--version" OUTPUT_VARIABLE clang_format_version_info) - string(REGEX MATCH "version ([0-9]+)\\.([0-9]+)\\.([0-9]+)(.*)?" clang_format_version_info CLANG_FORMAT_VERSION) - if(clang_format_version_info AND NOT CLANG_FORMAT_VERSION VERSION_LESS 13.0) - # Enable 'make reformat' target. - add_custom_target( - reformat - VERBATIM - COMMAND git ls-files | grep -E \\.\(c|cxx|cc|cpp|h|hxx|hpp\)\(\\.in\)?\$ | xargs ${CLANG_FORMAT} -i - --style=file - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) - endif() - endif() - endif() - - if(NOT "${PROJECT_BINARY_DIR}" STREQUAL "${PROJECT_SOURCE_DIR}") - add_custom_target(distclean) - add_custom_command( - TARGET distclean - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E remove_directory "${PROJECT_BINARY_DIR}" - COMMENT "Removing the build directory and its content") - elseif(IS_DIRECTORY .git AND GIT) - add_custom_target(distclean) - add_custom_command( - TARGET distclean - POST_BUILD - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} - COMMAND ${GIT} submodule foreach --recursive git clean -f -X -d - COMMAND ${GIT} clean -f -X -d - COMMENT "Removing all build files from the source directory") - endif() - endif(NOT MDBX_AMALGAMATED_SOURCE) - set(MDBX_MANAGE_BUILD_FLAGS_DEFAULT ON) endif(SUBPROJECT) @@ -538,7 +357,8 @@ if(NOT DEFINED MDBX_CXX_STANDARD) elseif(CXX_FALLBACK_GNU11 OR CXX_FALLBACK_11) set(MDBX_CXX_STANDARD 11) else() - set(MDBX_CXX_STANDARD 98) + message(NOTICE "The C++ example will be skipped, since C++20 standard is unavailable or C++ API of libmdbx was disabled.") + set(MDBX_CXX_STANDARD 0) endif() endif() @@ -574,9 +394,8 @@ if(WIN32 AND EXISTS "${MDBX_SOURCE_DIR}/ntdll.def") if(MSVC) if(NOT MSVC_LIB_EXE) # Find lib.exe - get_filename_component(CL_NAME ${CMAKE_C_COMPILER} NAME) - string(REPLACE cl.exe lib.exe MSVC_LIB_EXE ${CL_NAME}) - find_program(MSVC_LIB_EXE ${MSVC_LIB_EXE}) + get_filename_component(CC_DIR ${CMAKE_C_COMPILER} DIRECTORY) + find_program(MSVC_LIB_EXE "lib.exe" HINTS ${CC_DIR}) endif() if(MSVC_LIB_EXE) message(STATUS "Found MSVC's lib tool: ${MSVC_LIB_EXE}") @@ -588,25 +407,24 @@ if(WIN32 AND EXISTS "${MDBX_SOURCE_DIR}/ntdll.def") COMMAND ${MSVC_LIB_EXE} /def:"${MDBX_SOURCE_DIR}/ntdll.def" /out:"${MDBX_NTDLL_EXTRA_IMPLIB}" ${INITIAL_CMAKE_STATIC_LINKER_FLAGS}) else() - message(WARNING "MSVC's lib tool not found") + message(WARNING "MSVC's lib.exe not found") endif() elseif(MINGW OR MINGW64) - if(NOT DLLTOOL) - # Find dlltool - get_filename_component(GCC_NAME ${CMAKE_C_COMPILER} NAME) - string(REPLACE gcc dlltool DLLTOOL_NAME ${GCC_NAME}) - find_program(DLLTOOL NAMES ${DLLTOOL_NAME}) + if(NOT MINGW_DLLTOOL_EXE) + # Find dlltool.exe + get_filename_component(CC_DIR ${CMAKE_C_COMPILER} DIRECTORY) + find_program(MINGW_DLLTOOL_EXE "dlltool.exe" HINTS ${CC_DIR}) endif() - if(DLLTOOL) - message(STATUS "Found dlltool: ${DLLTOOL}") + if(MINGW_DLLTOOL_EXE) + message(STATUS "Found MINGW's dlltool: ${MINGW_DLLTOOL_EXE}") set(MDBX_NTDLL_EXTRA_IMPLIB "${CMAKE_CURRENT_BINARY_DIR}/mdbx_ntdll_extra.a") add_custom_command( OUTPUT "${MDBX_NTDLL_EXTRA_IMPLIB}" COMMENT "Create extra-import-library for ntdll.dll" MAIN_DEPENDENCY "${MDBX_SOURCE_DIR}/ntdll.def" - COMMAND ${DLLTOOL} -d "${MDBX_SOURCE_DIR}/ntdll.def" -l "${MDBX_NTDLL_EXTRA_IMPLIB}") + COMMAND ${MINGW_DLLTOOL_EXE} -d "${MDBX_SOURCE_DIR}/ntdll.def" -l "${MDBX_NTDLL_EXTRA_IMPLIB}") else() - message(WARNING "dlltool not found") + message(WARNING "MINGW's dlltool.exe not found") endif() endif() @@ -688,8 +506,7 @@ add_option(MDBX TRUST_RTC "Does a system have battery-backed Real-Time Clock or mark_as_advanced(MDBX_TRUST_RTC) add_option(MDBX FORCE_ASSERTIONS "Force enable assertion checking" OFF) add_option( - MDBX - DISABLE_VALIDATION + MDBX DISABLE_VALIDATION "Disable some checks to reduce an overhead and detection probability of database corruption to a values closer to the LMDB" OFF) mark_as_advanced(MDBX_DISABLE_VALIDATION) @@ -702,15 +519,8 @@ mark_as_advanced(MDBX_ENABLE_PROFGC) add_option(MDBX ENABLE_DBI_SPARSE "Support for sparse sets of DBI handles to reduce overhead when starting and processing transactions" ON) add_option(MDBX ENABLE_DBI_LOCKFREE "Support for deferred releasing and a lockfree path to quickly open DBI handles" ON) - -if(NOT MDBX_AMALGAMATED_SOURCE) - if(CMAKE_CONFIGURATION_TYPES OR CMAKE_BUILD_TYPE_UPPERCASE STREQUAL "DEBUG") - set(MDBX_ALLOY_BUILD_DEFAULT OFF) - else() - set(MDBX_ALLOY_BUILD_DEFAULT ON) - endif() - add_option(MDBX ALLOY_BUILD "Build MDBX library through single/alloyed object file" ${MDBX_ALLOY_BUILD_DEFAULT}) -endif() +add_option(MDBX USE_FALLOCATE "Using posix_fallocate() or fcntl(F_PREALLOCATE) on OSX" AUTO) +mark_as_advanced(MDBX_USE_FALLOCATE) if((MDBX_BUILD_TOOLS OR MDBX_ENABLE_TESTS) AND MDBX_BUILD_SHARED_LIBRARY) add_option(MDBX LINK_TOOLS_NONSTATIC "Link MDBX tools with non-static libmdbx" OFF) @@ -718,23 +528,34 @@ else() unset(MDBX_LINK_TOOLS_NONSTATIC CACHE) endif() +set(MDBX_CXX_AVAILABLE FALSE) if(CMAKE_CXX_COMPILER_LOADED - AND MDBX_CXX_STANDARD LESS 83 AND NOT MDBX_CXX_STANDARD LESS 11) - if(NOT MDBX_AMALGAMATED_SOURCE) - option(MDBX_ENABLE_TESTS "Build MDBX tests" ${BUILD_TESTING}) - endif() if(NOT MDBX_WITHOUT_MSVC_CRT AND NOT (CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.8) AND NOT (CMAKE_COMPILER_IS_CLANG AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.9) AND NOT (MSVC AND MSVC_VERSION LESS 1900)) + set(MDBX_CXX_AVAILABLE TRUE) option(MDBX_BUILD_CXX "Build C++ portion" ON) else() + if(DEFINED MDBX_BUILD_CXX AND MDBX_BUILD_CXX) + if(MDBX_WITHOUT_MSVC_CRT) + message( + WARNING + "MDBX_BUILD_CXX=${MDBX_BUILD_CXX}: But MDBX_WITHOUT_MSVC_CRT=${MDBX_WITHOUT_MSVC_CRT} which does not allow the use of C++." + ) + else() + message(WARNING "MDBX_BUILD_CXX=${MDBX_BUILD_CXX}: But there is no suitable C++ compiler available.") + endif() + endif() set(MDBX_BUILD_CXX FALSE) endif() else() - set(MDBX_BUILD_CXX FALSE) - set(MDBX_ENABLE_TESTS FALSE) + if(DEFINED MDBX_BUILD_CXX AND MDBX_BUILD_CXX) + message(WARNING "MDBX_BUILD_CXX=${MDBX_BUILD_CXX}: But there is no suitable C++11 compiler available.") + endif() + set(MDBX_ENABLE_TESTS OFF) + set(MDBX_BUILD_CXX OFF) endif() if(CI) @@ -743,8 +564,8 @@ endif() # ###################################################################################################################### -if(MDBX_BUILD_CXX AND NOT CMAKE_CXX_COMPILER_LOADED) - message(FATAL_ERROR "MDBX_BUILD_CXX=${MDBX_BUILD_CXX}: The C++ compiler is required to build the C++API.") +if(MDBX_BUILD_CXX AND NOT MDBX_CXX_AVAILABLE) + message(FATAL_ERROR "MDBX_BUILD_CXX=${MDBX_BUILD_CXX}: The C++ 11 compiler is required to build the C++API.") endif() if(MDBX_BUILD_CXX) @@ -754,107 +575,16 @@ endif() # sources list set(LIBMDBX_PUBLIC_HEADERS mdbx.h) -set(LIBMDBX_SOURCES mdbx.h "${CMAKE_CURRENT_BINARY_DIR}/config.h") +set(LIBMDBX_SOURCES mdbx.h "${CMAKE_CURRENT_BINARY_DIR}/config-cmake.h") if(MDBX_AMALGAMATED_SOURCE) list(APPEND LIBMDBX_SOURCES mdbx.c) else() - # generate version file - configure_file("${MDBX_SOURCE_DIR}/version.c.in" "${CMAKE_CURRENT_BINARY_DIR}/version.c" ESCAPE_QUOTES) - file(SHA256 "${CMAKE_CURRENT_BINARY_DIR}/version.c" MDBX_SOURCERY_DIGEST) - string(MAKE_C_IDENTIFIER "${MDBX_GIT_DESCRIBE}" MDBX_SOURCERY_SUFFIX) - set(MDBX_BUILD_SOURCERY "${MDBX_SOURCERY_DIGEST}_${MDBX_SOURCERY_SUFFIX}") - - if(MDBX_ALLOY_BUILD) - list(APPEND LIBMDBX_SOURCES "${MDBX_SOURCE_DIR}/alloy.c") - include_directories("${MDBX_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}") - else() - list( - APPEND - LIBMDBX_SOURCES - "${MDBX_SOURCE_DIR}/api-cold.c" - "${MDBX_SOURCE_DIR}/api-copy.c" - "${MDBX_SOURCE_DIR}/api-cursor.c" - "${MDBX_SOURCE_DIR}/api-dbi.c" - "${MDBX_SOURCE_DIR}/api-env.c" - "${MDBX_SOURCE_DIR}/api-extra.c" - "${MDBX_SOURCE_DIR}/api-key-transform.c" - "${MDBX_SOURCE_DIR}/api-misc.c" - "${MDBX_SOURCE_DIR}/api-opts.c" - "${MDBX_SOURCE_DIR}/api-range-estimate.c" - "${MDBX_SOURCE_DIR}/api-txn-data.c" - "${MDBX_SOURCE_DIR}/api-txn.c" - "${MDBX_SOURCE_DIR}/atomics-ops.h" - "${MDBX_SOURCE_DIR}/atomics-types.h" - "${MDBX_SOURCE_DIR}/audit.c" - "${MDBX_SOURCE_DIR}/chk.c" - "${MDBX_SOURCE_DIR}/cogs.c" - "${MDBX_SOURCE_DIR}/cogs.h" - "${MDBX_SOURCE_DIR}/coherency.c" - "${MDBX_SOURCE_DIR}/cursor.c" - "${MDBX_SOURCE_DIR}/cursor.h" - "${MDBX_SOURCE_DIR}/dbi.c" - "${MDBX_SOURCE_DIR}/dbi.h" - "${MDBX_SOURCE_DIR}/dpl.c" - "${MDBX_SOURCE_DIR}/dpl.h" - "${MDBX_SOURCE_DIR}/dxb.c" - "${MDBX_SOURCE_DIR}/env.c" - "${MDBX_SOURCE_DIR}/essentials.h" - "${MDBX_SOURCE_DIR}/gc-get.c" - "${MDBX_SOURCE_DIR}/gc-put.c" - "${MDBX_SOURCE_DIR}/gc.h" - "${MDBX_SOURCE_DIR}/global.c" - "${MDBX_SOURCE_DIR}/internals.h" - "${MDBX_SOURCE_DIR}/layout-dxb.h" - "${MDBX_SOURCE_DIR}/layout-lck.h" - "${MDBX_SOURCE_DIR}/lck.c" - "${MDBX_SOURCE_DIR}/lck.h" - "${MDBX_SOURCE_DIR}/logging_and_debug.c" - "${MDBX_SOURCE_DIR}/logging_and_debug.h" - "${MDBX_SOURCE_DIR}/meta.c" - "${MDBX_SOURCE_DIR}/meta.h" - "${MDBX_SOURCE_DIR}/mvcc-readers.c" - "${MDBX_SOURCE_DIR}/node.c" - "${MDBX_SOURCE_DIR}/node.h" - "${MDBX_SOURCE_DIR}/options.h" - "${MDBX_SOURCE_DIR}/osal.c" - "${MDBX_SOURCE_DIR}/osal.h" - "${MDBX_SOURCE_DIR}/page-get.c" - "${MDBX_SOURCE_DIR}/page-iov.c" - "${MDBX_SOURCE_DIR}/page-iov.h" - "${MDBX_SOURCE_DIR}/page-ops.c" - "${MDBX_SOURCE_DIR}/page-ops.h" - "${MDBX_SOURCE_DIR}/tree-search.c" - "${MDBX_SOURCE_DIR}/pnl.c" - "${MDBX_SOURCE_DIR}/pnl.h" - "${MDBX_SOURCE_DIR}/preface.h" - "${MDBX_SOURCE_DIR}/proto.h" - "${MDBX_SOURCE_DIR}/refund.c" - "${MDBX_SOURCE_DIR}/sort.h" - "${MDBX_SOURCE_DIR}/spill.c" - "${MDBX_SOURCE_DIR}/spill.h" - "${MDBX_SOURCE_DIR}/table.c" - "${MDBX_SOURCE_DIR}/tls.c" - "${MDBX_SOURCE_DIR}/tls.h" - "${MDBX_SOURCE_DIR}/tree-ops.c" - "${MDBX_SOURCE_DIR}/txl.c" - "${MDBX_SOURCE_DIR}/txl.h" - "${MDBX_SOURCE_DIR}/txn.c" - "${MDBX_SOURCE_DIR}/unaligned.h" - "${MDBX_SOURCE_DIR}/utils.c" - "${MDBX_SOURCE_DIR}/utils.h" - "${MDBX_SOURCE_DIR}/walk.c" - "${MDBX_SOURCE_DIR}/walk.h" - "${CMAKE_CURRENT_BINARY_DIR}/version.c") - if(NOT MSVC) - list(APPEND LIBMDBX_SOURCES "${MDBX_SOURCE_DIR}/lck-posix.c") - endif() - if(NOT APPLE) - list(APPEND LIBMDBX_SOURCES "${MDBX_SOURCE_DIR}/windows-import.h" "${MDBX_SOURCE_DIR}/windows-import.c" - "${MDBX_SOURCE_DIR}/lck-windows.c") - endif() - include_directories("${MDBX_SOURCE_DIR}") + if(NOT CMAKE_CURRENT_LIST_FILE) + set(CMAKE_CURRENT_LIST_FILE "CMakeLists.txt") endif() + message(FATAL_ERROR "This \"${CMAKE_CURRENT_LIST_FILE}\" has been trimmed and is therefore intended only for build from an amalgamated form of the source code.") endif(MDBX_AMALGAMATED_SOURCE) + if(MDBX_BUILD_CXX) message(STATUS "Use C${MDBX_C_STANDARD} and C++${MDBX_CXX_STANDARD} for libmdbx") list(APPEND LIBMDBX_PUBLIC_HEADERS mdbx.h++) @@ -938,8 +668,10 @@ target_setup_options(mdbx-static) libmdbx_setup_libs(mdbx-static INTERFACE) if(MDBX_BUILD_SHARED_LIBRARY) set_target_properties(mdbx-static PROPERTIES OUTPUT_NAME mdbx-static) + set(MDBX_LIBRARY mdbx) else() set_target_properties(mdbx-static PROPERTIES OUTPUT_NAME mdbx) + set(MDBX_LIBRARY mdbx-static) endif() target_include_directories(mdbx-static INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}") @@ -1241,17 +973,15 @@ foreach(item IN LISTS options) message(STATUS "${item}: ${value}") endforeach(item) -# provide config.h for library build info -configure_file("${MDBX_SOURCE_DIR}/config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/config.h" ESCAPE_QUOTES) -add_definitions(-DMDBX_CONFIG_H="${CMAKE_CURRENT_BINARY_DIR}/config.h") +# provide config-cmake.h for library build info +file(REMOVE "${MDBX_SOURCE_DIR}/config-cmake.h") +configure_file("${MDBX_SOURCE_DIR}/config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/config-cmake.h" ESCAPE_QUOTES) +add_definitions(-DMDBX_CONFIG_H="${CMAKE_CURRENT_BINARY_DIR}/config-cmake.h") # ###################################################################################################################### -if(NOT MDBX_AMALGAMATED_SOURCE AND MDBX_ENABLE_TESTS) - if(NOT CMAKE_CXX_COMPILER_LOADED) - message(FATAL_ERROR "MDBX_ENABLE_TESTS=${MDBX_ENABLE_TESTS}: The C++ compiler is required to build the tests.") - endif() - add_subdirectory(test) +if(MDBX_ENABLE_TESTS) + add_subdirectory(ut_and_examples) endif() # ###################################################################################################################### diff --git a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/COPYRIGHT b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/COPYRIGHT new file mode 100644 index 00000000000..09de19ba504 --- /dev/null +++ b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/COPYRIGHT @@ -0,0 +1,159 @@ +Copyright (c) 2015-2026 Леонид Юрьев aka Leonid Yuriev + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +----------------------------------------------------------------------- + +СМЕНА ЛИЦЕНЗИИ (THE LICENSE CHANGE) + +OpenLDAP Public License → Apache 2.0 + +Briefly: + Historically, in 2015 an early MDBX source code was derived from the + "LMDB engine" created by Howard Chu in 2011-2015, + which based on btree.c written by Martin Hedenfalk . + + By 2024, MDBX source code has actually been rewritten and has so + little in common with the original LMDB that I thought it admissible + to change the license. Below are more detailed explanations. + +Кратко: + + Исторически в 2015 году ранний исходный код MDBX был заимствован из + «LMDB engine», созданной Howard Chu в 2011-2015, + на основе btree.c, ранее созданного Martin Hedenfalk . + + К 2024 году исходный код MDBX фактически переписан и имеет настолько + мало общего с первоначальным заимствованием из LMDB, что я счел + уместным сменить лицензию. Ниже более подробные пояснения. + +--- + +Первоисточник текста формулирован на Русском языке, который является +родным для автора. Предполагается что все заинтересованные могут легко +воспользоваться машинным переводом, который при всех недостатках сможет +донести суть, намерения и местами даже передать тональность. + +The original source of this text is in Russian, which is the author's +native language. It is assumed that all concerned can easily use machine +translation, which, with all the disadvantages, will be able to convey +the essence, intentions and, in some places, even convey the tonality of +a wording. + +1. Причины + +1.1. Лицензия Apache-2.0 является одной из самых популярных, так как +содержит ряд уточнений, проясняющих и упрощающих использование исходного +кода в производных работах и больших проектах. Эти особенности лицензии +Apache-2.0 я нахожу достаточно ценными и удобными. Соответственно, +переход на лицензию Apache-2.0 полезным в целом. + +1.2. Проект OpenLDAP имеет определенную известность, в том числе, к +сожалению, среди специалистов славится кране плохим качеством кода и +сбоями при отходе от простых/базовых сценариев использования. Поэтому +использование лицензии OpenLDAP, в глазах части аудитории, бросает тень +на качества кода libmdbx, несмотря на то, что исходный код библиотеки +переписан, в том числе, с целью повышения качества, надежности, +стабильности и пригодности к тестированию. + +Отмечу, что здесь не место для обсуждения объективности подобных мнений +и причин, равно как и не место для оценки компетентности специалистов +высказывающих такие суждения. Однако, здесь необходимо озвучить сам факт +наличия такой негативной коннотации качества кода при упоминании +OpenLDAP, совершенно без намерения как-либо задеть или обидеть +контрибьюторов OpenLDAP. + +1.3. С точки зрения исходного кода, к настоящему времени libmdbx стала +совсем другим продуктом, о котором сейчас правильнее сказать что +разработка вдохновлена LMDB, нежели основывается на заимствовании кода. +Смена лицензии на переписанный код подчеркивает, что это действительно +новый исходный код. + +2. Легитимность + +2.1. Исходная лицензия OpenLDAP 2.8 и актуальная лицензия Apache 2.0 +совпадают по базовым условиям. При этом лицензия Apache 2.0 уточняет, +определяет и проясняет многие аспекты. Поэтому смену лицензии я склонен +трактовать как уточнение, но НЕ как принципиальное изменение, которое +могло-бы нарушить чьи-либо права. + +2.2. С процедурной точки зрения, у меня есть право сменить лицензию на +новый, написанный мной, исходный код. При этом объективно существует как +техническая, так и юридическая проблемы отделения «нового кода» от +«заимствованного», а также выделение/классификация кода, который +является общественным достоянием и/или общеупотребительным воплощением +«математических моделей и других публичных знаний». + +Основываясь на собственной субъективной оценке кодовой базы, включая +соотношения «нового», «заимствованного» и «общеупотребительного» +исходного кода, я считаю что смена лицензии допустима. Одновременно с +этим, я понимаю и признаю, что можно найти повод, чтобы трактовать +ситуацию как «стакан наполовину полон/пуст». Поэтому декларирую +готовность принимать претензии и устранять их путем полного +переписывания оставшегося исходного кода, который попадает под критерии +«заимствованного» и кто-то из контрибьюторов которого будет против +изменения лицензии. + +2.3. Вне зависимости от истории происхождения каждой строки исходного +кода и её буквального авторства, прошу не считать производимую смену +лицензии, и связанных с этим технических действий, как попытку плагиата, +присвоения чужого труда, присвоения авторства или принижения вклада +других авторов/контрибьторов. Безусловно проект MDBX/libmdbx не появился +бы без LMDB и всех участников проекта LMDB, в особенности Говарда Чу +(Howard Chu), Холлварда Фурусет (Hallvard Furuseth) и Мартина Хеденфок +(Martin Hedenfalk). Как-бы исходный код не переписывался он всё равно +будет основываться на базовых идеях и включать основные концепции LMDB. + +3. Последствия и актуальные требования + +Всё очень просто. Потребуется обеспечить требования новой лицензии в +соответствии с 4-м пунктом лицензции Apache 2.0. + +В частности, при использовании/распространении libmdbx потребуется +обеспечить наличие файлов с текстом лицензии и файла NOTICE, а также +обеспечить пользователям возможность ознакомиться с их содержимым в +работах/продуктах использующих libmdbx. + +----------------------------------------------------------------------- + +Далее в справочных целях приведены уведомления об авторских правах из +первоначально заимствованного кода. + +--- + +Original source code was derived from LMDB in 2015, +and later evolutionarily rewritten in 2015-2024: +Copyright (c) 2011-2015 Howard Chu, Symas Corp. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted only as authorized by the OpenLDAP +Public License. + +A copy of this license is available in the file LICENSE in the +top-level directory of the distribution or, alternatively, at +. + +LMDB itself devived code from btree.c written by Martin Hedenfalk: +Copyright (c) 2009, 2010 Martin Hedenfalk + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/GNUmakefile b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/GNUmakefile index 32cfc9a05e6..f40b43dbc05 100644 --- a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/GNUmakefile +++ b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/GNUmakefile @@ -52,7 +52,7 @@ CC ?= gcc CXX ?= g++ CFLAGS_EXTRA ?= LD ?= ld -CMAKE ?= cmake +CMAKE ?= "$(shell which cmake 2>&-)" CMAKE_OPT ?= CTEST ?= ctest CTEST_OPT ?= @@ -78,12 +78,12 @@ ifneq ($(make_ge_4_1),1) # don't use variable expansion trick as workaround for bugs of GNU Make before 4.1 LIBS ?= $(shell $(uname2libs)) LDFLAGS ?= $(shell $(uname2ldflags)) -LIB_STDCXXFS ?= $(shell echo '$(cxx_filesystem_probe)' | cat mdbx.h++ - | sed $$'1s/\xef\xbb\xbf//' | $(CXX) -x c++ $(CXXFLAGS) -Wno-error - -Wl,--allow-multiple-definition -lstdc++fs $(LIBS) $(LDFLAGS) $(EXE_LDFLAGS) -o /dev/null 2>probe4lstdfs.err >/dev/null && echo '-Wl,--allow-multiple-definition -lstdc++fs') +LIB_STDCXXFS ?= $(shell echo '$(cxx_filesystem_probe)' | cat mdbx.h++ - | sed $$'1s/\xef\xbb\xbf//' | grep -v 'pragma once' | $(CXX) -x c++ $(CXXFLAGS) -Wno-error - -Wl,--allow-multiple-definition -lstdc++fs $(LIBS) $(LDFLAGS) $(EXE_LDFLAGS) -o /dev/null 2>probe4lstdfs.err >/dev/null && echo '-Wl,--allow-multiple-definition -lstdc++fs') else # using variable expansion trick to avoid repeaded probes LIBS ?= $(eval LIBS := $$(shell $$(uname2libs)))$(LIBS) LDFLAGS ?= $(eval LDFLAGS := $$(shell $$(uname2ldflags)))$(LDFLAGS) -LIB_STDCXXFS ?= $(eval LIB_STDCXXFS := $$(shell echo '$$(cxx_filesystem_probe)' | cat mdbx.h++ - | sed $$$$'1s/\xef\xbb\xbf//' | $(CXX) -x c++ $(CXXFLAGS) -Wno-error - -Wl,--allow-multiple-definition -lstdc++fs $(LIBS) $(LDFLAGS) $(EXE_LDFLAGS) -o /dev/null 2>probe4lstdfs.err >/dev/null && echo '-Wl,--allow-multiple-definition -lstdc++fs'))$(LIB_STDCXXFS) +LIB_STDCXXFS ?= $(eval LIB_STDCXXFS := $$(shell echo '$$(cxx_filesystem_probe)' | cat mdbx.h++ - | sed $$$$'1s/\xef\xbb\xbf//' | grep -v '#pragma once' | $(CXX) -x c++ $(CXXFLAGS) -Wno-error - -Wl,--allow-multiple-definition -lstdc++fs $(LIBS) $(LDFLAGS) $(EXE_LDFLAGS) -o /dev/null 2>probe4lstdfs.err >/dev/null && echo '-Wl,--allow-multiple-definition -lstdc++fs'))$(LIB_STDCXXFS) endif ifneq ($(make_ge_4_4),1) @@ -106,11 +106,12 @@ endef define uname2ldflags case "$(UNAME)" in CYGWIN*|MINGW*|MSYS*|Windows*) - echo '-Wl,--gc-sections,-O1'; + echo '-Wl,--gc-sections,-O1,--as-needed'; ;; *) $(LD) --help 2>/dev/null | grep -q -- --gc-sections && echo '-Wl,--gc-sections,-z,relro,-O1'; $(LD) --help 2>/dev/null | grep -q -- -dead_strip && echo '-Wl,-dead_strip'; + $(LD) --help 2>/dev/null | grep -q -- --as-needed && echo '-Wl,--as-needed'; ;; esac endef @@ -141,7 +142,7 @@ MDBX_TOOLS := $(addprefix mdbx_,$(TOOLS)) MANPAGES := mdbx_stat.1 mdbx_copy.1 mdbx_dump.1 mdbx_load.1 mdbx_chk.1 mdbx_drop.1 TIP := // TIP: -.PHONY: all help options lib libs tools clean install uninstall check_buildflags_tag tools-static +.PHONY: all help options lib libs tools clean install uninstall check_buildflags_tag tools-static run-ut .PHONY: install-strip install-no-strip strip libmdbx mdbx show-options lib-static lib-shared cmake-build ninja boolean = $(if $(findstring $(strip $($1)),YES Yes yes y ON On on 1 true True TRUE),1,$(if $(findstring $(strip $($1)),NO No no n OFF Off off 0 false False FALSE),,$(error Wrong value `$($1)` of $1 for YES/NO option))) @@ -191,6 +192,11 @@ help: @echo " make bench-triplet - run ioarena-benchmark for mdbx, lmdb, sqlite3" @echo " make bench-quartet - run ioarena-benchmark for mdbx, lmdb, rocksdb, wiredtiger" @echo " make bench-clean - remove temp database(s) after benchmark" + @echo " make test - basic test(s)" + @echo " make build-test - build test(s) executable(s)" + @echo " make test-asan - build with AddressSanitizer and run basic test" + @echo " make test-leak - build with LeakSanitizer and run basic test" + @echo " make test-ubsan - build with UndefinedBehaviourSanitizer and run basic test" show-options: @echo " MDBX_BUILD_OPTIONS = $(MDBX_BUILD_OPTIONS)" @@ -242,21 +248,20 @@ strip: all $(TRACE )strip libmdbx.$(SO_SUFFIX) $(MDBX_TOOLS) clean: - @echo ' REMOVE ...' + @echo ' CLEANING...' $(QUIET)rm -rf $(MDBX_TOOLS) mdbx_test @* *.[ao] *.[ls]o *.$(SO_SUFFIX) *.dSYM *~ tmp.db/* \ *.gcov *.log *.err src/*.o test/*.o mdbx_example dist @dist-check \ - config.h src/config.h src/version.c *.tar* @buildflags.tag @dist-checked.tag \ + config-gnumake.h src/config-gnumake.h *.tar* @buildflags.tag @dist-checked.tag \ mdbx_*.static mdbx_*.static-lto CMakeFiles MDBX_BUILD_FLAGS =$(strip MDBX_BUILD_CXX=$(MDBX_BUILD_CXX) $(MDBX_BUILD_OPTIONS) $(call select_by,MDBX_BUILD_CXX,$(CXXFLAGS) $(LDFLAGS) $(LIB_STDCXXFS) $(LIBS),$(CFLAGS) $(LDFLAGS) $(LIBS))) check_buildflags_tag: $(QUIET)if [ "$(MDBX_BUILD_FLAGS)" != "$$(cat @buildflags.tag 2>&1)" ]; then \ - echo -n " CLEAN for build with specified flags..." && \ - $(MAKE) IOARENA=false CXXSTD= -s clean >/dev/null && echo " Ok" && \ + echo " TOUCH @buildflags.tag to force re-build with the (new) specified flags..." && \ echo '$(MDBX_BUILD_FLAGS)' > @buildflags.tag; \ fi -@buildflags.tag: check_buildflags_tag +@buildflags.tag: check_buildflags_tag $(WAIT) lib-static libmdbx.a: mdbx-static.o $(call select_by,MDBX_BUILD_CXX,mdbx++-static.o) @echo ' AR $@' @@ -266,9 +271,9 @@ lib-shared libmdbx.$(SO_SUFFIX): mdbx-dylib.o $(call select_by,MDBX_BUILD_CXX,md @echo ' LD $@' $(QUIET)$(call select_by,MDBX_BUILD_CXX,$(CXX) $(CXXFLAGS),$(CC) $(CFLAGS)) $^ -pthread -shared $(LDFLAGS) $(call select_by,MDBX_BUILD_CXX,$(LIB_STDCXXFS)) $(LIBS) -o $@ -ninja-assertions: CMAKE_OPT += -DMDBX_FORCE_ASSERTIONS=ON +ninja-assertions: CMAKE_OPT += -DMDBX_FORCE_ASSERTIONS=ON $(MDBX_BUILD_OPTIONS) ninja-assertions: cmake-build -ninja-debug: CMAKE_OPT += -DCMAKE_BUILD_TYPE=Debug +ninja-debug: CMAKE_OPT += -DCMAKE_BUILD_TYPE=Debug $(MDBX_BUILD_OPTIONS) ninja-debug: cmake-build ninja: cmake-build cmake-build: @@ -279,11 +284,60 @@ ctest: cmake-build @echo " RUN: ctest .." $(QUIET)$(CTEST) --test-dir @cmake-ninja-build --parallel `(nproc | sysctl -n hw.ncpu | echo 2) 2>/dev/null` --schedule-random $(CTEST_OPT) +run-ut: mdbx_example + $(QUIET)for UT in $^; do echo " Running $$UT" && ./$${UT} || exit -1; done + +TEST_TARGETS := mdbx_legacy_example $(call select_by,MDBX_BUILD_CXX,mdbx_modern_example,) +TEST_BUILD_TARGETS := build-test +ifneq ($(CMAKE),"") +TEST_TARGETS += ctest +TEST_BUILD_TARGETS += cmake-build +endif + +.PHONY: ninja-assertions ninja-debug ninja $(TEST_TARGETS) $(TEST_BUILD_TARGETS) test-ubsan test-asan test-memcheck test-leak test-assertion test build-test smoke check +test: $(TEST_TARGETS) +build-test: $(TEST_BUILD_TARGETS) + +test-assertion: MDBX_BUILD_OPTIONS += -DMDBX_FORCE_ASSERTIONS=1 -UNDEBUG -DMDBX_DEBUG=0 +test-assertion: CMAKE_OPT += -DMDBX_FORCE_ASSERTIONS=ON -DMDBX_DEBUG=0 +test-assertion: smoke + +test-valgrind: test-memcheck +test-memcheck: CFLAGS_EXTRA += -Ofast -DENABLE_MEMCHECK +test-memcheck: CMAKE_OPT += -DENABLE_MEMCHECK=ON +test-memcheck: build-test build-stochastic + @echo ' RUNNING `test/stochastic.sh --with-valgrind --loops 2`...' + $(QUIET)test/stochastic.sh --with-valgrind --loops 2 --db-upto-mb 256 --skip-make >$(TEST_LOG) || (cat $(TEST_LOG) && false) + +test-ubsan: + @echo ' RE-TEST with `-fsanitize=undefined` option...' + $(QUIET)$(MAKE) IOARENA=false CXXSTD=$(CXXSTD) CMAKE_OPT="-DENABLE_UBSAN=ON" CFLAGS_EXTRA="-DENABLE_UBSAN -Ofast -fsanitize=undefined -fsanitize-undefined-trap-on-error" build-test test-stochastic + +test-asan: + @echo ' RE-TEST with `-fsanitize=address` option...' + $(QUIET)$(MAKE) IOARENA=false CXXSTD=$(CXXSTD) CMAKE_OPT="-DENABLE_ASAN=ON" CFLAGS_EXTRA="-Os -fsanitize=address" build-test test-stochastic + +test-leak: + @echo ' RE-TEST with `-fsanitize=leak` option...' + $(QUIET)$(MAKE) IOARENA=false CXXSTD=$(CXXSTD) CFLAGS_EXTRA="-fsanitize=leak" test-stochastic + +mdbx_legacy_example: mdbx.h ut_and_examples/example-mdbx.c libmdbx.$(SO_SUFFIX) + @echo ' CC+LD $@' + $(QUIET)$(CC) $(CFLAGS) -I. ut_and_examples/example-mdbx.c ./libmdbx.$(SO_SUFFIX) -o $@ + +mdbx_modern_example: mdbx.h ut_and_examples/example-mdbx.c++ libmdbx.$(SO_SUFFIX) + @echo ' CC+LD $@' + $(QUIET)$(CXX) $(CXXFLAGS) -I. ut_and_examples/example-mdbx.c++ ./libmdbx.$(SO_SUFFIX) -o $@ + ################################################################################ # Amalgamated source code, i.e. distributed after `make dist` MAN_SRCDIR := man1/ -config.h: @buildflags.tag $(WAIT) mdbx.c $(lastword $(MAKEFILE_LIST)) LICENSE NOTICE +dist: + @echo ' Starting 2026 libmdbx is distrubuted in an amalgamated source code form.' + @echo ' So amalgamation is no longer required. Please update your build scripts.' + +config-gnumake.h: @buildflags.tag mdbx.c $(lastword $(MAKEFILE_LIST)) LICENSE NOTICE COPYRIGHT @echo ' MAKE $@' $(QUIET)(echo '#define MDBX_BUILD_TIMESTAMP "$(MDBX_BUILD_TIMESTAMP)"' \ && echo "#define MDBX_BUILD_FLAGS \"$$(cat @buildflags.tag)\"" \ @@ -293,34 +347,36 @@ config.h: @buildflags.tag $(WAIT) mdbx.c $(lastword $(MAKEFILE_LIST)) LICENSE NO && echo '#define MDBX_BUILD_METADATA "$(MDBX_BUILD_METADATA)"' \ ) >$@ -mdbx-dylib.o: config.h mdbx.c mdbx.h $(lastword $(MAKEFILE_LIST)) LICENSE NOTICE +mdbx-dylib.o: config-gnumake.h mdbx.c mdbx.h $(lastword $(MAKEFILE_LIST)) LICENSE NOTICE COPYRIGHT @echo ' CC $@' - $(QUIET)$(CC) $(CFLAGS) $(MDBX_BUILD_OPTIONS) '-DMDBX_CONFIG_H="config.h"' -DLIBMDBX_EXPORTS=1 -c mdbx.c -o $@ + $(QUIET)$(CC) $(CFLAGS) $(MDBX_BUILD_OPTIONS) '-DMDBX_CONFIG_H="config-gnumake.h"' -DLIBMDBX_EXPORTS=1 -c mdbx.c -o $@ -mdbx-static.o: config.h mdbx.c mdbx.h $(lastword $(MAKEFILE_LIST)) LICENSE NOTICE +mdbx-static.o: config-gnumake.h mdbx.c mdbx.h $(lastword $(MAKEFILE_LIST)) LICENSE NOTICE COPYRIGHT @echo ' CC $@' - $(QUIET)$(CC) $(CFLAGS) $(MDBX_BUILD_OPTIONS) '-DMDBX_CONFIG_H="config.h"' -ULIBMDBX_EXPORTS -c mdbx.c -o $@ + $(QUIET)$(CC) $(CFLAGS) $(MDBX_BUILD_OPTIONS) '-DMDBX_CONFIG_H="config-gnumake.h"' -ULIBMDBX_EXPORTS -c mdbx.c -o $@ -mdbx++-dylib.o: config.h mdbx.c++ mdbx.h mdbx.h++ $(lastword $(MAKEFILE_LIST)) LICENSE NOTICE +mdbx++-dylib.o: config-gnumake.h mdbx.c++ $(HEADERS) $(lastword $(MAKEFILE_LIST)) LICENSE NOTICE COPYRIGHT @echo ' CC $@' - $(QUIET)$(CXX) $(CXXFLAGS) $(MDBX_BUILD_OPTIONS) '-DMDBX_CONFIG_H="config.h"' -DLIBMDBX_EXPORTS=1 -c mdbx.c++ -o $@ + $(QUIET)$(CXX) $(CXXFLAGS) $(MDBX_BUILD_OPTIONS) '-DMDBX_CONFIG_H="config-gnumake.h"' -DLIBMDBX_EXPORTS=1 -c mdbx.c++ -o $@ -mdbx++-static.o: config.h mdbx.c++ mdbx.h mdbx.h++ $(lastword $(MAKEFILE_LIST)) LICENSE NOTICE +mdbx++-static.o: config-gnumake.h mdbx.c++ $(HEADERS) $(lastword $(MAKEFILE_LIST)) LICENSE NOTICE COPYRIGHT @echo ' CC $@' - $(QUIET)$(CXX) $(CXXFLAGS) $(MDBX_BUILD_OPTIONS) '-DMDBX_CONFIG_H="config.h"' -ULIBMDBX_EXPORTS -c mdbx.c++ -o $@ + $(QUIET)$(CXX) $(CXXFLAGS) $(MDBX_BUILD_OPTIONS) '-DMDBX_CONFIG_H="config-gnumake.h"' -ULIBMDBX_EXPORTS -c mdbx.c++ -o $@ mdbx_%: mdbx_%.c mdbx-static.o @echo ' CC+LD $@' - $(QUIET)$(CC) $(CFLAGS) $(MDBX_BUILD_OPTIONS) '-DMDBX_CONFIG_H="config.h"' $^ $(EXE_LDFLAGS) $(LIBS) -o $@ + $(QUIET)$(CC) $(CFLAGS) $(MDBX_BUILD_OPTIONS) '-DMDBX_CONFIG_H="config-gnumake.h"' $^ $(LDFLAGS) $(EXE_LDFLAGS) $(LIBS) -o $@ mdbx_%.static: mdbx_%.c mdbx-static.o @echo ' CC+LD $@' - $(QUIET)$(CC) $(CFLAGS) $(MDBX_BUILD_OPTIONS) '-DMDBX_CONFIG_H="config.h"' $^ $(EXE_LDFLAGS) -static -Wl,--strip-all -o $@ + $(QUIET)$(CC) $(CFLAGS) $(MDBX_BUILD_OPTIONS) '-DMDBX_CONFIG_H="config-gnumake.h"' $^ $(LDFLAGS) $(EXE_LDFLAGS) -static -Wl,--strip-all -o $@ -mdbx_%.static-lto: mdbx_%.c config.h mdbx.c mdbx.h +mdbx_%.static-lto: mdbx_%.c config-gnumake.h mdbx.c mdbx.h @echo ' CC+LD $@' - $(QUIET)$(CC) $(CFLAGS) -Os -flto $(MDBX_BUILD_OPTIONS) '-DLIBMDBX_API=' '-DMDBX_CONFIG_H="config.h"' \ - $< mdbx.c $(EXE_LDFLAGS) $(LIBS) -static -Wl,--strip-all -o $@ + $(QUIET)$(CC) $(CFLAGS) -Os -flto $(MDBX_BUILD_OPTIONS) '-DLIBMDBX_API=' '-DMDBX_CONFIG_H="config-gnumake.h"' \ + $< mdbx.c $(LDFLAGS) $(EXE_LDFLAGS) $(LIBS) -static -Wl,--strip-all -o $@ + +check smoke: test install: $(LIBRARIES) $(MDBX_TOOLS) $(HEADERS) @echo ' INSTALLING...' diff --git a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/NOTICE b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/NOTICE index dd58a0b540d..b9201d4ad89 100644 --- a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/NOTICE +++ b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/NOTICE @@ -8,10 +8,11 @@ documentation, C++ API description and links to the original git repo with the source code. Questions, feedback and suggestions are welcome to the Telegram' group https://t.me/libmdbx. -Donations are welcome to the Ethereum/ERC-20 `0xD104d8f8B2dC312aaD74899F83EBf3EEBDC1EA3A`. +Donations are welcome to ETH `0xD104d8f8B2dC312aaD74899F83EBf3EEBDC1EA3A`, +BTC `bc1qzvl9uegf2ea6cwlytnanrscyv8snwsvrc0xfsu`, SOL `FTCTgbHajoLVZGr8aEFWMzx3NDMyS5wXJgfeMTmJznRi`. Всё будет хорошо! -Copyright 2015-2025 Леонид Юрьев aka Leonid Yuriev +Copyright 2015-2026 Леонид Юрьев aka Leonid Yuriev SPDX-License-Identifier: Apache-2.0 For notes about the license change, credits and acknowledgments, please refer to the COPYRIGHT file within libmdbx source. @@ -30,10 +31,13 @@ As a result of what has happened, I will never, under any circumstances, post the primary sources (aka origins) of my projects on Github, or rely in any way on the Github infrastructure. -Nevertheless, realizing that it is more convenient for users of -_libmdbx_ and other my projects to access ones on Github, I do not want -to restrict their freedom or create inconvenience, and therefore I place -mirrors (aka mirrors) of such repositories on Github since 2025. At the -same time, I would like to emphasize once again that these are only -mirrors that can be frozen, blocked or deleted at any time, as was the -case in 2022. +Nonetheless, taking into account that it is more convenient for users of +my projects to access them on Github, I did not want to restrict their +freedom or create inconvenience, and therefore I posted mirrors on +Github. However, it was noticed that despite the development of +_libmdbx_, free support and consultations, many projects and users +deviated from the rules of a fair deal and instead of helping and +building relationships, they began to adjust links and delete references +in violation of the license. Therefore, in protest against such unworthy +actions, on December 10, 2025, I decided to abandon the placement of +mirrors on Github. diff --git a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/VERSION.json b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/VERSION.json index 534d22e15c6..e5440b00498 100644 --- a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/VERSION.json +++ b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/VERSION.json @@ -1 +1 @@ -{ "git_describe": "v0.13.7-0-g566b0f93", "git_timestamp": "2025-07-30T11:44:04+03:00", "git_tree": "7777cbdf5aa4c1ce85ff902a4c3e6170edd42495", "git_commit": "566b0f93c7c9a3bdffb8fb3dc0ce8ca42641bd72", "semver": "0.13.7" } +{ "git_describe": "v0.13.12-0-gf619d43d", "git_timestamp": "2026-04-30T16:36:24+03:00", "git_tree": "f5574b87cc64fa7a3a6b21ba33809258498d5f17", "git_commit": "f619d43dfbc36cbc9a1832503ce43f2e5223996e", "semver": "0.13.12" } diff --git a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/cmake/compiler.cmake b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/cmake/compiler.cmake index a3d789ce9af..1165bb3b51d 100644 --- a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/cmake/compiler.cmake +++ b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/cmake/compiler.cmake @@ -1,4 +1,4 @@ -# Copyright (c) 2010-2025 Леонид Юрьев aka Leonid Yuriev ############################################### +# Copyright (c) 2010-2026 Леонид Юрьев aka Leonid Yuriev ############################################### # SPDX-License-Identifier: Apache-2.0 if(CMAKE_VERSION VERSION_LESS 3.8.2) diff --git a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/cmake/profile.cmake b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/cmake/profile.cmake index 6a8a466ef34..e2aad1bbc29 100644 --- a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/cmake/profile.cmake +++ b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/cmake/profile.cmake @@ -1,4 +1,4 @@ -# Copyright (c) 2012-2025 Леонид Юрьев aka Leonid Yuriev ############################################### +# Copyright (c) 2012-2026 Леонид Юрьев aka Leonid Yuriev ############################################### # SPDX-License-Identifier: Apache-2.0 if(CMAKE_VERSION VERSION_LESS 3.8.2) diff --git a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/cmake/utils.cmake b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/cmake/utils.cmake index abb4cd30980..2bd0680bd73 100644 --- a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/cmake/utils.cmake +++ b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/cmake/utils.cmake @@ -1,4 +1,4 @@ -# Copyright (c) 2012-2025 Леонид Юрьев aka Leonid Yuriev ############################################### +# Copyright (c) 2012-2026 Леонид Юрьев aka Leonid Yuriev ############################################### # SPDX-License-Identifier: Apache-2.0 if(CMAKE_VERSION VERSION_LESS 3.8.2) @@ -121,11 +121,15 @@ macro(semver_parse str) set(_semver_ok TRUE) else() set(_semver_err - "Поля prerelease и/или buildmetadata (строка `-foo+bar` в составе `0.0.0[.0][-foo][+bar]`) не соответствуют SemVer-спецификации" + # "Поля prerelease и/или buildmetadata (подстрока `-foo+bar` в составе `0.0.0[.0][-foo][+bar]`) не соответствуют SemVer-спецификации" + "The prerelease and/or buildmetadata fields (substring `-foo+bar` as part of `0.0.0[.0][-foo][+bar]`) do not comply with the SemVer specification" ) endif() else() - set(_semver_err "Версионная отметка в целом не соответствует шаблону `0.0.0[.0][-foo][+bar]` SemVer-спецификации") + set(_semver_err + # "Версионная отметка в целом не соответствует шаблону `0.0.0[.0][-foo][+bar]` SemVer-спецификации" + "The version mark as a whole does not match the pattern `0.0.0[.0][-foo][+bar]` of the SemVer specification" + ) endif() endmacro(semver_parse) @@ -215,138 +219,6 @@ function(semver_parse_selfcheck) FALSE) endfunction() -macro(git_get_versioninfo source_root_directory) - set(_git_describe "") - set(_git_timestamp "") - set(_git_tree "") - set(_git_commit "") - set(_git_last_vtag "") - set(_git_trailing_commits 0) - set(_git_is_dirty FALSE) - - execute_process( - COMMAND ${GIT} show --no-patch --format=%cI HEAD - OUTPUT_VARIABLE _git_timestamp - OUTPUT_STRIP_TRAILING_WHITESPACE - WORKING_DIRECTORY ${source_root_directory} - RESULT_VARIABLE _rc) - if(_rc OR "${_git_timestamp}" STREQUAL "%cI") - execute_process( - COMMAND ${GIT} show --no-patch --format=%ci HEAD - OUTPUT_VARIABLE _git_timestamp - OUTPUT_STRIP_TRAILING_WHITESPACE - WORKING_DIRECTORY ${source_root_directory} - RESULT_VARIABLE _rc) - if(_rc OR "${_git_timestamp}" STREQUAL "%ci") - message(FATAL_ERROR "Please install latest version of git (`show --no-patch --format=%cI HEAD` failed)") - endif() - endif() - - execute_process( - COMMAND ${GIT} show --no-patch --format=%T HEAD - OUTPUT_VARIABLE _git_tree - OUTPUT_STRIP_TRAILING_WHITESPACE - WORKING_DIRECTORY ${source_root_directory} - RESULT_VARIABLE _rc) - if(_rc OR "${_git_tree}" STREQUAL "") - message(FATAL_ERROR "Please install latest version of git (`show --no-patch --format=%T HEAD` failed)") - endif() - - execute_process( - COMMAND ${GIT} show --no-patch --format=%H HEAD - OUTPUT_VARIABLE _git_commit - OUTPUT_STRIP_TRAILING_WHITESPACE - WORKING_DIRECTORY ${source_root_directory} - RESULT_VARIABLE _rc) - if(_rc OR "${_git_commit}" STREQUAL "") - message(FATAL_ERROR "Please install latest version of git (`show --no-patch --format=%H HEAD` failed)") - endif() - - execute_process( - COMMAND ${GIT} status --untracked-files=no --porcelain - OUTPUT_VARIABLE _git_status - OUTPUT_STRIP_TRAILING_WHITESPACE - WORKING_DIRECTORY ${source_root_directory} - RESULT_VARIABLE _rc) - if(_rc) - message(FATAL_ERROR "Please install latest version of git (`status --untracked-files=no --porcelain` failed)") - endif() - if(NOT "${_git_status}" STREQUAL "") - set(_git_commit "DIRTY-${_git_commit}") - set(_git_is_dirty TRUE) - endif() - unset(_git_status) - - execute_process( - COMMAND ${GIT} describe --tags --abbrev=0 "--match=v[0-9]*" - OUTPUT_VARIABLE _git_last_vtag - OUTPUT_STRIP_TRAILING_WHITESPACE - WORKING_DIRECTORY ${source_root_directory} - RESULT_VARIABLE _rc) - if(_rc OR "${_git_last_vtag}" STREQUAL "") - execute_process( - COMMAND ${GIT} tag - OUTPUT_VARIABLE _git_tags_dump - OUTPUT_STRIP_TRAILING_WHITESPACE - WORKING_DIRECTORY ${source_root_directory} - RESULT_VARIABLE _rc) - execute_process( - COMMAND ${GIT} rev-list --count --no-merges --remove-empty HEAD - OUTPUT_VARIABLE _git_whole_count - OUTPUT_STRIP_TRAILING_WHITESPACE - WORKING_DIRECTORY ${source_root_directory} - RESULT_VARIABLE _rc) - if(_rc) - message( - FATAL_ERROR - "Please install latest version of git (`git rev-list --count --no-merges --remove-empty HEAD` failed)") - endif() - if(_git_whole_count GREATER 42 AND "${_git_tags_dump}" STREQUAL "") - message(FATAL_ERROR "Please fetch tags (`describe --tags --abbrev=0 --match=v[0-9]*` failed)") - else() - message(NOTICE "Falling back to version `0.0.0` (have you made an initial release?") - endif() - set(_git_last_vtag "0.0.0") - set(_git_trailing_commits ${_git_whole_count}) - execute_process( - COMMAND ${GIT} describe --tags --dirty --long --always - OUTPUT_VARIABLE _git_describe - OUTPUT_STRIP_TRAILING_WHITESPACE - WORKING_DIRECTORY ${source_root_directory} - RESULT_VARIABLE _rc) - if(_rc OR "${_git_describe}" STREQUAL "") - execute_process( - COMMAND ${GIT} describe --tags --all --dirty --long --always - OUTPUT_VARIABLE _git_describe - OUTPUT_STRIP_TRAILING_WHITESPACE - WORKING_DIRECTORY ${source_root_directory} - RESULT_VARIABLE _rc) - if(_rc OR "${_git_describe}" STREQUAL "") - message(FATAL_ERROR "Please install latest version of git (`describe --tags --all --long` failed)") - endif() - endif() - else() - execute_process( - COMMAND ${GIT} describe --tags --dirty --long "--match=v[0-9]*" - OUTPUT_VARIABLE _git_describe - OUTPUT_STRIP_TRAILING_WHITESPACE - WORKING_DIRECTORY ${source_root_directory} - RESULT_VARIABLE _rc) - if(_rc OR "${_git_describe}" STREQUAL "") - message(FATAL_ERROR "Please install latest version of git (`describe --tags --long --match=v[0-9]*`)") - endif() - execute_process( - COMMAND ${GIT} rev-list --count "${_git_last_vtag}..HEAD" - OUTPUT_VARIABLE _git_trailing_commits - OUTPUT_STRIP_TRAILING_WHITESPACE - WORKING_DIRECTORY ${source_root_directory} - RESULT_VARIABLE _rc) - if(_rc OR "${_git_trailing_commits}" STREQUAL "") - message(FATAL_ERROR "Please install latest version of git (`rev-list --count ${_git_last_vtag}..HEAD` failed)") - endif() - endif() -endmacro(git_get_versioninfo) - macro(semver_provide name source_root_directory build_directory_for_json_output build_metadata parent_scope) set(_semver "") set(_git_describe "") @@ -354,43 +226,15 @@ macro(semver_provide name source_root_directory build_directory_for_json_output set(_git_tree "") set(_git_commit "") set(_version_from "") - set(_git_root FALSE) - - find_program(GIT git) - if(GIT) - execute_process( - COMMAND ${GIT} rev-parse --show-toplevel - OUTPUT_VARIABLE _git_root - ERROR_VARIABLE _git_root_error - OUTPUT_STRIP_TRAILING_WHITESPACE - WORKING_DIRECTORY ${source_root_directory} - RESULT_VARIABLE _rc) - if(_rc OR "${_git_root}" STREQUAL "") - if(EXISTS "${source_root_directory}/.git") - message(ERROR "`git rev-parse --show-toplevel` failed '${_git_root_error}'") - else() - message(VERBOSE "`git rev-parse --show-toplevel` failed '${_git_root_error}'") - endif() - else() - set(_source_root "${source_root_directory}") - if(NOT CMAKE_VERSION VERSION_LESS 3.19) - file(REAL_PATH "${_git_root}" _git_root) - file(REAL_PATH "${_source_root}" _source_root) - endif() - if(_source_root STREQUAL _git_root AND EXISTS "${_git_root}/VERSION.json") - message( - FATAL_ERROR - "Несколько источников информации о версии, допустим только один из: репозиторий git, либо файл VERSION.json" - ) - endif() - endif() - endif() if(EXISTS "${source_root_directory}/VERSION.json") set(_version_from "${source_root_directory}/VERSION.json") if(CMAKE_VERSION VERSION_LESS 3.19) - message(FATAL_ERROR "Требуется CMake версии >= 3.19 для чтения VERSION.json") + message(FATAL_ERROR + # "Требуется CMake версии >= 3.19 для чтения VERSION.json" + "Requires CMake version >= 3.19 to parse VERSION.json" + ) endif() file( STRINGS "${_version_from}" _versioninfo_json NEWLINE_CONSUME @@ -410,24 +254,11 @@ macro(semver_provide name source_root_directory build_directory_for_json_output if(NOT _semver_ok) message(FATAL_ERROR "SemVer `${_semver}` from ${_version_from}: ${_semver_err}") endif() - elseif(_git_root AND _source_root STREQUAL _git_root) - set(_version_from git) - git_get_versioninfo(${source_root_directory}) - semver_parse(${_git_last_vtag}) - if(NOT _semver_ok) - message(FATAL_ERROR "Git tag `${_git_last_vtag}`: ${_semver_err}") - endif() - if(_git_trailing_commits GREATER 0 AND "${_semver_tweak}" STREQUAL "") - set(_semver_tweak ${_git_trailing_commits}) - endif() - - elseif(GIT) - message( - FATAL_ERROR - "Нет источника информации о версии (${source_root_directory}), требуется один из: репозиторий git, либо VERSION.json" - ) else() - message(FATAL_ERROR "Требуется git для получения информации о версии") + message(FATAL_ERROR + # "Отсутствует `VERSION.json` с информацией о версии" + "The `VERSION.json` with version information is missing" + ) endif() if(NOT _git_describe @@ -506,19 +337,6 @@ macro(semver_provide name source_root_directory build_directory_for_json_output PARENT_SCOPE) endif() - if(_version_from STREQUAL "git") - string( - CONFIGURE - "{ - \"git_describe\" : \"@_git_describe@\", - \"git_timestamp\" : \"@_git_timestamp@\", - \"git_tree\" : \"@_git_tree@\", - \"git_commit\" : \"@_git_commit@\", - \"semver\" : \"@_semver@\"\n}" - _versioninfo_json - @ONLY ESCAPE_QUOTES) - file(WRITE "${build_directory_for_json_output}/VERSION.json" "${_versioninfo_json}") - endif() endmacro(semver_provide) cmake_policy(POP) diff --git a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/config.h.in b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/config.h.in index 5d53860cfc2..8af2e1818ca 100644 --- a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/config.h.in +++ b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/config.h.in @@ -11,8 +11,10 @@ #cmakedefine ENABLE_ASAN #cmakedefine ENABLE_UBSAN #cmakedefine01 MDBX_FORCE_ASSERTIONS + +/* allowing override MDBX_BUILD_CXX=OFF to build the C++ API in "private" mode only for tests */ #if !defined(MDBX_BUILD_TEST) && !defined(MDBX_BUILD_CXX) -#cmakedefine01 MDBX_BUILD_CXX +#cmakedefine01 MDBX_BUILD_CXX /* using MDBX_BUILD_CXX CMake's option */ #endif /* Common */ @@ -39,10 +41,11 @@ #cmakedefine01 MDBX_ENABLE_DBI_LOCKFREE /* Windows */ +/* allowing override MDBX_WITHOUT_MSVC_CRT=ON to build the C++ API in "private" mode only for tests */ #if defined(MDBX_BUILD_TEST) || !defined(MDBX_BUILD_CXX) || MDBX_BUILD_CXX -#define MDBX_WITHOUT_MSVC_CRT 0 +#define MDBX_WITHOUT_MSVC_CRT /* hardcoded zero */ 0 #else -#cmakedefine01 MDBX_WITHOUT_MSVC_CRT +#cmakedefine01 MDBX_WITHOUT_MSVC_CRT /* using MDBX_WITHOUT_MSVC_CRT CMake's option */ #endif /* MDBX_WITHOUT_MSVC_CRT */ /* MacOS & iOS */ @@ -63,6 +66,11 @@ #cmakedefine01 MDBX_USE_MINCORE +#cmakedefine MDBX_USE_FALLOCATE_AUTO +#ifndef MDBX_USE_FALLOCATE_AUTO +#cmakedefine01 MDBX_USE_FALLOCATE +#endif /* MDBX_USE_FALLOCATE */ + /* Build Info */ #ifndef MDBX_BUILD_TIMESTAMP #cmakedefine MDBX_BUILD_TIMESTAMP "@MDBX_BUILD_TIMESTAMP@" diff --git a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/man1/mdbx_chk.1 b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/man1/mdbx_chk.1 index bc6de4b7758..80a4547d134 100644 --- a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/man1/mdbx_chk.1 +++ b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/man1/mdbx_chk.1 @@ -1,4 +1,4 @@ -.\" Copyright 2015-2025 Leonid Yuriev . +.\" Copyright 2015-2026 Leonid Yuriev . .\" Copying restrictions apply. See COPYRIGHT/LICENSE. .TH MDBX_CHK 1 "2024-08-29" "MDBX 0.13" .SH NAME diff --git a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/man1/mdbx_copy.1 b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/man1/mdbx_copy.1 index 83ec8553bd4..b0a969d11d6 100644 --- a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/man1/mdbx_copy.1 +++ b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/man1/mdbx_copy.1 @@ -1,4 +1,4 @@ -.\" Copyright 2015-2025 Leonid Yuriev . +.\" Copyright 2015-2026 Leonid Yuriev . .\" Copyright 2015,2016 Peter-Service R&D LLC . .\" Copyright 2012-2015 Howard Chu, Symas Corp. All Rights Reserved. .\" Copying restrictions apply. See COPYRIGHT/LICENSE. diff --git a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/man1/mdbx_drop.1 b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/man1/mdbx_drop.1 index a6abbe4198d..11b2c7e404b 100644 --- a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/man1/mdbx_drop.1 +++ b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/man1/mdbx_drop.1 @@ -1,4 +1,4 @@ -.\" Copyright 2021-2025 Leonid Yuriev . +.\" Copyright 2021-2026 Leonid Yuriev . .\" Copyright 2014-2021 Howard Chu, Symas Corp. All Rights Reserved. .\" Copying restrictions apply. See COPYRIGHT/LICENSE. .TH MDBX_DROP 1 "2024-08-29" "MDBX 0.13" diff --git a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/man1/mdbx_dump.1 b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/man1/mdbx_dump.1 index 030617c3478..70f324027b4 100644 --- a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/man1/mdbx_dump.1 +++ b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/man1/mdbx_dump.1 @@ -1,4 +1,4 @@ -.\" Copyright 2015-2025 Leonid Yuriev . +.\" Copyright 2015-2026 Leonid Yuriev . .\" Copyright 2015,2016 Peter-Service R&D LLC . .\" Copyright 2014-2015 Howard Chu, Symas Corp. All Rights Reserved. .\" Copying restrictions apply. See COPYRIGHT/LICENSE. diff --git a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/man1/mdbx_load.1 b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/man1/mdbx_load.1 index 27b533e398a..f8f7b10b623 100644 --- a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/man1/mdbx_load.1 +++ b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/man1/mdbx_load.1 @@ -1,4 +1,4 @@ -.\" Copyright 2015-2025 Leonid Yuriev . +.\" Copyright 2015-2026 Leonid Yuriev . .\" Copyright 2015,2016 Peter-Service R&D LLC . .\" Copyright 2014-2015 Howard Chu, Symas Corp. All Rights Reserved. .\" Copying restrictions apply. See COPYRIGHT/LICENSE. diff --git a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/man1/mdbx_stat.1 b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/man1/mdbx_stat.1 index 68a698c1cb2..36bc26d3956 100644 --- a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/man1/mdbx_stat.1 +++ b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/man1/mdbx_stat.1 @@ -1,4 +1,4 @@ -.\" Copyright 2015-2025 Leonid Yuriev . +.\" Copyright 2015-2026 Leonid Yuriev . .\" Copyright 2015,2016 Peter-Service R&D LLC . .\" Copyright 2012-2015 Howard Chu, Symas Corp. All Rights Reserved. .\" Copying restrictions apply. See COPYRIGHT/LICENSE. diff --git a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx.c b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx.c index ae5de1be4c9..01b663ba430 100644 --- a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx.c +++ b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx.c @@ -1,10 +1,10 @@ /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 /* clang-format off */ #define xMDBX_ALLOY 1 /* alloyed build */ -#define MDBX_BUILD_SOURCERY 6b5df6869d2bf5419e3a8189d9cc849cc9911b9c8a951b9750ed0a261ce43724_v0_13_7_0_g566b0f93 +#define MDBX_BUILD_SOURCERY a575a490fc080ca11e89ff6db9f0bd38aa830959905998cac0e45274b9e6bb0e_v0_13_12_0_gf619d43d #define LIBMDBX_INTERNALS #define MDBX_DEPRECATED @@ -1173,6 +1173,14 @@ typedef char pathchar_t; #define MDBX_PRIsPATH "s" #endif +MDBX_MAYBE_UNUSED static inline bool osal_yield(void) { +#if defined(_WIN32) || defined(_WIN64) + return SleepEx(0, true) == WAIT_IO_COMPLETION; +#else + return sched_yield() != 0; +#endif +} + typedef struct osal_mmap { union { void *base; @@ -1192,6 +1200,8 @@ typedef struct osal_mmap { #define MDBX_HAVE_PWRITEV 0 +MDBX_INTERNAL int osal_waitstatus2errcode(DWORD result); + #elif defined(__ANDROID_API__) #if __ANDROID_API__ < 24 @@ -1435,7 +1445,7 @@ enum osal_syncmode_bits { }; MDBX_INTERNAL int osal_fsync(mdbx_filehandle_t fd, const enum osal_syncmode_bits mode_bits); -MDBX_INTERNAL int osal_ftruncate(mdbx_filehandle_t fd, uint64_t length); +MDBX_INTERNAL int osal_fsetsize(mdbx_filehandle_t fd, const uint64_t length); MDBX_INTERNAL int osal_fseek(mdbx_filehandle_t fd, uint64_t pos); MDBX_INTERNAL int osal_filesize(mdbx_filehandle_t fd, uint64_t *length); @@ -1471,11 +1481,11 @@ MDBX_INTERNAL int osal_removedirectory(const pathchar_t *pathname); MDBX_INTERNAL int osal_is_pipe(mdbx_filehandle_t fd); MDBX_INTERNAL int osal_lockfile(mdbx_filehandle_t fd, bool wait); -#define MMAP_OPTION_TRUNCATE 1 +#define MMAP_OPTION_SETLENGTH 1 #define MMAP_OPTION_SEMAPHORE 2 MDBX_INTERNAL int osal_mmap(const int flags, osal_mmap_t *map, size_t size, const size_t limit, const unsigned options, const pathchar_t *pathname4logging); -MDBX_INTERNAL int osal_munmap(osal_mmap_t *map); +MDBX_INTERNAL void osal_munmap(osal_mmap_t *map); #define MDBX_MRESIZE_MAY_MOVE 0x00000100 #define MDBX_MRESIZE_MAY_UNMAP 0x00000200 MDBX_INTERNAL int osal_mresize(const int flags, osal_mmap_t *map, size_t size, size_t limit); @@ -1878,7 +1888,8 @@ MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline uint32_t osal_bswap32 ((defined(_POSIX_THREAD_ROBUST_PRIO_INHERIT) && _POSIX_THREAD_ROBUST_PRIO_INHERIT > 0) || \ (defined(_POSIX_THREAD_ROBUST_PRIO_PROTECT) && _POSIX_THREAD_ROBUST_PRIO_PROTECT > 0) || \ defined(PTHREAD_MUTEX_ROBUST) || defined(PTHREAD_MUTEX_ROBUST_NP)) && \ - (!defined(__GLIBC__) || __GLIBC_PREREQ(2, 10) /* troubles with Robust mutexes before 2.10 */) + (!defined(__GLIBC__) || __GLIBC_PREREQ(2, 10) /* troubles with Robust mutexes before 2.10 */) && \ + !defined(__OHOS__) /* Harmony OS doesn't support robust mutexes at the end of 2025 */ #define MDBX_LOCKING MDBX_LOCKING_POSIX2008 #else #define MDBX_LOCKING MDBX_LOCKING_POSIX2001 @@ -1933,6 +1944,22 @@ MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline uint32_t osal_bswap32 #error MDBX_USE_COPYFILERANGE must be defined as 0 or 1 #endif /* MDBX_USE_COPYFILERANGE */ +/** Advanced: Using posix_fallocate() or fcntl(F_PREALLOCATE) on OSX (autodetection by default). */ +#ifndef MDBX_USE_FALLOCATE +#if defined(__APPLE__) +#define MDBX_USE_FALLOCATE 0 /* Too slow and unclean, but not required to prevent SIGBUS */ +#elif (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L) || (__GLIBC_PREREQ(2, 10) && defined(_GNU_SOURCE)) +#define MDBX_USE_FALLOCATE 1 +#else +#define MDBX_USE_FALLOCATE 0 +#endif +#define MDBX_USE_FALLOCATE_CONFIG "AUTO=" MDBX_STRINGIFY(MDBX_USE_FALLOCATE) +#elif !(MDBX_USE_FALLOCATE == 0 || MDBX_USE_FALLOCATE == 1) +#error MDBX_USE_FALLOCATE must be defined as 0 or 1 +#else +#define MDBX_USE_FALLOCATE_CONFIG MDBX_STRINGIFY(MDBX_USE_FALLOCATE) +#endif /* MDBX_USE_FALLOCATE */ + //------------------------------------------------------------------------------ #ifndef MDBX_CPU_WRITEBACK_INCOHERENT @@ -3361,8 +3388,7 @@ MDBX_MAYBE_UNUSED static #if MDBX_64BIT_ATOMIC __always_inline #endif /* MDBX_64BIT_ATOMIC */ - uint64_t - atomic_load64(const volatile mdbx_atomic_uint64_t *p, enum mdbx_memory_order order) { + uint64_t atomic_load64(const volatile mdbx_atomic_uint64_t *p, enum mdbx_memory_order order) { STATIC_ASSERT(sizeof(mdbx_atomic_uint64_t) == 8); #if MDBX_64BIT_ATOMIC #ifdef MDBX_HAVE_C11ATOMICS @@ -3417,10 +3443,8 @@ MDBX_MAYBE_UNUSED static __always_inline void atomic_yield(void) { #elif defined(__mips) || defined(__mips__) || defined(__mips64) || defined(__mips64__) || defined(_M_MRX000) || \ defined(_MIPS_) || defined(__MWERKS__) || defined(__sgi) __asm__ __volatile__(".word 0x00000140"); -#elif defined(__linux__) || defined(__gnu_linux__) || defined(_UNIX03_SOURCE) - sched_yield(); -#elif (defined(_GNU_SOURCE) && __GLIBC_PREREQ(2, 1)) || defined(_OPEN_THREADS) - pthread_yield(); +#else + osal_yield(); #endif } @@ -3601,8 +3625,7 @@ MDBX_MAYBE_UNUSED static #if MDBX_64BIT_ATOMIC __always_inline #endif /* MDBX_64BIT_ATOMIC */ - void - safe64_inc(mdbx_atomic_uint64_t *p, const uint64_t v) { + void safe64_inc(mdbx_atomic_uint64_t *p, const uint64_t v) { assert(v > 0); safe64_update(p, safe64_read(p) + v); } @@ -3611,6 +3634,9 @@ MDBX_MAYBE_UNUSED static /* Internal prototypes */ +MDBX_INTERNAL int MDBX_PRINTF_ARGS(2, 3) bad_page(const page_t *mp, const char *fmt, ...); +MDBX_INTERNAL void MDBX_PRINTF_ARGS(2, 3) poor_page(const page_t *mp, const char *fmt, ...); + /* audit.c */ MDBX_INTERNAL int audit_ex(MDBX_txn *txn, size_t retired_stored, bool dont_filter_gc); @@ -4158,7 +4184,7 @@ enum txn_flags { txn_rw_begin_flags = MDBX_TXN_NOMETASYNC | MDBX_TXN_NOSYNC | MDBX_TXN_TRY, txn_shrink_allowed = UINT32_C(0x40000000), txn_parked = MDBX_TXN_PARKED, - txn_gc_drained = 0x40 /* GC was depleted up to oldest reader */, + txn_gc_drained = 0x100 /* GC was depleted up to oldest reader */, txn_state_flags = MDBX_TXN_FINISHED | MDBX_TXN_ERROR | MDBX_TXN_DIRTY | MDBX_TXN_SPILLS | MDBX_TXN_HAS_CHILD | MDBX_TXN_INVALID | txn_gc_drained }; @@ -4635,8 +4661,35 @@ MDBX_INTERNAL int __must_check_result node_read_bigdata(MDBX_cursor *mc, const n static inline int __must_check_result node_read(MDBX_cursor *mc, const node_t *node, MDBX_val *data, const page_t *mp) { data->iov_len = node_ds(node); data->iov_base = node_data(node); - if (likely(node_flags(node) != N_BIG)) + if (likely(node_flags(node) != N_BIG)) { +#if 0 + /* This is an example of a code that checks out-of-bounds by an incorrect/bad/crafted node. + * Such checks look useful, but they are unreasonable really: + * - an each such check catches some damage case of the structure, apparently in one of the hot execution paths, + * but LEAVES several dozen more aside; + * - the overhead increases slightly, + * but there is also NO CERTAINTY that all or most of the corruption cases will be solved; + * - libmdbx already has a fairly complete page content validation mode by MDBX_VALIDATION, which leads + * calling the page_check() for each page from all page_get_xxx() variants, excepts page_get_unchecked(); + * = So, the MDBX_VALIDATION must be used to work with untrusted data, + * but such checks are not necessary for trusted data. + * + * Thus, libmdbx allows a user, if necessary, to enable control of the database structure at the cost of reduced + * performance. On the other hand, such approach allows not to lose productivity unnecessarily. + * + * Related issues: + * - https://sourcecraft.dev/dqdkfa/libmdbx/issues/290 + * - https://github.com/Mithril-mine/libmdbx/pull/306 + */ + const char *data_end = ptr_disp(data->iov_base, data->iov_len); + const char *page_tail = (const char *)((intptr_t)mp | /* Using the OR operation to get the tail of a real page + in case here is a dupsort nested sub-page even. */ + (intptr_t)(mc->txn->env->ps - 1)); + if (!MDBX_DISABLE_VALIDATION && unlikely(data_end > page_tail)) + return bad_page(mp, "node-data (size %zu bytes) beyond the end of page", data->iov_len); +#endif /* code example */ return MDBX_SUCCESS; + } return node_read_bigdata(mc, node, data, mp); } @@ -4661,7 +4714,7 @@ MDBX_NOTHROW_CONST_FUNCTION MDBX_MAYBE_UNUSED MDBX_INTERNAL size_t dbi_bitmap_ct intptr_t bmi); static inline size_t dbi_bitmap_ctz(const MDBX_txn *txn, intptr_t bmi) { - tASSERT(txn, bmi > 0); + tASSERT(txn, bmi != 0); STATIC_ASSERT(sizeof(bmi) >= sizeof(txn->dbi_sparse[0])); #if __GNUC_PREREQ(4, 1) || __has_builtin(__builtin_ctzl) if (sizeof(txn->dbi_sparse[0]) <= sizeof(int)) @@ -5522,7 +5575,7 @@ enum cursor_checking { MDBX_INTERNAL int __must_check_result cursor_validate(const MDBX_cursor *mc); MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline size_t cursor_dbi(const MDBX_cursor *mc) { - cASSERT(mc, mc->txn && mc->txn->signature == txn_signature); + cASSERT(mc, mc->txn->signature == txn_signature); size_t dbi = mc->dbi_state - mc->txn->dbi_state; cASSERT(mc, dbi < mc->txn->env->n_dbi); return dbi; @@ -5808,7 +5861,7 @@ typedef struct gc_update_context { unsigned loop; pgno_t prev_first_unallocated; bool dense; - size_t reserve_adj; + size_t reserve_adj, backlog_adj; size_t retired_stored; size_t amount, reserved, cleaned_slot, reused_slot, fill_idx; txnid_t cleaned_id, rid; @@ -6143,10 +6196,6 @@ MDBX_INTERNAL int __must_check_result page_split(MDBX_cursor *mc, const MDBX_val /*----------------------------------------------------------------------------*/ -MDBX_INTERNAL int MDBX_PRINTF_ARGS(2, 3) bad_page(const page_t *mp, const char *fmt, ...); - -MDBX_INTERNAL void MDBX_PRINTF_ARGS(2, 3) poor_page(const page_t *mp, const char *fmt, ...); - MDBX_NOTHROW_PURE_FUNCTION static inline bool is_frozen(const MDBX_txn *txn, const page_t *mp) { return mp->txnid < txn->txnid; } @@ -6788,7 +6837,7 @@ MDBX_INTERNAL int walk_pages(MDBX_txn *txn, walk_func *visitor, void *user, walk return it; \ } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 __cold size_t mdbx_default_pagesize(void) { size_t pagesize = globals.sys_pagesize; @@ -7331,7 +7380,7 @@ LIBMDBX_API __cold intptr_t mdbx_limits_pgsize_max(void) { return __inline_mdbx_ /// \copyright SPDX-License-Identifier: Apache-2.0 /// \note Please refer to the COPYRIGHT file for explanations license change, /// credits and acknowledgments. -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 typedef struct compacting_context { MDBX_env *env; @@ -7807,7 +7856,7 @@ __cold static int copy_with_compacting(MDBX_env *env, MDBX_txn *txn, mdbx_fileha if (meta->geometry.now != meta->geometry.first_unallocated) { const size_t whole_size = pgno2bytes(env, meta->geometry.now); if (!dest_is_pipe) - return osal_ftruncate(fd, whole_size); + return osal_fsetsize(fd, whole_size); const size_t used_size = pgno2bytes(env, meta->geometry.first_unallocated); memset(data_buffer, 0, (size_t)MDBX_ENVCOPY_WRITEBUF); @@ -7976,7 +8025,7 @@ __cold static int copy_asis(MDBX_env *env, MDBX_txn *txn, mdbx_filehandle_t fd, /* Extend file if required */ if (likely(rc == MDBX_SUCCESS) && whole_size != used_size) { if (!dest_is_pipe) - rc = osal_ftruncate(fd, whole_size); + rc = osal_fsetsize(fd, whole_size); else { memset(data_buffer, 0, (size_t)MDBX_ENVCOPY_WRITEBUF); for (size_t offset = used_size; rc == MDBX_SUCCESS && offset < whole_size;) { @@ -8239,7 +8288,7 @@ __cold int mdbx_env_copyW(MDBX_env *env, const wchar_t *dest_path, MDBX_copy_fla return LOG_IFERR(rc); } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 MDBX_cursor *mdbx_cursor_create(void *context) { cursor_couple_t *couple = osal_calloc(1, sizeof(cursor_couple_t)); @@ -8991,7 +9040,7 @@ __cold int mdbx_cursor_ignord(MDBX_cursor *mc) { return MDBX_SUCCESS; } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 int mdbx_dbi_open2(MDBX_txn *txn, const MDBX_val *name, MDBX_db_flags_t flags, MDBX_dbi *dbi) { return LOG_IFERR(dbi_open(txn, name, flags, dbi, nullptr, nullptr)); @@ -9243,8 +9292,8 @@ __cold int mdbx_dbi_stat(const MDBX_txn *txn, MDBX_dbi dbi, MDBX_stat *dest, siz if (unlikely(bytes != sizeof(MDBX_stat)) && bytes != size_before_modtxnid) return LOG_IFERR(MDBX_EINVAL); - dest->ms_psize = txn->env->ps; stat_get(&txn->dbs[dbi], dest, bytes); + dest->ms_psize = txn->env->ps; return MDBX_SUCCESS; } @@ -9293,6 +9342,7 @@ __cold int mdbx_enumerate_tables(const MDBX_txn *txn, MDBX_table_enum_func *func MDBX_stat stat; stat_get(tree, &stat, sizeof(stat)); + stat.ms_psize = txn->env->ps; rc = func(ctx, txn, &name, tree->flags, &stat, dbi); if (rc != MDBX_SUCCESS) goto bailout; @@ -9304,7 +9354,7 @@ __cold int mdbx_enumerate_tables(const MDBX_txn *txn, MDBX_table_enum_func *func return LOG_IFERR(rc); } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 __cold static intptr_t reasonable_db_maxsize(void) { static intptr_t cached_result; @@ -9511,10 +9561,19 @@ __cold int mdbx_env_create(MDBX_env **penv) { } #if defined(__linux__) || defined(__gnu_linux__) - if (unlikely(globals.linux_kernel_version < 0x04000000)) { - /* 2022-09-01: Прошло уже более двух лет после окончания какой-либо - * поддержки самого "долгоиграющего" ядра 3.16.85 ветки 3.x */ - ERROR("too old linux kernel %u.%u.%u.%u, the >= 4.0.0 is required", globals.linux_kernel_version >> 24, + if (unlikely(globals.linux_kernel_version < 0x03100000)) { + /* 2025-08-05: Ядро 3.16 выпущено 11 лет назад и было самым долго поддерживаемым из 3.x до июля 2020. + * Три года назад (в 2022) здесь была заблокирована работа на ядрах меньше 4.x, как устаревших и для которых + * крайне затруднительно обеспечить какое-либо тестирование. Теперь же я решил изменить решение и разрешить + * работу на старых ядрах начиная с 3.16, логика тут такая: + * - поведение старых ядер уже точно не будет меняться, + * а в текущем коде libmdbx есть всё необходимое для работы начиная с 3.16; + * - есть широко-используемые проекты (Isar), которым требуется поддержка старых ядер; + * - сейчас тестирование для 4.x также затруднено, как и для 3.16, уже не приносит какого-либо облегчения + * с тестированием и мне приходится полагаться на гарантии совместимости API ядра и glibc/musl; + * - использование возможностей из новых ядер всё равно требует проверок/ветвлений; + * = поэтому сейчас нет причин отказываться от работы на 3.16 поддерживая ядра 4.0 */ + ERROR("too old linux kernel %u.%u.%u.%u, the >= 3.16 is required", globals.linux_kernel_version >> 24, (globals.linux_kernel_version >> 16) & 255, (globals.linux_kernel_version >> 8) & 255, globals.linux_kernel_version & 255); return LOG_IFERR(MDBX_INCOMPATIBLE); @@ -10322,14 +10381,13 @@ __cold int mdbx_env_set_geometry(MDBX_env *env, intptr_t size_lower, intptr_t si /* is requested some auto-value for pagesize ? */ if (pagesize >= INT_MAX /* maximal */) pagesize = MDBX_MAX_PAGESIZE; - else if (pagesize <= 0) { - if (pagesize < 0 /* default */) { - pagesize = globals.sys_pagesize; - if ((uintptr_t)pagesize > MDBX_MAX_PAGESIZE) - pagesize = MDBX_MAX_PAGESIZE; - eASSERT(env, (uintptr_t)pagesize >= MDBX_MIN_PAGESIZE); - } else if (pagesize == 0 /* minimal */) - pagesize = MDBX_MIN_PAGESIZE; + else if (pagesize == 0 /* minimal */) + pagesize = MDBX_MIN_PAGESIZE; + else if (pagesize < 0 /* default */) { + pagesize = globals.sys_pagesize; + if ((uintptr_t)pagesize > MDBX_MAX_PAGESIZE) + pagesize = MDBX_MAX_PAGESIZE; + eASSERT(env, (uintptr_t)pagesize >= MDBX_MIN_PAGESIZE); /* choose pagesize */ intptr_t top = (size_now > size_lower) ? size_now : size_lower; @@ -10736,7 +10794,7 @@ __cold int mdbx_env_stat_ex(const MDBX_env *env, const MDBX_txn *txn, MDBX_stat return LOG_IFERR(rc); } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 /*------------------------------------------------------------------------------ * Readers API */ @@ -10899,7 +10957,7 @@ int mdbx_txn_unlock(MDBX_env *env) { return MDBX_SUCCESS; } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 static inline double key2double(const int64_t key) { union { @@ -11094,7 +11152,7 @@ int64_t mdbx_int64_from_key(const MDBX_val v) { return (int64_t)(unaligned_peek_u64(2, v.iov_base) - UINT64_C(0x8000000000000000)); } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 __cold int mdbx_is_readahead_reasonable(size_t volume, intptr_t redundancy) { if (volume <= 1024 * 1024 * 4ul) @@ -11231,7 +11289,7 @@ __cold const char *mdbx_liberr2str(int errnum) { nullptr /* MDBX_TLS_FULL (-30789): unused in MDBX */, "MDBX_TXN_FULL: Transaction has too many dirty pages," " i.e transaction is too big", - "MDBX_CURSOR_FULL: Cursor stack limit reachedn - this usually indicates" + "MDBX_CURSOR_FULL: Cursor stack limit reached - this usually indicates" " corruption, i.e branch-pages loop", "MDBX_PAGE_FULL: Internal error - Page has no more space", "MDBX_UNABLE_EXTEND_MAPSIZE: Database engine was unable to extend" @@ -11378,7 +11436,7 @@ const char *mdbx_strerror_ANSI2OEM(int errnum) { } #endif /* Bit of madness for Windows */ /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 static pgno_t env_max_pgno(const MDBX_env *env) { return env->ps ? bytes2pgno(env, env->geo_in_bytes.upper ? env->geo_in_bytes.upper : MAX_MAPSIZE) : PAGELIST_LIMIT; @@ -11951,7 +12009,7 @@ __cold int mdbx_env_get_option(const MDBX_env *env, const MDBX_option_t option, return MDBX_SUCCESS; } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 typedef struct diff_result { ptrdiff_t diff; @@ -12314,7 +12372,7 @@ __hot int mdbx_estimate_range(const MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val return MDBX_SUCCESS; } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 __cold int mdbx_dbi_dupsort_depthmask(const MDBX_txn *txn, MDBX_dbi dbi, uint32_t *mask) { if (unlikely(!mask)) @@ -12766,14 +12824,13 @@ int mdbx_replace(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, MDBX_val *new return mdbx_replace_ex(txn, dbi, key, new_data, old_data, flags, default_value_preserver, nullptr); } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 #ifdef __SANITIZE_THREAD__ /* LY: avoid tsan-trap by txn, mm_last_pg and geo.first_unallocated */ __attribute__((__no_sanitize_thread__, __noinline__)) #endif -int mdbx_txn_straggler(const MDBX_txn *txn, int *percent) -{ +int mdbx_txn_straggler(const MDBX_txn *txn, int *percent) { int rc = check_txn(txn, MDBX_TXN_BLOCKED - MDBX_TXN_PARKED); if (unlikely(rc != MDBX_SUCCESS)) return LOG_IFERR((rc > 0) ? -rc : rc); @@ -13252,8 +13309,8 @@ int mdbx_txn_commit_ex(MDBX_txn *txn, MDBX_commit_latency *latency) { goto done; } - /* Preserve space for spill list to avoid parent's state corruption - * if allocation fails. */ + /* Preserve space for parent->tw.repnl and spill list to avoid parent's + * state corruption if allocation fails. */ const size_t parent_retired_len = (uintptr_t)parent->tw.retired_pages; tASSERT(txn, parent_retired_len <= MDBX_PNL_GETSIZE(txn->tw.retired_pages)); const size_t retired_delta = MDBX_PNL_GETSIZE(txn->tw.retired_pages) - parent_retired_len; @@ -13685,7 +13742,7 @@ int mdbx_txn_info(const MDBX_txn *txn, MDBX_txn_info *info, bool scan_rlt) { return MDBX_SUCCESS; } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 struct audit_ctx { size_t used; @@ -13792,7 +13849,7 @@ __cold int audit_ex(MDBX_txn *txn, size_t retired_stored, bool dont_filter_gc) { return rc; } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 typedef struct MDBX_chk_internal { MDBX_chk_context_t *usr; @@ -13804,11 +13861,11 @@ typedef struct MDBX_chk_internal { bool got_break; bool write_locked; uint8_t scope_depth; + pgno_t last_nested_root; MDBX_chk_table_t table_gc, table_main; int16_t *pagemap; MDBX_chk_table_t *last_lookup; - const void *last_nested; MDBX_chk_scope_t scope_stack[12]; MDBX_chk_table_t *table[MDBX_MAX_DBI + CORE_DBS]; @@ -14297,9 +14354,9 @@ static void histogram_reduce(struct MDBX_chk_histogram *p) { p->ranges[min_i].end = p->ranges[min_i + 1].end; p->ranges[min_i].amount += p->ranges[min_i + 1].amount; p->ranges[min_i].count += p->ranges[min_i + 1].count; - if (min_i < last) + if (min_i < last - 1) // перемещаем хвост - memmove(p->ranges + min_i, p->ranges + min_i + 1, (last - min_i) * sizeof(p->ranges[0])); + memmove(p->ranges + min_i + 1, p->ranges + min_i + 2, (last - min_i - 1) * sizeof(p->ranges[0])); // обнуляем последний элемент и продолжаем p->ranges[last].count = 0; } @@ -14496,8 +14553,7 @@ __cold static int chk_pgvisitor(const size_t pgno, const unsigned npages, void * chk_v2a(chk, &tbl->name), tbl->flags, deep); nested = nullptr; } - } else - chk->last_nested = nullptr; + } const char *pagetype_caption; bool branch = false; @@ -14550,9 +14606,9 @@ __cold static int chk_pgvisitor(const size_t pgno, const unsigned npages, void * } else { pagetype_caption = (pagetype == page_leaf) ? "nested-leaf" : "nested-leaf-dupfix"; tbl->pages.nested_leaf += 1; - if (chk->last_nested != nested) { + if (chk->last_nested_root != nested->root) { histogram_acc(height, &tbl->histogram.nested_tree); - chk->last_nested = nested; + chk->last_nested_root = nested->root; } if (height != nested->height) chk_object_issue(scope, "page", pgno, "wrong nested-tree height", "actual %i != %i dupsort-node %s", height, @@ -15586,7 +15642,7 @@ __cold int mdbx_env_chk(MDBX_env *env, const struct MDBX_chk_callbacks *cb, MDBX return LOG_IFERR(rc); } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 /*------------------------------------------------------------------------------ * Pack/Unpack 16-bit values for Grow step & Shrink threshold */ @@ -15893,7 +15949,7 @@ uint32_t combine_durability_flags(const uint32_t a, const uint32_t b) { return r; } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 /* check against https://libmdbx.dqdkfa.ru/dead-github/issues/269 */ static bool coherency_check(const MDBX_env *env, const txnid_t txnid, const volatile tree_t *trees, @@ -15995,15 +16051,7 @@ __cold int coherency_timeout(uint64_t *timestamp, intptr_t pgno, const MDBX_env } osal_memory_fence(mo_AcquireRelease, true); -#if defined(_WIN32) || defined(_WIN64) - SwitchToThread(); -#elif defined(__linux__) || defined(__gnu_linux__) || defined(_UNIX03_SOURCE) - sched_yield(); -#elif (defined(_GNU_SOURCE) && __GLIBC_PREREQ(2, 1)) || defined(_OPEN_THREADS) - pthread_yield(); -#else - usleep(42); -#endif + osal_yield(); return MDBX_RESULT_TRUE; } @@ -16063,7 +16111,7 @@ bool coherency_check_meta(const MDBX_env *env, const volatile meta_t *meta, bool /// \copyright SPDX-License-Identifier: Apache-2.0 /// \note Please refer to the COPYRIGHT file for explanations license change, /// credits and acknowledgments. -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 __cold int cursor_validate(const MDBX_cursor *mc) { if (!mc->txn->tw.dirtylist) { @@ -16282,7 +16330,7 @@ MDBX_cursor *cursor_eot(MDBX_cursor *mc, MDBX_txn *txn, const bool merge) { MDBX_cursor *const next = mc->next; const unsigned stage = mc->signature; MDBX_cursor *const bk = mc->backup; - ENSURE(txn->env, stage == cur_signature_live || (stage == cur_signature_wait4eot && bk)); + ENSURE(txn->env, stage == cur_signature_live || stage == cur_signature_wait4eot); tASSERT(txn, mc->txn == txn); if (bk) { subcur_t *mx = mc->subcur; @@ -16313,10 +16361,13 @@ MDBX_cursor *cursor_eot(MDBX_cursor *mc, MDBX_txn *txn, const bool merge) { bk->signature = 0; osal_free(bk); } else { - ENSURE(mc->txn->env, stage == cur_signature_live); - mc->signature = cur_signature_ready4dispose /* Cursor may be reused */; - mc->next = mc; cursor_drown((cursor_couple_t *)mc); + mc->next = mc; + if (stage == cur_signature_wait4eot) { + mc->signature = 0; + osal_free(mc); + } else + mc->signature = cur_signature_ready4dispose /* Cursor may be reused */; } return next; } @@ -16863,7 +16914,7 @@ __hot int cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, unsig } } else { csr_t csr = - /* olddata may not be updated in case DUPFIX-page of dupfix-table */ + /* old_data may not be updated in case DUPFIX-page of dupfix-table */ cursor_seek(mc, (MDBX_val *)key, &old_data, MDBX_SET); rc = csr.err; exact = csr.exact; @@ -16876,12 +16927,9 @@ __hot int cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, unsig return MDBX_KEYEXIST; } if (unlikely(mc->flags & z_inner)) { - /* nested subtree of DUPSORT-database with the same key, - * nothing to update */ - eASSERT(env, data->iov_len == 0 && (old_data.iov_len == 0 || - /* olddata may not be updated in case - DUPFIX-page of dupfix-table */ - (mc->tree->flags & MDBX_DUPFIXED))); + /* nested subtree of DUPSORT-database with the same key, nothing to update */ + eASSERT(env, data->iov_len == 0 && (/* old_data may not be updated in case DUPFIX-page of dupfix-table */ + (mc->tree->flags & MDBX_DUPFIXED) || old_data.iov_len == 0)); return MDBX_SUCCESS; } if (unlikely(flags & MDBX_ALLDUPS) && inner_pointed(mc)) { @@ -17433,8 +17481,7 @@ insert_node:; if (!is_related(mc, m2) || m2->pg[mc->top] != mp) continue; if (/* пропускаем незаполненные курсоры, иначе получится что у такого - курсора будет инициализирован вложенный, - что антилогично и бесполезно. */ + курсора будет инициализирован вложенный, что антилогично и бесполезно. */ is_filled(m2) && m2->ki[mc->top] == mc->ki[mc->top]) { cASSERT(m2, m2->subcur->cursor.clc == mx->cursor.clc); m2->subcur->nested_tree = mx->nested_tree; @@ -17605,7 +17652,7 @@ __hot int cursor_del(MDBX_cursor *mc, unsigned flags) { /* will subtract the final entry later */ mc->tree->items -= mc->subcur->nested_tree.items - 1; } else { - if (!(node_flags(node) & N_TREE)) { + if ((node_flags(node) & N_TREE) == 0) { page_t *sp = node_data(node); cASSERT(mc, is_subpage(sp)); sp->txnid = mp->txnid; @@ -17620,6 +17667,11 @@ __hot int cursor_del(MDBX_cursor *mc, unsigned flags) { /* update table info */ mc->subcur->nested_tree.mod_txnid = mc->txn->txnid; memcpy(node_data(node), &mc->subcur->nested_tree, sizeof(tree_t)); + /* fix other sub-DB cursors pointed at the same sub-tree */ + for (MDBX_cursor *m2 = mc->txn->cursors[cursor_dbi(mc)]; m2; m2 = m2->next) { + if (is_related(mc, m2) && m2->pg[mc->top] == mp && m2->ki[mc->top] == mc->ki[mc->top]) + m2->subcur->nested_tree = mc->subcur->nested_tree; + } } else { /* shrink sub-page */ node = node_shrink(mp, mc->ki[mc->top], node); @@ -17630,16 +17682,17 @@ __hot int cursor_del(MDBX_cursor *mc, unsigned flags) { continue; const node_t *inner = node; if (unlikely(m2->ki[mc->top] >= page_numkeys(mp))) { - m2->flags = z_poor_mark; + m2->flags |= z_eof_hard | z_eof_soft | z_after_delete; m2->subcur->nested_tree.root = 0; m2->subcur->cursor.top_and_flags = z_inner | z_poor_mark; continue; } if (m2->ki[mc->top] != mc->ki[mc->top]) { inner = page_node(mp, m2->ki[mc->top]); - if (node_flags(inner) & N_TREE) + if (node_flags(inner) != N_DUP) continue; - } + } else + m2->subcur->nested_tree = mc->subcur->nested_tree; m2->subcur->cursor.pg[0] = node_data(inner); } } @@ -18455,11 +18508,11 @@ int cursor_check(const MDBX_cursor *mc, int txn_bad_bits) { return MDBX_SUCCESS; } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 #if MDBX_ENABLE_DBI_SPARSE size_t dbi_bitmap_ctz_fallback(const MDBX_txn *txn, intptr_t bmi) { - tASSERT(txn, bmi > 0); + tASSERT(txn, bmi != 0); bmi &= -bmi; if (sizeof(txn->dbi_sparse[0]) > 4) { static const uint8_t debruijn_ctz64[64] = {0, 1, 2, 53, 3, 7, 54, 27, 4, 38, 41, 8, 34, 55, 48, 28, @@ -18626,13 +18679,8 @@ int dbi_defer_release(MDBX_env *const env, defer_free_item_t *const chain) { #endif /* MDBX_ENABLE_DBI_LOCKFREE */ ENSURE(env, osal_fastmutex_release(&env->dbi_lock) == MDBX_SUCCESS); - if (length > 42) { -#if defined(_WIN32) || defined(_WIN64) - SwitchToThread(); -#else - sched_yield(); -#endif /* Windows */ - } + if (length > 42) + osal_yield(); while (obsolete_chain) { defer_free_item_t *item = obsolete_chain; obsolete_chain = obsolete_chain->next; @@ -18644,7 +18692,7 @@ int dbi_defer_release(MDBX_env *const env, defer_free_item_t *const chain) { /* Export or close DBI handles opened in this txn. */ int dbi_update(MDBX_txn *txn, int keep) { MDBX_env *const env = txn->env; - tASSERT(txn, !txn->parent && txn == env->basal_txn); + tASSERT(txn, (!txn->parent && txn == env->basal_txn) || !keep); bool locked = false; defer_free_item_t *defer_chain = nullptr; TXN_FOREACH_DBI_USER(txn, dbi) { @@ -19170,7 +19218,7 @@ __cold const tree_t *dbi_dig(const MDBX_txn *txn, const size_t dbi, tree_t *fall int dbi_close_release(MDBX_env *env, MDBX_dbi dbi) { return dbi_defer_release(env, dbi_close_locked(env, dbi)); } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 static inline size_t dpl_size2bytes(ptrdiff_t size) { assert(size > CURSOR_STACK_SIZE && (size_t)size <= PAGELIST_LIMIT); @@ -19654,7 +19702,7 @@ void dpl_release_shadows(MDBX_txn *txn) { dpl_clear(dl); } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 __cold int dxb_read_header(MDBX_env *env, meta_t *dest, const int lck_exclusive, const mdbx_mode_t mode_bits) { memset(dest, 0, sizeof(meta_t)); @@ -19755,7 +19803,7 @@ __cold int dxb_read_header(MDBX_env *env, meta_t *dest, const int lck_exclusive, if (dest->pagesize == 0 || (env->stuck_meta < 0 && !(meta_is_steady(dest) || meta_weak_acceptable(env, dest, lck_exclusive)))) { ERROR("%s", "no usable meta-pages, database is corrupted"); - if (rc == MDBX_SUCCESS) { + if (!MDBX_IS_ERROR(rc)) { /* TODO: try to restore the database by fully checking b-tree structure * for the each meta page, if the corresponding option was given */ return MDBX_CORRUPTED; @@ -20185,7 +20233,7 @@ __cold int dxb_setup(MDBX_env *env, const int lck_rc, const mdbx_mode_t mode_bit if (unlikely(err != MDBX_SUCCESS)) return err; - err = osal_ftruncate(env->lazy_fd, env->dxb_mmap.filesize = env->dxb_mmap.current = env->geo_in_bytes.now); + err = osal_fsetsize(env->lazy_fd, env->dxb_mmap.filesize = env->dxb_mmap.current = env->geo_in_bytes.now); if (unlikely(err != MDBX_SUCCESS)) return err; @@ -20314,9 +20362,8 @@ __cold int dxb_setup(MDBX_env *env, const int lck_rc, const mdbx_mode_t mode_bit } if (env->flags & MDBX_RDONLY) { - if (filesize_before & (globals.sys_allocation_granularity - 1)) { - ERROR("filesize should be rounded-up to system allocation granularity %u", - globals.sys_allocation_granularity); + if (filesize_before & (globals.sys_pagesize - 1)) { + ERROR("filesize should be rounded-up to system page size %u", globals.sys_pagesize); return MDBX_WANNA_RECOVERY; } WARNING("%s", "ignore filesize mismatch in readonly-mode"); @@ -20335,7 +20382,7 @@ __cold int dxb_setup(MDBX_env *env, const int lck_rc, const mdbx_mode_t mode_bit !(env->flags & MDBX_NORDAHEAD) && mdbx_is_readahead_reasonable(used_bytes, 0) == MDBX_RESULT_TRUE; err = osal_mmap(env->flags, &env->dxb_mmap, env->geo_in_bytes.now, env->geo_in_bytes.upper, - (lck_rc && env->stuck_meta < 0) ? MMAP_OPTION_TRUNCATE : 0, env->pathname.dxb); + (lck_rc && env->stuck_meta < 0) ? MMAP_OPTION_SETLENGTH : 0, env->pathname.dxb); if (unlikely(err != MDBX_SUCCESS)) return err; @@ -20470,14 +20517,14 @@ __cold int dxb_setup(MDBX_env *env, const int lck_rc, const mdbx_mode_t mode_bit meta_troika_dump(env, &troika); return MDBX_CORRUPTED; } + + purge_meta_head: if (env->flags & MDBX_RDONLY) { ERROR("%s and rollback needed: (from head %" PRIaTXN " to steady %" PRIaTXN ")%s", "opening after an unclean shutdown", recent.txnid, prefer_steady.txnid, ", but unable in read-only mode"); meta_troika_dump(env, &troika); return MDBX_WANNA_RECOVERY; } - - purge_meta_head: NOTICE("%s and doing automatic rollback: " "purge%s meta[%u] with%s txnid %" PRIaTXN, "opening after an unclean shutdown", last_valid ? "" : " invalid", pgno, last_valid ? " weak" : "", @@ -20535,7 +20582,13 @@ __cold int dxb_setup(MDBX_env *env, const int lck_rc, const mdbx_mode_t mode_bit header.geometry.lower, header.geometry.now, header.geometry.upper, pv2pages(header.geometry.shrink_pv), pv2pages(header.geometry.grow_pv), next_txnid); - ENSURE(env, header.unsafe_txnid == recent.txnid); + if (unlikely(header.unsafe_txnid != recent.txnid)) { + const pgno_t recent_pgno = bytes2pgno(env, ptr_dist(recent.ptr_c, env->dxb_mmap.base)); + ERROR("meta[%u] recent steady txnid %" PRIaTXN " != header txnid %" PRIaTXN + ", this is too unexpected and requires manual analysis.", + recent_pgno, recent.txnid, header.unsafe_txnid); + return MDBX_PROBLEM; + } meta_set_txnid(env, &header, next_txnid); err = dxb_sync_locked(env, env->flags | txn_shrink_allowed, &header, &troika); if (err) { @@ -20754,12 +20807,14 @@ int dxb_sync_locked(MDBX_env *env, unsigned flags, meta_t *const pending, troika /* LY: step#1 - sync previously written/updated data-pages */ rc = MDBX_RESULT_FALSE /* carry steady */; - if (atomic_load64(&env->lck->unsynced_pages, mo_Relaxed)) { + const uint64_t snap_unsynced_pages = atomic_load64(&env->lck->unsynced_pages, mo_Relaxed); + if (snap_unsynced_pages) { + if (snap_unsynced_pages == 1 && (flags & MDBX_NOMETASYNC)) + goto skip_lastmeta_sync; + eASSERT(env, ((flags ^ env->flags) & MDBX_WRITEMAP) == 0); enum osal_syncmode_bits mode_bits = MDBX_SYNC_NONE; - unsigned sync_op = 0; if ((flags & MDBX_SAFE_NOSYNC) == 0) { - sync_op = 1; mode_bits = MDBX_SYNC_DATA; if (pending->geometry.first_unallocated > meta_prefer_steady(env, troika).ptr_c->geometry.now) mode_bits |= MDBX_SYNC_SIZE; @@ -20767,18 +20822,15 @@ int dxb_sync_locked(MDBX_env *env, unsigned flags, meta_t *const pending, troika mode_bits |= MDBX_SYNC_IODQ; } else if (unlikely(env->incore)) goto skip_incore_sync; + if (flags & MDBX_WRITEMAP) { #if MDBX_ENABLE_PGOP_STAT - env->lck->pgops.msync.weak += sync_op; -#else - (void)sync_op; + env->lck->pgops.msync.weak += (mode_bits > MDBX_SYNC_NONE); #endif /* MDBX_ENABLE_PGOP_STAT */ rc = osal_msync(&env->dxb_mmap, 0, pgno_align2os_bytes(env, pending->geometry.first_unallocated), mode_bits); } else { #if MDBX_ENABLE_PGOP_STAT - env->lck->pgops.fsync.weak += sync_op; -#else - (void)sync_op; + env->lck->pgops.fsync.weak += (mode_bits > MDBX_SYNC_NONE); #endif /* MDBX_ENABLE_PGOP_STAT */ rc = osal_fsync(env->lazy_fd, mode_bits); } @@ -20801,6 +20853,7 @@ int dxb_sync_locked(MDBX_env *env, unsigned flags, meta_t *const pending, troika /* Может быть нулевым если unsynced_pages > 0 в результате спиллинга. * eASSERT(env, env->lck->eoos_timestamp.weak != 0); */ unaligned_poke_u64(4, pending->sign, DATASIGN_WEAK); + skip_lastmeta_sync:; } const bool legal4overwrite = head.txnid == pending->unsafe_txnid && @@ -20935,13 +20988,17 @@ int dxb_sync_locked(MDBX_env *env, unsigned flags, meta_t *const pending, troika } osal_flush_incoherent_mmap(target, sizeof(meta_t), globals.sys_pagesize); /* sync meta-pages */ - if ((flags & MDBX_NOMETASYNC) == 0 && env->fd4meta == env->lazy_fd && !env->incore) { + if (env->fd4meta == env->lazy_fd && !env->incore) { + if (flags & MDBX_NOMETASYNC) + env->lck->unsynced_pages.weak += 1; + else { #if MDBX_ENABLE_PGOP_STAT - env->lck->pgops.fsync.weak += 1; + env->lck->pgops.fsync.weak += 1; #endif /* MDBX_ENABLE_PGOP_STAT */ - rc = osal_fsync(env->lazy_fd, MDBX_SYNC_DATA | MDBX_SYNC_IODQ); - if (rc != MDBX_SUCCESS) - goto undo; + rc = osal_fsync(env->lazy_fd, MDBX_SYNC_DATA | MDBX_SYNC_IODQ); + if (rc != MDBX_SUCCESS) + goto undo; + } } } @@ -20987,7 +21044,7 @@ int dxb_sync_locked(MDBX_env *env, unsigned flags, meta_t *const pending, troika return rc; } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 MDBX_txn *env_owned_wrtxn(const MDBX_env *env) { if (likely(env->basal_txn)) { @@ -21592,7 +21649,7 @@ __cold int env_close(MDBX_env *env, bool resurrect_after_fork) { return rc; } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 #if MDBX_USE_MINCORE /*------------------------------------------------------------------------------ @@ -22734,7 +22791,8 @@ next_gc:; /* Does reclaiming stopped at the last steady point? */ const meta_ptr_t recent = meta_recent(env, &txn->tw.troika); const meta_ptr_t prefer_steady = meta_prefer_steady(env, &txn->tw.troika); - if (recent.ptr_c != prefer_steady.ptr_c && prefer_steady.is_steady && detent == prefer_steady.txnid + 1) { + if ((flags & ALLOC_UNIMPORTANT) == 0 && recent.ptr_c != prefer_steady.ptr_c && prefer_steady.is_steady && + detent == prefer_steady.txnid) { DEBUG("gc-kick-steady: recent %" PRIaTXN "-%s, steady %" PRIaTXN "-%s, detent %" PRIaTXN, recent.txnid, durable_caption(recent.ptr_c), prefer_steady.txnid, durable_caption(prefer_steady.ptr_c), detent); const pgno_t autosync_threshold = atomic_load32(&env->lck->autosync_threshold, mo_Relaxed); @@ -22772,13 +22830,14 @@ next_gc:; env->lck->pgops.gc_prof.flushes += 1; #endif /* MDBX_ENABLE_PROFGC */ meta_t meta = *recent.ptr_c; - ret.err = dxb_sync_locked(env, env->flags & MDBX_WRITEMAP, &meta, &txn->tw.troika); + ret.err = dxb_sync_locked(env, env->flags & (MDBX_WRITEMAP | MDBX_NOMETASYNC), &meta, &txn->tw.troika); DEBUG("gc-make-steady, rc %d", ret.err); eASSERT(env, ret.err != MDBX_RESULT_TRUE); if (unlikely(ret.err != MDBX_SUCCESS)) goto fail; - eASSERT(env, prefer_steady.ptr_c != meta_prefer_steady(env, &txn->tw.troika).ptr_c); - goto retry_gc_refresh_oldest; + if (prefer_steady.ptr_c != meta_prefer_steady(env, &txn->tw.troika).ptr_c) + goto retry_gc_refresh_oldest; + eASSERT(env, env->incore); } } @@ -22805,14 +22864,11 @@ next_gc:; no_gc: eASSERT(env, pgno == 0); -#ifndef MDBX_ENABLE_BACKLOG_DEPLETED -#define MDBX_ENABLE_BACKLOG_DEPLETED 0 -#endif /* MDBX_ENABLE_BACKLOG_DEPLETED*/ - if (MDBX_ENABLE_BACKLOG_DEPLETED && unlikely(!(txn->flags & txn_gc_drained))) { + if (unlikely(!(txn->flags & txn_gc_drained))) { ret.err = MDBX_BACKLOG_DEPLETED; goto fail; } - if (flags & ALLOC_RESERVE) { + if (flags & (ALLOC_RESERVE | ALLOC_UNIMPORTANT)) { ret.err = MDBX_NOTFOUND; goto fail; } @@ -22935,7 +22991,7 @@ __hot pgr_t gc_alloc_single(const MDBX_cursor *const mc) { return gc_alloc_ex(mc, 1, ALLOC_DEFAULT); } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 MDBX_NOTHROW_PURE_FUNCTION static bool is_lifo(const MDBX_txn *txn) { return (txn->env->flags & MDBX_LIFORECLAIM) != 0; @@ -23020,16 +23076,18 @@ static int prepare_backlog(MDBX_txn *txn, gcu_t *ctx) { const size_t for_tree_after_touch = for_rebalance + for_split; const size_t for_all_before_touch = for_repnl + for_tree_before_touch; const size_t for_all_after_touch = for_repnl + for_tree_after_touch; + const size_t enough_before_touch = for_all_before_touch + ctx->backlog_adj; + const size_t enough_after_touch = for_all_after_touch + ctx->backlog_adj; - if (likely(for_repnl < 2 && backlog_size(txn) > for_all_before_touch) && + if (likely(for_repnl < 2 && backlog_size(txn) > enough_before_touch) && (ctx->cursor.top < 0 || is_modifable(txn, ctx->cursor.pg[ctx->cursor.top]))) return MDBX_SUCCESS; - TRACE(">> retired-stored %zu, left %zi, backlog %zu, need %zu (4list %zu, " + TRACE(">> retired-stored %zu, left %zi, backlog %zu, adj %zu, need %zu (4list %zu, " "4split %zu, " "4cow %zu, 4tree %zu)", - ctx->retired_stored, retired_left, backlog_size(txn), for_all_before_touch, for_repnl, for_split, for_cow, - for_tree_before_touch); + ctx->retired_stored, retired_left, backlog_size(txn), ctx->backlog_adj, enough_before_touch, for_repnl, + for_split, for_cow, for_tree_before_touch); int err = touch_gc(ctx); TRACE("== after-touch, backlog %zu, err %d", backlog_size(txn), err); @@ -23048,12 +23106,12 @@ static int prepare_backlog(MDBX_txn *txn, gcu_t *ctx) { cASSERT(&ctx->cursor, backlog_size(txn) >= for_repnl || err != MDBX_SUCCESS); } - while (backlog_size(txn) < for_all_after_touch && err == MDBX_SUCCESS) + while (backlog_size(txn) < enough_after_touch && err == MDBX_SUCCESS) err = gc_alloc_ex(&ctx->cursor, 0, ALLOC_RESERVE | ALLOC_UNIMPORTANT).err; - TRACE("<< backlog %zu, err %d, gc: height %u, branch %zu, leaf %zu, large " + TRACE("<< backlog %zu, err %d, adj %zu, gc: height %u, branch %zu, leaf %zu, large " "%zu, entries %zu", - backlog_size(txn), err, txn->dbs[FREE_DBI].height, (size_t)txn->dbs[FREE_DBI].branch_pages, + backlog_size(txn), err, ctx->backlog_adj, txn->dbs[FREE_DBI].height, (size_t)txn->dbs[FREE_DBI].branch_pages, (size_t)txn->dbs[FREE_DBI].leaf_pages, (size_t)txn->dbs[FREE_DBI].large_pages, (size_t)txn->dbs[FREE_DBI].items); tASSERT(txn, err != MDBX_NOTFOUND || (txn->flags & txn_gc_drained) != 0); @@ -23468,6 +23526,7 @@ int gc_update(MDBX_txn *txn, gcu_t *ctx) { * But page numbers cannot disappear from txn->tw.retired_pages[]. */ retry_clean_adj: ctx->reserve_adj = 0; + ctx->backlog_adj = 0; retry: ctx->loop += !(ctx->prev_first_unallocated > txn->geo.first_unallocated); TRACE(">> restart, loop %u", ctx->loop); @@ -23482,8 +23541,13 @@ int gc_update(MDBX_txn *txn, gcu_t *ctx) { if (unlikely(ctx->dense || ctx->prev_first_unallocated > txn->geo.first_unallocated)) { rc = clean_stored_retired(txn, ctx); - if (unlikely(rc != MDBX_SUCCESS)) + if (unlikely(rc != MDBX_SUCCESS)) { + if (rc == MDBX_BACKLOG_DEPLETED) { + ctx->backlog_adj += ctx->backlog_adj + 1; + goto retry; + } goto bailout; + } } ctx->prev_first_unallocated = txn->geo.first_unallocated; @@ -23525,8 +23589,13 @@ int gc_update(MDBX_txn *txn, gcu_t *ctx) { TRACE("%s: cleanup-reclaimed-id [%zu]%" PRIaTXN, dbg_prefix(ctx), ctx->cleaned_slot, ctx->cleaned_id); tASSERT(txn, *txn->cursors == &ctx->cursor); rc = cursor_del(&ctx->cursor, 0); - if (unlikely(rc != MDBX_SUCCESS)) + if (unlikely(rc != MDBX_SUCCESS)) { + if (rc == MDBX_BACKLOG_DEPLETED) { + ctx->backlog_adj += ctx->backlog_adj + 1; + goto retry; + } goto bailout; + } } while (ctx->cleaned_slot < MDBX_PNL_GETSIZE(txn->tw.gc.retxl)); txl_sort(txn->tw.gc.retxl); } @@ -23564,8 +23633,13 @@ int gc_update(MDBX_txn *txn, gcu_t *ctx) { TRACE("%s: cleanup-reclaimed-id %" PRIaTXN, dbg_prefix(ctx), ctx->cleaned_id); tASSERT(txn, *txn->cursors == &ctx->cursor); rc = cursor_del(&ctx->cursor, 0); - if (unlikely(rc != MDBX_SUCCESS)) + if (unlikely(rc != MDBX_SUCCESS)) { + if (rc == MDBX_BACKLOG_DEPLETED) { + ctx->backlog_adj += ctx->backlog_adj + 1; + goto retry; + } goto bailout; + } } } @@ -23610,8 +23684,13 @@ int gc_update(MDBX_txn *txn, gcu_t *ctx) { if (ctx->retired_stored < MDBX_PNL_GETSIZE(txn->tw.retired_pages)) { /* store retired-list into GC */ rc = gcu_retired(txn, ctx); - if (unlikely(rc != MDBX_SUCCESS)) + if (unlikely(rc != MDBX_SUCCESS)) { + if (rc == MDBX_BACKLOG_DEPLETED) { + ctx->backlog_adj += ctx->backlog_adj + 1; + goto retry; + } goto bailout; + } continue; } @@ -23640,6 +23719,7 @@ int gc_update(MDBX_txn *txn, gcu_t *ctx) { continue; if (likely(rc == MDBX_RESULT_TRUE)) goto retry; + eASSERT(env, rc != MDBX_BACKLOG_DEPLETED); goto bailout; } tASSERT(txn, rid_result.err == MDBX_SUCCESS); @@ -23717,8 +23797,13 @@ int gc_update(MDBX_txn *txn, gcu_t *ctx) { prepare_backlog(txn, ctx); rc = cursor_put(&ctx->cursor, &key, &data, MDBX_RESERVE | MDBX_NOOVERWRITE); tASSERT(txn, pnl_check_allocated(txn->tw.repnl, txn->geo.first_unallocated - MDBX_ENABLE_REFUND)); - if (unlikely(rc != MDBX_SUCCESS)) + if (unlikely(rc != MDBX_SUCCESS)) { + if (rc == MDBX_BACKLOG_DEPLETED) { + ctx->backlog_adj += ctx->backlog_adj + 1; + goto retry; + } goto bailout; + } zeroize_reserved(env, data); ctx->reserved += chunk; @@ -23809,8 +23894,13 @@ int gc_update(MDBX_txn *txn, gcu_t *ctx) { chunk = left; } rc = cursor_put(&ctx->cursor, &key, &data, MDBX_CURRENT | MDBX_RESERVE); - if (unlikely(rc != MDBX_SUCCESS)) + if (unlikely(rc != MDBX_SUCCESS)) { + if (rc == MDBX_BACKLOG_DEPLETED) { + ctx->backlog_adj += ctx->backlog_adj + 1; + goto retry; + } goto bailout; + } zeroize_reserved(env, data); if (unlikely(txn->tw.loose_count || ctx->amount != MDBX_PNL_GETSIZE(txn->tw.repnl))) { @@ -23903,7 +23993,7 @@ int gc_update(MDBX_txn *txn, gcu_t *ctx) { return rc; } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 static void mdbx_init(void); static void mdbx_fini(void); @@ -23929,8 +24019,7 @@ BOOL APIENTRY DllMain(HANDLE module, DWORD reason, LPVOID reserved) #if !MDBX_MANUAL_MODULE_HANDLER static #endif /* !MDBX_MANUAL_MODULE_HANDLER */ - void NTAPI - mdbx_module_handler(PVOID module, DWORD reason, PVOID reserved) + void NTAPI mdbx_module_handler(PVOID module, DWORD reason, PVOID reserved) #endif /* MDBX_BUILD_SHARED_LIBRARY */ { (void)reserved; @@ -24124,6 +24213,8 @@ __dll_export #else #if defined(__ANDROID_API__) "Android" MDBX_STRINGIFY(__ANDROID_API__) + #elif defined(__OHOS__) + "Harmony OS" #elif defined(__linux__) || defined(__gnu_linux__) "Linux" #elif defined(EMSCRIPTEN) || defined(__EMSCRIPTEN__) @@ -24171,7 +24262,9 @@ __dll_export "-" - #if defined(__amd64__) + #if defined(__e2k__) || defined(__elbrus__) + "Elbrus" + #elif defined(__amd64__) "AMD64" #elif defined(__ia32__) "IA32" @@ -24208,6 +24301,8 @@ __dll_export "SPARC" #elif defined(__s390__) || defined(__s390) || defined(__zarch__) || defined(__zarch) "S390" + #elif defined(__riscv) || defined(__riscv__) || defined(__RISCV) || defined(__RISCV__) + "RISC-V (стеклянные бусы)" #else "UnknownARCH" #endif @@ -24274,6 +24369,7 @@ __dll_export #else /* Windows */ " MDBX_LOCKING=" MDBX_LOCKING_CONFIG " MDBX_USE_OFDLOCKS=" MDBX_USE_OFDLOCKS_CONFIG + " MDBX_USE_FALLOCATE=" MDBX_USE_FALLOCATE_CONFIG #endif /* !Windows */ " MDBX_CACHELINE_SIZE=" MDBX_STRINGIFY(MDBX_CACHELINE_SIZE) " MDBX_CPU_WRITEBACK_INCOHERENT=" MDBX_STRINGIFY(MDBX_CPU_WRITEBACK_INCOHERENT) @@ -24366,7 +24462,7 @@ const char *__asan_default_options(void) { #endif /* __SANITIZE_ADDRESS__ */ /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 #if !(defined(_WIN32) || defined(_WIN64)) /*----------------------------------------------------------------------------* @@ -24520,7 +24616,28 @@ static int lck_op(const mdbx_filehandle_t fd, int cmd, const int lck, const off_ } } -MDBX_INTERNAL int osal_lockfile(mdbx_filehandle_t fd, bool wait) { +static int lck_setlk_with3retries(const mdbx_filehandle_t fd, const int lck, const off_t offset, off_t len) { + assert(lck != F_UNLCK); + int retry_left = 3; +#if defined(__ANDROID_API__) + retry_left *= 3; +#endif /* Android */ + while (true) { + int rc = lck_op(fd, op_setlk, lck, offset, len); + if (!(rc == EAGAIN || rc == EACCES || rc == EBUSY || rc == EWOULDBLOCK || rc == EDEADLK) || --retry_left < 1) + return rc; +#if defined(__ANDROID_API__) + if (retry_left == 5 || retry_left == 3) { + usleep(1000 * 42 / 2); + continue; + } +#endif /* Android */ + if (osal_yield()) + return rc; + } +} + +int osal_lockfile(mdbx_filehandle_t fd, bool wait) { #if MDBX_USE_OFDLOCKS if (unlikely(op_setlk == 0)) choice_fcntl(); @@ -24528,7 +24645,7 @@ MDBX_INTERNAL int osal_lockfile(mdbx_filehandle_t fd, bool wait) { return lck_op(fd, wait ? op_setlkw : op_setlk, F_WRLCK, 0, OFF_T_MAX); } -MDBX_INTERNAL int lck_rpid_set(MDBX_env *env) { +int lck_rpid_set(MDBX_env *env) { assert(env->lck_mmap.fd != INVALID_HANDLE_VALUE); assert(env->pid > 0); if (unlikely(osal_getpid() != env->pid)) @@ -24536,13 +24653,13 @@ MDBX_INTERNAL int lck_rpid_set(MDBX_env *env) { return lck_op(env->lck_mmap.fd, op_setlk, F_WRLCK, env->pid, 1); } -MDBX_INTERNAL int lck_rpid_clear(MDBX_env *env) { +int lck_rpid_clear(MDBX_env *env) { assert(env->lck_mmap.fd != INVALID_HANDLE_VALUE); assert(env->pid > 0); return lck_op(env->lck_mmap.fd, op_setlk, F_UNLCK, env->pid, 1); } -MDBX_INTERNAL int lck_rpid_check(MDBX_env *env, uint32_t pid) { +int lck_rpid_check(MDBX_env *env, uint32_t pid) { assert(env->lck_mmap.fd != INVALID_HANDLE_VALUE); assert(pid > 0); return lck_op(env->lck_mmap.fd, op_getlk, F_WRLCK, pid, 1); @@ -24551,7 +24668,7 @@ MDBX_INTERNAL int lck_rpid_check(MDBX_env *env, uint32_t pid) { /*---------------------------------------------------------------------------*/ #if MDBX_LOCKING > MDBX_LOCKING_SYSV -MDBX_INTERNAL int lck_ipclock_stubinit(osal_ipclock_t *ipc) { +int lck_ipclock_stubinit(osal_ipclock_t *ipc) { #if MDBX_LOCKING == MDBX_LOCKING_POSIX1988 return sem_init(ipc, false, 1) ? errno : 0; #elif MDBX_LOCKING == MDBX_LOCKING_POSIX2001 || MDBX_LOCKING == MDBX_LOCKING_POSIX2008 @@ -24561,7 +24678,7 @@ MDBX_INTERNAL int lck_ipclock_stubinit(osal_ipclock_t *ipc) { #endif } -MDBX_INTERNAL int lck_ipclock_destroy(osal_ipclock_t *ipc) { +int lck_ipclock_destroy(osal_ipclock_t *ipc) { #if MDBX_LOCKING == MDBX_LOCKING_POSIX1988 return sem_destroy(ipc) ? errno : 0; #elif MDBX_LOCKING == MDBX_LOCKING_POSIX2001 || MDBX_LOCKING == MDBX_LOCKING_POSIX2008 @@ -24625,7 +24742,7 @@ static int check_fstat(MDBX_env *env) { return rc; } -__cold MDBX_INTERNAL int lck_seize(MDBX_env *env) { +__cold int lck_seize(MDBX_env *env) { assert(env->lazy_fd != INVALID_HANDLE_VALUE); if (unlikely(osal_getpid() != env->pid)) return MDBX_PANIC; @@ -24649,7 +24766,7 @@ __cold MDBX_INTERNAL int lck_seize(MDBX_env *env) { if (env->lck_mmap.fd == INVALID_HANDLE_VALUE) { /* LY: without-lck mode (e.g. exclusive or on read-only filesystem) */ - rc = lck_op(env->lazy_fd, op_setlk, (env->flags & MDBX_RDONLY) ? F_RDLCK : F_WRLCK, 0, OFF_T_MAX); + rc = lck_setlk_with3retries(env->lazy_fd, (env->flags & MDBX_RDONLY) ? F_RDLCK : F_WRLCK, 0, OFF_T_MAX); if (rc != MDBX_SUCCESS) { ERROR("%s, err %u", "without-lck", rc); eASSERT(env, MDBX_IS_ERROR(rc)); @@ -24657,9 +24774,6 @@ __cold MDBX_INTERNAL int lck_seize(MDBX_env *env) { } return MDBX_RESULT_TRUE /* Done: return with exclusive locking. */; } -#if defined(_POSIX_PRIORITY_SCHEDULING) && _POSIX_PRIORITY_SCHEDULING > 0 - sched_yield(); -#endif retry: if (rc == MDBX_RESULT_TRUE) { @@ -24672,14 +24786,14 @@ __cold MDBX_INTERNAL int lck_seize(MDBX_env *env) { } /* Firstly try to get exclusive locking. */ - rc = lck_op(env->lck_mmap.fd, op_setlk, F_WRLCK, 0, 1); + rc = lck_setlk_with3retries(env->lck_mmap.fd, F_WRLCK, 0, 1); if (rc == MDBX_SUCCESS) { rc = check_fstat(env); if (MDBX_IS_ERROR(rc)) return rc; continue_dxb_exclusive: - rc = lck_op(env->lazy_fd, op_setlk, (env->flags & MDBX_RDONLY) ? F_RDLCK : F_WRLCK, 0, OFF_T_MAX); + rc = lck_setlk_with3retries(env->lazy_fd, (env->flags & MDBX_RDONLY) ? F_RDLCK : F_WRLCK, 0, OFF_T_MAX); if (rc == MDBX_SUCCESS) return MDBX_RESULT_TRUE /* Done: return with exclusive locking. */; @@ -24727,7 +24841,7 @@ __cold MDBX_INTERNAL int lck_seize(MDBX_env *env) { } /* got shared, retry exclusive */ - rc = lck_op(env->lck_mmap.fd, op_setlk, F_WRLCK, 0, 1); + rc = lck_setlk_with3retries(env->lck_mmap.fd, F_WRLCK, 0, 1); if (rc == MDBX_SUCCESS) goto continue_dxb_exclusive; @@ -24738,7 +24852,7 @@ __cold MDBX_INTERNAL int lck_seize(MDBX_env *env) { } /* Lock against another process operating in without-lck or exclusive mode. */ - rc = lck_op(env->lazy_fd, op_setlk, (env->flags & MDBX_RDONLY) ? F_RDLCK : F_WRLCK, env->pid, 1); + rc = lck_setlk_with3retries(env->lazy_fd, (env->flags & MDBX_RDONLY) ? F_RDLCK : F_WRLCK, env->pid, 1); if (rc != MDBX_SUCCESS) { ERROR("%s, err %u", "lock-against-without-lck", rc); eASSERT(env, MDBX_IS_ERROR(rc)); @@ -24749,7 +24863,7 @@ __cold MDBX_INTERNAL int lck_seize(MDBX_env *env) { return MDBX_RESULT_FALSE; } -MDBX_INTERNAL int lck_downgrade(MDBX_env *env) { +int lck_downgrade(MDBX_env *env) { assert(env->lck_mmap.fd != INVALID_HANDLE_VALUE); if (unlikely(osal_getpid() != env->pid)) return MDBX_PANIC; @@ -24761,7 +24875,7 @@ MDBX_INTERNAL int lck_downgrade(MDBX_env *env) { rc = lck_op(env->lazy_fd, op_setlk, F_UNLCK, env->pid + 1, OFF_T_MAX - env->pid - 1); } if (rc == MDBX_SUCCESS) - rc = lck_op(env->lck_mmap.fd, op_setlk, F_RDLCK, 0, 1); + rc = lck_setlk_with3retries(env->lck_mmap.fd, F_RDLCK, 0, 1); if (unlikely(rc != 0)) { ERROR("%s, err %u", "lck", rc); assert(MDBX_IS_ERROR(rc)); @@ -24769,7 +24883,7 @@ MDBX_INTERNAL int lck_downgrade(MDBX_env *env) { return rc; } -MDBX_INTERNAL int lck_upgrade(MDBX_env *env, bool dont_wait) { +int lck_upgrade(MDBX_env *env, bool dont_wait) { assert(env->lck_mmap.fd != INVALID_HANDLE_VALUE); if (unlikely(osal_getpid() != env->pid)) return MDBX_PANIC; @@ -24780,10 +24894,10 @@ MDBX_INTERNAL int lck_upgrade(MDBX_env *env, bool dont_wait) { rc = (env->pid > 1) ? lck_op(env->lazy_fd, cmd, F_WRLCK, 0, env->pid - 1) : MDBX_SUCCESS; if (rc == MDBX_SUCCESS) { rc = lck_op(env->lazy_fd, cmd, F_WRLCK, env->pid + 1, OFF_T_MAX - env->pid - 1); - if (rc != MDBX_SUCCESS && env->pid > 1 && lck_op(env->lazy_fd, op_setlk, F_UNLCK, 0, env->pid - 1)) + if (rc != MDBX_SUCCESS && env->pid > 1 && lck_setlk_with3retries(env->lazy_fd, F_UNLCK, 0, env->pid - 1)) rc = MDBX_PANIC; } - if (rc != MDBX_SUCCESS && lck_op(env->lck_mmap.fd, op_setlk, F_RDLCK, 0, 1)) + if (rc != MDBX_SUCCESS && lck_setlk_with3retries(env->lck_mmap.fd, F_RDLCK, 0, 1)) rc = MDBX_PANIC; } if (unlikely(rc != 0)) { @@ -24793,7 +24907,7 @@ MDBX_INTERNAL int lck_upgrade(MDBX_env *env, bool dont_wait) { return rc; } -__cold MDBX_INTERNAL int lck_destroy(MDBX_env *env, MDBX_env *inprocess_neighbor, const uint32_t current_pid) { +__cold int lck_destroy(MDBX_env *env, MDBX_env *inprocess_neighbor, const uint32_t current_pid) { eASSERT(env, osal_getpid() == current_pid); int rc = MDBX_SUCCESS; struct stat lck_info; @@ -24882,7 +24996,7 @@ __cold MDBX_INTERNAL int lck_destroy(MDBX_env *env, MDBX_env *inprocess_neighbor /*---------------------------------------------------------------------------*/ -__cold MDBX_INTERNAL int lck_init(MDBX_env *env, MDBX_env *inprocess_neighbor, int global_uniqueness_flag) { +__cold int lck_init(MDBX_env *env, MDBX_env *inprocess_neighbor, int global_uniqueness_flag) { #if MDBX_LOCKING == MDBX_LOCKING_SYSV int semid = -1; /* don't initialize semaphores twice */ @@ -25092,7 +25206,7 @@ __cold static int osal_ipclock_failed(MDBX_env *env, osal_ipclock_t *ipc, const } #if defined(__ANDROID_API__) || defined(ANDROID) || defined(BIONIC) -MDBX_INTERNAL int osal_check_tid4bionic(void) { +int osal_check_tid4bionic(void) { /* avoid 32-bit Bionic bug/hang with 32-pit TID */ if (sizeof(pthread_mutex_t) < sizeof(pid_t) + sizeof(unsigned)) { pid_t tid = gettid(); @@ -25175,7 +25289,7 @@ static int osal_ipclock_unlock(MDBX_env *env, osal_ipclock_t *ipc) { return rc; } -MDBX_INTERNAL int lck_rdt_lock(MDBX_env *env) { +int lck_rdt_lock(MDBX_env *env) { TRACE("%s", ">>"); jitter4testing(true); int rc = osal_ipclock_lock(env, &env->lck->rdt_lock, false); @@ -25183,7 +25297,7 @@ MDBX_INTERNAL int lck_rdt_lock(MDBX_env *env) { return rc; } -MDBX_INTERNAL void lck_rdt_unlock(MDBX_env *env) { +void lck_rdt_unlock(MDBX_env *env) { TRACE("%s", ">>"); int err = osal_ipclock_unlock(env, &env->lck->rdt_lock); TRACE("<< err %d", err); @@ -25223,7 +25337,7 @@ void lck_txn_unlock(MDBX_env *env) { #endif /* !Windows LCK-implementation */ /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 #if defined(_WIN32) || defined(_WIN64) @@ -25362,7 +25476,7 @@ void lck_txn_unlock(MDBX_env *env) { #define LCK_LOWER LCK_LO_OFFSET, LCK_LO_LEN #define LCK_UPPER LCK_UP_OFFSET, LCK_UP_LEN -MDBX_INTERNAL int lck_rdt_lock(MDBX_env *env) { +int lck_rdt_lock(MDBX_env *env) { imports.srwl_AcquireShared(&env->remap_guard); if (env->lck_mmap.fd == INVALID_HANDLE_VALUE) return MDBX_SUCCESS; /* readonly database in readonly filesystem */ @@ -25380,7 +25494,7 @@ MDBX_INTERNAL int lck_rdt_lock(MDBX_env *env) { return rc; } -MDBX_INTERNAL void lck_rdt_unlock(MDBX_env *env) { +void lck_rdt_unlock(MDBX_env *env) { if (env->lck_mmap.fd != INVALID_HANDLE_VALUE && (env->flags & MDBX_EXCLUSIVE) == 0) { /* transition from S-E (locked) to S-? (used), e.g. unlock upper-part */ int err = funlock(env->lck_mmap.fd, LCK_UPPER); @@ -25390,7 +25504,7 @@ MDBX_INTERNAL void lck_rdt_unlock(MDBX_env *env) { imports.srwl_ReleaseShared(&env->remap_guard); } -MDBX_INTERNAL int osal_lockfile(mdbx_filehandle_t fd, bool wait) { +int osal_lockfile(mdbx_filehandle_t fd, bool wait) { return flock(fd, wait ? LCK_EXCLUSIVE | LCK_WAITFOR : LCK_EXCLUSIVE | LCK_DONTWAIT, 0, DXB_MAXLEN); } @@ -25426,7 +25540,7 @@ static int suspend_and_append(mdbx_handle_array_t **array, const DWORD ThreadId) return MDBX_SUCCESS; } -MDBX_INTERNAL int osal_suspend_threads_before_remap(MDBX_env *env, mdbx_handle_array_t **array) { +int osal_suspend_threads_before_remap(MDBX_env *env, mdbx_handle_array_t **array) { eASSERT(env, (env->flags & MDBX_NOSTICKYTHREADS) == 0); const uintptr_t CurrentTid = GetCurrentThreadId(); int rc; @@ -25493,7 +25607,7 @@ MDBX_INTERNAL int osal_suspend_threads_before_remap(MDBX_env *env, mdbx_handle_a return MDBX_SUCCESS; } -MDBX_INTERNAL int osal_resume_threads_after_remap(mdbx_handle_array_t *array) { +int osal_resume_threads_after_remap(mdbx_handle_array_t *array) { int rc = MDBX_SUCCESS; for (unsigned i = 0; i < array->count; ++i) { const HANDLE hThread = array->handles[i]; @@ -25629,7 +25743,7 @@ static int internal_seize_lck(HANDLE lfd) { return rc; } -MDBX_INTERNAL int lck_seize(MDBX_env *env) { +int lck_seize(MDBX_env *env) { const HANDLE fd4data = env->ioring.overlapped_fd ? env->ioring.overlapped_fd : env->lazy_fd; assert(fd4data != INVALID_HANDLE_VALUE); if (env->flags & MDBX_EXCLUSIVE) @@ -25671,7 +25785,7 @@ MDBX_INTERNAL int lck_seize(MDBX_env *env) { return rc; } -MDBX_INTERNAL int lck_downgrade(MDBX_env *env) { +int lck_downgrade(MDBX_env *env) { const HANDLE fd4data = env->ioring.overlapped_fd ? env->ioring.overlapped_fd : env->lazy_fd; /* Transite from exclusive-write state (E-E) to used (S-?) */ assert(fd4data != INVALID_HANDLE_VALUE); @@ -25701,7 +25815,7 @@ MDBX_INTERNAL int lck_downgrade(MDBX_env *env) { return MDBX_SUCCESS /* 5) now at S-? (used), done */; } -MDBX_INTERNAL int lck_upgrade(MDBX_env *env, bool dont_wait) { +int lck_upgrade(MDBX_env *env, bool dont_wait) { /* Transite from used state (S-?) to exclusive-write (E-E) */ assert(env->lck_mmap.fd != INVALID_HANDLE_VALUE); @@ -25735,7 +25849,7 @@ MDBX_INTERNAL int lck_upgrade(MDBX_env *env, bool dont_wait) { return MDBX_SUCCESS /* 6) now at E-E (exclusive-write), done */; } -MDBX_INTERNAL int lck_init(MDBX_env *env, MDBX_env *inprocess_neighbor, int global_uniqueness_flag) { +int lck_init(MDBX_env *env, MDBX_env *inprocess_neighbor, int global_uniqueness_flag) { (void)env; (void)inprocess_neighbor; (void)global_uniqueness_flag; @@ -25756,7 +25870,7 @@ MDBX_INTERNAL int lck_init(MDBX_env *env, MDBX_env *inprocess_neighbor, int glob return MDBX_SUCCESS; } -MDBX_INTERNAL int lck_destroy(MDBX_env *env, MDBX_env *inprocess_neighbor, const uint32_t current_pid) { +int lck_destroy(MDBX_env *env, MDBX_env *inprocess_neighbor, const uint32_t current_pid) { (void)current_pid; /* LY: should unmap before releasing the locks to avoid race condition and * STATUS_USER_MAPPED_FILE/ERROR_USER_MAPPED_FILE */ @@ -25768,7 +25882,7 @@ MDBX_INTERNAL int lck_destroy(MDBX_env *env, MDBX_env *inprocess_neighbor, const if (synced && !inprocess_neighbor && env->lck_mmap.fd != INVALID_HANDLE_VALUE && lck_upgrade(env, true) == MDBX_SUCCESS) /* this will fail if LCK is used/mmapped by other process(es) */ - osal_ftruncate(env->lck_mmap.fd, 0); + osal_fsetsize(env->lck_mmap.fd, 0); } lck_unlock(env); return MDBX_SUCCESS; @@ -25777,12 +25891,12 @@ MDBX_INTERNAL int lck_destroy(MDBX_env *env, MDBX_env *inprocess_neighbor, const /*----------------------------------------------------------------------------*/ /* reader checking (by pid) */ -MDBX_INTERNAL int lck_rpid_set(MDBX_env *env) { +int lck_rpid_set(MDBX_env *env) { (void)env; return MDBX_SUCCESS; } -MDBX_INTERNAL int lck_rpid_clear(MDBX_env *env) { +int lck_rpid_clear(MDBX_env *env) { (void)env; return MDBX_SUCCESS; } @@ -25793,7 +25907,7 @@ MDBX_INTERNAL int lck_rpid_clear(MDBX_env *env) { * MDBX_RESULT_TRUE, if pid is live (unable to acquire lock) * MDBX_RESULT_FALSE, if pid is dead (lock acquired) * or otherwise the errcode. */ -MDBX_INTERNAL int lck_rpid_check(MDBX_env *env, uint32_t pid) { +int lck_rpid_check(MDBX_env *env, uint32_t pid) { (void)env; HANDLE hProcess = OpenProcess(SYNCHRONIZE, FALSE, pid); int rc; @@ -25828,7 +25942,7 @@ MDBX_INTERNAL int lck_rpid_check(MDBX_env *env, uint32_t pid) { #endif /* Windows */ /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 __cold static int lck_setup_locked(MDBX_env *env) { int err = rthc_register(env); @@ -25889,9 +26003,9 @@ __cold static int lck_setup_locked(MDBX_env *env) { } env->max_readers = (maxreaders <= MDBX_READERS_LIMIT) ? (unsigned)maxreaders : (unsigned)MDBX_READERS_LIMIT; - err = - osal_mmap((env->flags & MDBX_EXCLUSIVE) | MDBX_WRITEMAP, &env->lck_mmap, (size_t)size, (size_t)size, - lck_seize_rc ? MMAP_OPTION_TRUNCATE | MMAP_OPTION_SEMAPHORE : MMAP_OPTION_SEMAPHORE, env->pathname.lck); + err = osal_mmap((env->flags & MDBX_EXCLUSIVE) | MDBX_WRITEMAP, &env->lck_mmap, (size_t)size, (size_t)size, + lck_seize_rc ? MMAP_OPTION_SETLENGTH | MMAP_OPTION_SEMAPHORE : MMAP_OPTION_SEMAPHORE, + env->pathname.lck); if (unlikely(err != MDBX_SUCCESS)) return err; @@ -26000,7 +26114,7 @@ void mincore_clean_cache(const MDBX_env *const env) { memset(env->lck->mincore_cache.begin, -1, sizeof(env->lck->mincore_cache.begin)); } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 __cold void debug_log_va(int level, const char *function, int line, const char *fmt, va_list args) { ENSURE(nullptr, osal_fastmutex_acquire(&globals.debug_lock) == 0); @@ -26248,7 +26362,7 @@ __cold int mdbx_setup_debug(MDBX_log_level_t level, MDBX_debug_flags_t flags, MD return setup_debug(level, flags, thunk, nullptr, 0); } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 typedef struct meta_snap { uint64_t txnid; @@ -26507,9 +26621,8 @@ __cold int meta_wipe_steady(MDBX_env *env, txnid_t inclusive_upto) { int meta_sync(const MDBX_env *env, const meta_ptr_t head) { eASSERT(env, atomic_load32(&env->lck->meta_sync_txnid, mo_Relaxed) != (uint32_t)head.txnid); - /* Функция может вызываться (в том числе) при (env->flags & - * MDBX_NOMETASYNC) == 0 и env->fd4meta == env->dsync_fd, например если - * предыдущая транзакция была выполненна с флагом MDBX_NOMETASYNC. */ + /* Функция может вызываться (в том числе) при (env->flags & MDBX_NOMETASYNC) == 0 и env->fd4meta == env->dsync_fd, + * например если предыдущая транзакция была выполненна с флагом MDBX_NOMETASYNC. */ int rc = MDBX_RESULT_TRUE; if (env->flags & MDBX_WRITEMAP) { @@ -26519,7 +26632,7 @@ int meta_sync(const MDBX_env *env, const meta_ptr_t head) { env->lck->pgops.msync.weak += 1; #endif /* MDBX_ENABLE_PGOP_STAT */ } else { -#if MDBX_ENABLE_PGOP_ST +#if MDBX_ENABLE_PGOP_STAT env->lck->pgops.wops.weak += 1; #endif /* MDBX_ENABLE_PGOP_STAT */ const page_t *page = data_page(head.ptr_c); @@ -26767,6 +26880,7 @@ __cold int meta_validate(MDBX_env *env, meta_t *const meta, const page_t *const } const uint64_t used_bytes = meta->geometry.first_unallocated * (uint64_t)meta->pagesize; + const uint64_t dxbsize_pages = env->dxb_mmap.filesize / (uint64_t)meta->pagesize; if (unlikely(used_bytes > env->dxb_mmap.filesize)) { /* Here could be a race with DB-shrinking performed by other process */ int err = osal_filesize(env->lazy_fd, &env->dxb_mmap.filesize); @@ -26809,11 +26923,16 @@ __cold int meta_validate(MDBX_env *env, meta_t *const meta, const page_t *const } } + /* It has already been verified above that the size of the allocated space does not exceed the file size. */ pgno_t geo_upper = meta->geometry.upper; uint64_t mapsize_max = geo_upper * (uint64_t)meta->pagesize; STATIC_ASSERT(MIN_MAPSIZE < MAX_MAPSIZE); if (unlikely(mapsize_max > MAX_MAPSIZE || - (MAX_PAGENO + 1) < ceil_powerof2((size_t)mapsize_max, globals.sys_pagesize) / (size_t)meta->pagesize)) { + (MAX_PAGENO + 1) < ceil_powerof2((/* допустимо, так как уже проверили что mapsize_max <= MAX_MAPSIZE, + * а следовательно mapsize_max помещается в size_t */ + size_t)mapsize_max, + globals.sys_allocation_granularity) / + meta->pagesize)) { if (mapsize_max > MAX_MAPSIZE64) { WARNING("meta[%u] has invalid max-mapsize (%" PRIu64 "), skip it", meta_number, mapsize_max); return MDBX_VERSION_MISMATCH; @@ -26824,30 +26943,40 @@ __cold int meta_validate(MDBX_env *env, meta_t *const meta, const page_t *const "but size of used space still acceptable (%" PRIu64 ")", meta_number, mapsize_max, used_bytes); geo_upper = (pgno_t)((mapsize_max = MAX_MAPSIZE) / meta->pagesize); - if (geo_upper > MAX_PAGENO + 1) { + if (geo_upper > MAX_PAGENO + 1) geo_upper = MAX_PAGENO + 1; - mapsize_max = geo_upper * (uint64_t)meta->pagesize; - } + } + + if (geo_upper < meta->geometry.first_unallocated) { + WARNING("meta[%u] has too less max-mapsize (%" PRIu64 "), " + "but size of allocated space still acceptable (%" PRIu64 ")", + meta_number, mapsize_max, used_bytes); + geo_upper = meta->geometry.first_unallocated; + } + if (meta->geometry.upper != geo_upper) { WARNING("meta[%u] consider get-%s pageno is %" PRIaPGNO " instead of wrong %" PRIaPGNO ", will be corrected on next commit(s)", meta_number, "upper", geo_upper, meta->geometry.upper); meta->geometry.upper = geo_upper; + mapsize_max = geo_upper * (uint64_t)meta->pagesize; } /* LY: check and silently put geometry.now into [geo.lower...geo.upper]. * - * Copy-with-compaction by old version of libmdbx could produce DB-file - * less than meta.geo.lower bound, in case actual filling is low or no data - * at all. This is not a problem as there is no damage or loss of data. - * Therefore it is better not to consider such situation as an error, but - * silently correct it. */ + * It has already been verified above that the size of the allocated space does not exceed the file size. + * + * Copy-with-compaction by old version of libmdbx could produce DB-file less than meta.geo.lower bound, in case actual + * filling is low or no data at all. This is not a problem as there is no damage or loss of data. Therefore it is + * better not to consider such situation as an error, but silently correct it. */ pgno_t geo_now = meta->geometry.now; if (geo_now < geo_lower) geo_now = geo_lower; - if (geo_now > geo_upper && meta->geometry.first_unallocated <= geo_upper) + if (geo_now < dxbsize_pages) + geo_now = dxbsize_pages; + if (geo_now > geo_upper) geo_now = geo_upper; - if (unlikely(meta->geometry.first_unallocated > geo_now)) { + if (unlikely(/* paranoid */ meta->geometry.first_unallocated > geo_now)) { WARNING("meta[%u] next-pageno (%" PRIaPGNO ") is beyond end-pgno (%" PRIaPGNO "), skip it", meta_number, meta->geometry.first_unallocated, geo_now); return MDBX_CORRUPTED; @@ -26866,7 +26995,7 @@ __cold int meta_validate(MDBX_env *env, meta_t *const meta, const page_t *const WARNING("meta[%u] has false-empty %s, skip it", meta_number, "GC"); return MDBX_CORRUPTED; } - } else if (unlikely(meta->trees.gc.root >= meta->geometry.first_unallocated)) { + } else if (unlikely(meta->trees.gc.root >= meta->geometry.first_unallocated || meta->trees.gc.root < NUM_METAS)) { WARNING("meta[%u] has invalid %s-root %" PRIaPGNO ", skip it", meta_number, "GC", meta->trees.gc.root); return MDBX_CORRUPTED; } @@ -26878,7 +27007,7 @@ __cold int meta_validate(MDBX_env *env, meta_t *const meta, const page_t *const WARNING("meta[%u] has false-empty %s", meta_number, "MainDB"); return MDBX_CORRUPTED; } - } else if (unlikely(meta->trees.main.root >= meta->geometry.first_unallocated)) { + } else if (unlikely(meta->trees.main.root >= meta->geometry.first_unallocated || meta->trees.main.root < NUM_METAS)) { WARNING("meta[%u] has invalid %s-root %" PRIaPGNO ", skip it", meta_number, "MainDB", meta->trees.main.root); return MDBX_CORRUPTED; } @@ -26902,7 +27031,7 @@ __cold int meta_validate_copy(MDBX_env *env, const meta_t *meta, meta_t *dest) { return meta_validate(env, dest, data_page(meta), bytes2pgno(env, ptr_dist(meta, env->dxb_mmap.base)), nullptr); } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 bsr_t mvcc_bind_slot(MDBX_env *env) { eASSERT(env, env->lck_mmap.lck); @@ -27105,7 +27234,7 @@ static bool pid_insert(uint32_t *list, uint32_t pid) { return true; } -__cold MDBX_INTERNAL int mvcc_cleanup_dead(MDBX_env *env, int rdt_locked, int *dead) { +__cold int mvcc_cleanup_dead(MDBX_env *env, int rdt_locked, int *dead) { int rc = check_env(env, true); if (unlikely(rc != MDBX_SUCCESS)) return rc; @@ -27316,7 +27445,7 @@ __cold txnid_t mvcc_kick_laggards(MDBX_env *env, const txnid_t straggler) { /// \copyright SPDX-License-Identifier: Apache-2.0 /// \note Please refer to the COPYRIGHT file for explanations license change, /// credits and acknowledgments. -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 __hot int __must_check_result node_add_dupfix(MDBX_cursor *mc, size_t indx, const MDBX_val *key) { page_t *mp = mc->pg[mc->top]; @@ -27677,7 +27806,7 @@ __hot struct node_search_result node_search(MDBX_cursor *mc, const MDBX_val *key return ret; } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 /// /// https://en.wikipedia.org/wiki/Operating_system_abstraction_layer @@ -27690,7 +27819,18 @@ __hot struct node_search_result node_search(MDBX_cursor *mc, const MDBX_val *key #include #endif -static int waitstatus2errcode(DWORD result) { +/* Map a result from an NTAPI call to WIN32 error code. */ +static int osal_ntstatus2errcode(NTSTATUS status) { + DWORD dummy; + OVERLAPPED ov; + memset(&ov, 0, sizeof(ov)); + ov.Internal = status; + /* Zap: '_Param_(1)' could be '0' */ + MDBX_SUPPRESS_GOOFY_MSVC_ANALYZER(6387); + return GetOverlappedResult(nullptr, &ov, &dummy, FALSE) ? MDBX_SUCCESS : (int)GetLastError(); +} + +MDBX_INTERNAL int osal_waitstatus2errcode(DWORD result) { switch (result) { case WAIT_OBJECT_0: return MDBX_SUCCESS; @@ -27703,21 +27843,10 @@ static int waitstatus2errcode(DWORD result) { case WAIT_TIMEOUT: return ERROR_TIMEOUT; default: - return ERROR_UNHANDLED_ERROR; + return osal_ntstatus2errcode(result); } } -/* Map a result from an NTAPI call to WIN32 error code. */ -static int ntstatus2errcode(NTSTATUS status) { - DWORD dummy; - OVERLAPPED ov; - memset(&ov, 0, sizeof(ov)); - ov.Internal = status; - /* Zap: '_Param_(1)' could be '0' */ - MDBX_SUPPRESS_GOOFY_MSVC_ANALYZER(6387); - return GetOverlappedResult(nullptr, &ov, &dummy, FALSE) ? MDBX_SUCCESS : (int)GetLastError(); -} - /* We use native NT APIs to setup the memory map, so that we can * let the DB file grow incrementally instead of always preallocating * the full size. These APIs are defined in and @@ -27936,7 +28065,7 @@ __cold void mdbx_panic(const char *fmt, ...) { if (IsDebuggerPresent()) DebugBreak(); #endif - FatalExit(ERROR_UNHANDLED_ERROR); + FatalExit(ERROR_UNHANDLED_EXCEPTION); #else __assert_fail(const_message, "mdbx", 0, "panic"); abort(); @@ -27947,7 +28076,7 @@ __cold void mdbx_panic(const char *fmt, ...) { /*----------------------------------------------------------------------------*/ #ifndef osal_vasprintf -MDBX_INTERNAL int osal_vasprintf(char **strp, const char *fmt, va_list ap) { +int osal_vasprintf(char **strp, const char *fmt, va_list ap) { va_list ones; va_copy(ones, ap); const int needed = vsnprintf(nullptr, 0, fmt, ones); @@ -27979,7 +28108,7 @@ MDBX_INTERNAL int osal_vasprintf(char **strp, const char *fmt, va_list ap) { #endif /* osal_vasprintf */ #ifndef osal_asprintf -MDBX_INTERNAL int osal_asprintf(char **strp, const char *fmt, ...) { +int osal_asprintf(char **strp, const char *fmt, ...) { va_list ap; va_start(ap, fmt); const int rc = osal_vasprintf(strp, fmt, ap); @@ -27989,7 +28118,7 @@ MDBX_INTERNAL int osal_asprintf(char **strp, const char *fmt, ...) { #endif /* osal_asprintf */ #ifndef osal_memalign_alloc -MDBX_INTERNAL int osal_memalign_alloc(size_t alignment, size_t bytes, void **result) { +int osal_memalign_alloc(size_t alignment, size_t bytes, void **result) { assert(is_powerof2(alignment) && alignment >= sizeof(void *)); #if defined(_WIN32) || defined(_WIN64) (void)alignment; @@ -28011,7 +28140,7 @@ MDBX_INTERNAL int osal_memalign_alloc(size_t alignment, size_t bytes, void **res #endif /* osal_memalign_alloc */ #ifndef osal_memalign_free -MDBX_INTERNAL void osal_memalign_free(void *ptr) { +void osal_memalign_free(void *ptr) { #if defined(_WIN32) || defined(_WIN64) VirtualFree(ptr, 0, MEM_RELEASE); #else @@ -28034,7 +28163,7 @@ char *osal_strdup(const char *str) { /*----------------------------------------------------------------------------*/ -MDBX_INTERNAL int osal_condpair_init(osal_condpair_t *condpair) { +int osal_condpair_init(osal_condpair_t *condpair) { int rc; memset(condpair, 0, sizeof(osal_condpair_t)); #if defined(_WIN32) || defined(_WIN64) @@ -28073,7 +28202,7 @@ MDBX_INTERNAL int osal_condpair_init(osal_condpair_t *condpair) { return rc; } -MDBX_INTERNAL int osal_condpair_destroy(osal_condpair_t *condpair) { +int osal_condpair_destroy(osal_condpair_t *condpair) { #if defined(_WIN32) || defined(_WIN64) int rc = CloseHandle(condpair->mutex) ? MDBX_SUCCESS : (int)GetLastError(); rc = CloseHandle(condpair->event[0]) ? rc : (int)GetLastError(); @@ -28087,16 +28216,16 @@ MDBX_INTERNAL int osal_condpair_destroy(osal_condpair_t *condpair) { return rc; } -MDBX_INTERNAL int osal_condpair_lock(osal_condpair_t *condpair) { +int osal_condpair_lock(osal_condpair_t *condpair) { #if defined(_WIN32) || defined(_WIN64) DWORD code = WaitForSingleObject(condpair->mutex, INFINITE); - return waitstatus2errcode(code); + return osal_waitstatus2errcode(code); #else return osal_pthread_mutex_lock(&condpair->mutex); #endif } -MDBX_INTERNAL int osal_condpair_unlock(osal_condpair_t *condpair) { +int osal_condpair_unlock(osal_condpair_t *condpair) { #if defined(_WIN32) || defined(_WIN64) return ReleaseMutex(condpair->mutex) ? MDBX_SUCCESS : (int)GetLastError(); #else @@ -28104,7 +28233,7 @@ MDBX_INTERNAL int osal_condpair_unlock(osal_condpair_t *condpair) { #endif } -MDBX_INTERNAL int osal_condpair_signal(osal_condpair_t *condpair, bool part) { +int osal_condpair_signal(osal_condpair_t *condpair, bool part) { #if defined(_WIN32) || defined(_WIN64) return SetEvent(condpair->event[part]) ? MDBX_SUCCESS : (int)GetLastError(); #else @@ -28112,7 +28241,7 @@ MDBX_INTERNAL int osal_condpair_signal(osal_condpair_t *condpair, bool part) { #endif } -MDBX_INTERNAL int osal_condpair_wait(osal_condpair_t *condpair, bool part) { +int osal_condpair_wait(osal_condpair_t *condpair, bool part) { #if defined(_WIN32) || defined(_WIN64) DWORD code = SignalObjectAndWait(condpair->mutex, condpair->event[part], INFINITE, FALSE); if (code == WAIT_OBJECT_0) { @@ -28120,7 +28249,7 @@ MDBX_INTERNAL int osal_condpair_wait(osal_condpair_t *condpair, bool part) { if (code == WAIT_OBJECT_0) return MDBX_SUCCESS; } - return waitstatus2errcode(code); + return osal_waitstatus2errcode(code); #else return pthread_cond_wait(&condpair->cond[part], &condpair->mutex); #endif @@ -28128,7 +28257,7 @@ MDBX_INTERNAL int osal_condpair_wait(osal_condpair_t *condpair, bool part) { /*----------------------------------------------------------------------------*/ -MDBX_INTERNAL int osal_fastmutex_init(osal_fastmutex_t *fastmutex) { +int osal_fastmutex_init(osal_fastmutex_t *fastmutex) { #if defined(_WIN32) || defined(_WIN64) InitializeCriticalSection(fastmutex); return MDBX_SUCCESS; @@ -28147,7 +28276,7 @@ MDBX_INTERNAL int osal_fastmutex_init(osal_fastmutex_t *fastmutex) { #endif } -MDBX_INTERNAL int osal_fastmutex_destroy(osal_fastmutex_t *fastmutex) { +int osal_fastmutex_destroy(osal_fastmutex_t *fastmutex) { #if defined(_WIN32) || defined(_WIN64) DeleteCriticalSection(fastmutex); return MDBX_SUCCESS; @@ -28156,7 +28285,7 @@ MDBX_INTERNAL int osal_fastmutex_destroy(osal_fastmutex_t *fastmutex) { #endif } -MDBX_INTERNAL int osal_fastmutex_acquire(osal_fastmutex_t *fastmutex) { +int osal_fastmutex_acquire(osal_fastmutex_t *fastmutex) { #if defined(_WIN32) || defined(_WIN64) __try { EnterCriticalSection(fastmutex); @@ -28171,7 +28300,7 @@ MDBX_INTERNAL int osal_fastmutex_acquire(osal_fastmutex_t *fastmutex) { #endif } -MDBX_INTERNAL int osal_fastmutex_release(osal_fastmutex_t *fastmutex) { +int osal_fastmutex_release(osal_fastmutex_t *fastmutex) { #if defined(_WIN32) || defined(_WIN64) LeaveCriticalSection(fastmutex); return MDBX_SUCCESS; @@ -28184,7 +28313,7 @@ MDBX_INTERNAL int osal_fastmutex_release(osal_fastmutex_t *fastmutex) { #if defined(_WIN32) || defined(_WIN64) -MDBX_INTERNAL int osal_mb2w(const char *const src, wchar_t **const pdst) { +int osal_mb2w(const char *const src, wchar_t **const pdst) { const size_t dst_wlen = MultiByteToWideChar(CP_THREAD_ACP, MB_ERR_INVALID_CHARS, src, -1, nullptr, 0); wchar_t *dst = *pdst; int rc = ERROR_INVALID_NAME; @@ -28254,10 +28383,10 @@ static size_t osal_iov_max; #undef OSAL_IOV_MAX #endif /* OSAL_IOV_MAX */ -MDBX_INTERNAL int osal_ioring_create(osal_ioring_t *ior +int osal_ioring_create(osal_ioring_t *ior #if defined(_WIN32) || defined(_WIN64) - , - bool enable_direct, mdbx_filehandle_t overlapped_fd + , + bool enable_direct, mdbx_filehandle_t overlapped_fd #endif /* Windows */ ) { memset(ior, 0, sizeof(osal_ioring_t)); @@ -28300,7 +28429,7 @@ static inline ior_item_t *ior_next(ior_item_t *item, size_t sgvcnt) { #endif } -MDBX_INTERNAL int osal_ioring_add(osal_ioring_t *ior, const size_t offset, void *data, const size_t bytes) { +int osal_ioring_add(osal_ioring_t *ior, const size_t offset, void *data, const size_t bytes) { assert(bytes && data); assert(bytes % MDBX_MIN_PAGESIZE == 0 && bytes <= MAX_WRITE); assert(offset % MDBX_MIN_PAGESIZE == 0 && offset + (uint64_t)bytes <= MAX_MAPSIZE); @@ -28412,8 +28541,8 @@ MDBX_INTERNAL int osal_ioring_add(osal_ioring_t *ior, const size_t offset, void return MDBX_SUCCESS; } -MDBX_INTERNAL void osal_ioring_walk(osal_ioring_t *ior, iov_ctx_t *ctx, - void (*callback)(iov_ctx_t *ctx, size_t offset, void *data, size_t bytes)) { +void osal_ioring_walk(osal_ioring_t *ior, iov_ctx_t *ctx, + void (*callback)(iov_ctx_t *ctx, size_t offset, void *data, size_t bytes)) { for (ior_item_t *item = ior->pool; item <= ior->last;) { #if defined(_WIN32) || defined(_WIN64) size_t offset = ior_offset(item); @@ -28454,7 +28583,7 @@ MDBX_INTERNAL void osal_ioring_walk(osal_ioring_t *ior, iov_ctx_t *ctx, } } -MDBX_INTERNAL osal_ioring_write_result_t osal_ioring_write(osal_ioring_t *ior, mdbx_filehandle_t fd) { +osal_ioring_write_result_t osal_ioring_write(osal_ioring_t *ior, mdbx_filehandle_t fd) { osal_ioring_write_result_t r = {MDBX_SUCCESS, 0}; #if defined(_WIN32) || defined(_WIN64) @@ -28675,7 +28804,7 @@ MDBX_INTERNAL osal_ioring_write_result_t osal_ioring_write(osal_ioring_t *ior, m return r; } -MDBX_INTERNAL void osal_ioring_reset(osal_ioring_t *ior) { +void osal_ioring_reset(osal_ioring_t *ior) { #if defined(_WIN32) || defined(_WIN64) if (ior->last) { for (ior_item_t *item = ior->pool; item <= ior->last;) { @@ -28717,7 +28846,7 @@ static void ior_cleanup(osal_ioring_t *ior, const size_t since) { #endif /* Windows */ } -MDBX_INTERNAL int osal_ioring_resize(osal_ioring_t *ior, size_t items) { +int osal_ioring_resize(osal_ioring_t *ior, size_t items) { assert(items > 0 && items < INT_MAX / sizeof(ior_item_t)); #if defined(_WIN32) || defined(_WIN64) if (ior->state & IOR_STATE_LOCKED) @@ -28769,7 +28898,7 @@ MDBX_INTERNAL int osal_ioring_resize(osal_ioring_t *ior, size_t items) { return MDBX_SUCCESS; } -MDBX_INTERNAL void osal_ioring_destroy(osal_ioring_t *ior) { +void osal_ioring_destroy(osal_ioring_t *ior) { if (ior->allocated) ior_cleanup(ior, 0); #if defined(_WIN32) || defined(_WIN64) @@ -28786,7 +28915,7 @@ MDBX_INTERNAL void osal_ioring_destroy(osal_ioring_t *ior) { /*----------------------------------------------------------------------------*/ -MDBX_INTERNAL int osal_removefile(const pathchar_t *pathname) { +int osal_removefile(const pathchar_t *pathname) { #if defined(_WIN32) || defined(_WIN64) return DeleteFileW(pathname) ? MDBX_SUCCESS : (int)GetLastError(); #else @@ -28798,7 +28927,7 @@ MDBX_INTERNAL int osal_removefile(const pathchar_t *pathname) { static bool is_valid_fd(int fd) { return !(isatty(fd) < 0 && errno == EBADF); } #endif /*! Windows */ -MDBX_INTERNAL int osal_removedirectory(const pathchar_t *pathname) { +int osal_removedirectory(const pathchar_t *pathname) { #if defined(_WIN32) || defined(_WIN64) return RemoveDirectoryW(pathname) ? MDBX_SUCCESS : (int)GetLastError(); #else @@ -28806,7 +28935,7 @@ MDBX_INTERNAL int osal_removedirectory(const pathchar_t *pathname) { #endif } -MDBX_INTERNAL int osal_fileexists(const pathchar_t *pathname) { +int osal_fileexists(const pathchar_t *pathname) { #if defined(_WIN32) || defined(_WIN64) if (GetFileAttributesW(pathname) != INVALID_FILE_ATTRIBUTES) return MDBX_RESULT_TRUE; @@ -28820,7 +28949,7 @@ MDBX_INTERNAL int osal_fileexists(const pathchar_t *pathname) { #endif } -MDBX_INTERNAL pathchar_t *osal_fileext(const pathchar_t *pathname, size_t len) { +pathchar_t *osal_fileext(const pathchar_t *pathname, size_t len) { const pathchar_t *ext = nullptr; for (size_t i = 0; i < len && pathname[i]; i++) if (pathname[i] == '.') @@ -28830,7 +28959,7 @@ MDBX_INTERNAL pathchar_t *osal_fileext(const pathchar_t *pathname, size_t len) { return (pathchar_t *)ext; } -MDBX_INTERNAL bool osal_pathequal(const pathchar_t *l, const pathchar_t *r, size_t len) { +bool osal_pathequal(const pathchar_t *l, const pathchar_t *r, size_t len) { #if defined(_WIN32) || defined(_WIN64) for (size_t i = 0; i < len; ++i) { pathchar_t a = l[i]; @@ -28846,8 +28975,8 @@ MDBX_INTERNAL bool osal_pathequal(const pathchar_t *l, const pathchar_t *r, size #endif } -MDBX_INTERNAL int osal_openfile(const enum osal_openfile_purpose purpose, const MDBX_env *env, - const pathchar_t *pathname, mdbx_filehandle_t *fd, mdbx_mode_t unix_mode_bits) { +int osal_openfile(const enum osal_openfile_purpose purpose, const MDBX_env *env, const pathchar_t *pathname, + mdbx_filehandle_t *fd, mdbx_mode_t unix_mode_bits) { *fd = INVALID_HANDLE_VALUE; #if defined(_WIN32) || defined(_WIN64) @@ -29056,7 +29185,7 @@ MDBX_INTERNAL int osal_openfile(const enum osal_openfile_purpose purpose, const return MDBX_SUCCESS; } -MDBX_INTERNAL int osal_closefile(mdbx_filehandle_t fd) { +int osal_closefile(mdbx_filehandle_t fd) { #if defined(_WIN32) || defined(_WIN64) return CloseHandle(fd) ? MDBX_SUCCESS : (int)GetLastError(); #else @@ -29065,7 +29194,7 @@ MDBX_INTERNAL int osal_closefile(mdbx_filehandle_t fd) { #endif } -MDBX_INTERNAL int osal_pread(mdbx_filehandle_t fd, void *buf, size_t bytes, uint64_t offset) { +int osal_pread(mdbx_filehandle_t fd, void *buf, size_t bytes, uint64_t offset) { if (bytes > MAX_WRITE) return MDBX_EINVAL; #if defined(_WIN32) || defined(_WIN64) @@ -29076,8 +29205,14 @@ MDBX_INTERNAL int osal_pread(mdbx_filehandle_t fd, void *buf, size_t bytes, uint DWORD read = 0; if (unlikely(!ReadFile(fd, buf, (DWORD)bytes, &read, &ov))) { - int rc = (int)GetLastError(); - return (rc == MDBX_SUCCESS) ? /* paranoia */ ERROR_READ_FAULT : rc; + int err = (int)GetLastError(); + if (err != ERROR_IO_PENDING) + return (err == MDBX_SUCCESS) ? /* paranoia */ ERROR_READ_FAULT : err; + if (!GetOverlappedResult(fd, &ov, &read, true)) { + err = (int)GetLastError(); + CancelIo(fd); + return (err == MDBX_SUCCESS) ? /* paranoia */ ERROR_READ_FAULT : err; + } } #else STATIC_ASSERT_MSG(sizeof(off_t) >= sizeof(size_t), "libmdbx requires 64-bit file I/O on 64-bit systems"); @@ -29090,7 +29225,7 @@ MDBX_INTERNAL int osal_pread(mdbx_filehandle_t fd, void *buf, size_t bytes, uint return (bytes == (size_t)read) ? MDBX_SUCCESS : MDBX_ENODATA; } -MDBX_INTERNAL int osal_pwrite(mdbx_filehandle_t fd, const void *buf, size_t bytes, uint64_t offset) { +int osal_pwrite(mdbx_filehandle_t fd, const void *buf, size_t bytes, uint64_t offset) { while (true) { #if defined(_WIN32) || defined(_WIN64) OVERLAPPED ov; @@ -29121,7 +29256,7 @@ MDBX_INTERNAL int osal_pwrite(mdbx_filehandle_t fd, const void *buf, size_t byte } } -MDBX_INTERNAL int osal_write(mdbx_filehandle_t fd, const void *buf, size_t bytes) { +int osal_write(mdbx_filehandle_t fd, const void *buf, size_t bytes) { while (true) { #if defined(_WIN32) || defined(_WIN64) DWORD written; @@ -29174,7 +29309,7 @@ int osal_pwritev(mdbx_filehandle_t fd, struct iovec *iov, size_t sgvcnt, uint64_ #endif } -MDBX_INTERNAL int osal_fsync(mdbx_filehandle_t fd, enum osal_syncmode_bits mode_bits) { +int osal_fsync(mdbx_filehandle_t fd, enum osal_syncmode_bits mode_bits) { #if defined(_WIN32) || defined(_WIN64) if ((mode_bits & (MDBX_SYNC_DATA | MDBX_SYNC_IODQ)) && !FlushFileBuffers(fd)) return (int)GetLastError(); @@ -29237,7 +29372,7 @@ int osal_filesize(mdbx_filehandle_t fd, uint64_t *length) { return MDBX_SUCCESS; } -MDBX_INTERNAL int osal_is_pipe(mdbx_filehandle_t fd) { +int osal_is_pipe(mdbx_filehandle_t fd) { #if defined(_WIN32) || defined(_WIN64) switch (GetFileType(fd)) { case FILE_TYPE_DISK: @@ -29268,7 +29403,7 @@ MDBX_INTERNAL int osal_is_pipe(mdbx_filehandle_t fd) { #endif } -MDBX_INTERNAL int osal_ftruncate(mdbx_filehandle_t fd, uint64_t length) { +int osal_fsetsize(mdbx_filehandle_t fd, const uint64_t length) { #if defined(_WIN32) || defined(_WIN64) if (imports.SetFileInformationByHandle) { FILE_END_OF_FILE_INFO EndOfFileInfo; @@ -29283,11 +29418,61 @@ MDBX_INTERNAL int osal_ftruncate(mdbx_filehandle_t fd, uint64_t length) { } #else STATIC_ASSERT_MSG(sizeof(off_t) >= sizeof(size_t), "libmdbx requires 64-bit file I/O on 64-bit systems"); - return ftruncate(fd, length) == 0 ? MDBX_SUCCESS : errno; + +#if MDBX_USE_FALLOCATE + struct stat info; + if (unlikely(fstat(fd, &info))) + return errno; + + const uint64_t allocated = UINT64_C(512) * info.st_blocks; + if (length > allocated) { +#if defined(__APPLE__) + fstore_t store = { + .fst_flags = F_ALLOCATECONTIG, .fst_posmode = F_PEOFPOSMODE, .fst_offset = 0, .fst_length = length}; + int err = MDBX_SUCCESS; + if (fcntl(fd, F_PREALLOCATE, &store)) { + /* TODO: implement step-by-step allocation in chunks of 16384, 8192, 4094, 2048, 1024 Kb */ + store.fst_flags = F_ALLOCATEALL; + if (fcntl(fd, F_PREALLOCATE, &store)) + err = errno; + } +#elif defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L + const int err = posix_fallocate(fd, 0, length); + if (!err && length > (uint64_t)info.st_size) + info.st_size = length /* posix_fallocate() extends the file */; +#else + const int err = fallocate(fd, 0, 0, length) ? errno : MDBX_SUCCESS; + if (!err && length > (uint64_t)info.st_size) + info.st_size = length /* fallocate() extends the file */; #endif + if (unlikely(err) && ignore_enosys_and_eremote(err) != MDBX_RESULT_TRUE) { + /* Workaround for testing: ignore ENOSPC for TMPFS/RAMFS. + * This is insignificant for production, but it helps in some tests using /dev/shm inside docker/containers. */ + if (err != ENOSPC || osal_check_fs_incore(fd) != MDBX_RESULT_TRUE) + return err; + } + } + + if (length == (uint64_t)info.st_size) + return MDBX_SUCCESS; +#endif + +#if defined(__linux__) || defined(__gnu_linux__) + if (globals.linux_kernel_version < 0x05110000 && globals.linux_kernel_version >= 0x050a0000) { + struct statfs statfs_info; + if (fstatfs(fd, &statfs_info)) + return errno; + if (statfs_info.f_type == 0xEF53 /* EXT4_SUPER_MAGIC */ && unlikely(fdatasync(fd))) + return errno; + } +#endif /* Linux */ + + return unlikely(ftruncate(fd, length)) ? errno : MDBX_SUCCESS; + +#endif /* !Windows */ } -MDBX_INTERNAL int osal_fseek(mdbx_filehandle_t fd, uint64_t pos) { +int osal_fseek(mdbx_filehandle_t fd, uint64_t pos) { #if defined(_WIN32) || defined(_WIN64) LARGE_INTEGER li; li.QuadPart = pos; @@ -29300,8 +29485,7 @@ MDBX_INTERNAL int osal_fseek(mdbx_filehandle_t fd, uint64_t pos) { /*----------------------------------------------------------------------------*/ -MDBX_INTERNAL int osal_thread_create(osal_thread_t *thread, THREAD_RESULT(THREAD_CALL *start_routine)(void *), - void *arg) { +int osal_thread_create(osal_thread_t *thread, THREAD_RESULT(THREAD_CALL *start_routine)(void *), void *arg) { #if defined(_WIN32) || defined(_WIN64) *thread = CreateThread(nullptr, 0, start_routine, arg, 0, nullptr); return *thread ? MDBX_SUCCESS : (int)GetLastError(); @@ -29310,10 +29494,10 @@ MDBX_INTERNAL int osal_thread_create(osal_thread_t *thread, THREAD_RESULT(THREAD #endif } -MDBX_INTERNAL int osal_thread_join(osal_thread_t thread) { +int osal_thread_join(osal_thread_t thread) { #if defined(_WIN32) || defined(_WIN64) DWORD code = WaitForSingleObject(thread, INFINITE); - return waitstatus2errcode(code); + return osal_waitstatus2errcode(code); #else void *unused_retval = &unused_retval; return pthread_join(thread, &unused_retval); @@ -29322,7 +29506,7 @@ MDBX_INTERNAL int osal_thread_join(osal_thread_t thread) { /*----------------------------------------------------------------------------*/ -MDBX_INTERNAL int osal_msync(const osal_mmap_t *map, size_t offset, size_t length, enum osal_syncmode_bits mode_bits) { +int osal_msync(const osal_mmap_t *map, size_t offset, size_t length, enum osal_syncmode_bits mode_bits) { if (!MDBX_MMAP_NEEDS_JOLT && mode_bits == MDBX_SYNC_NONE) return MDBX_SUCCESS; @@ -29353,7 +29537,7 @@ MDBX_INTERNAL int osal_msync(const osal_mmap_t *map, size_t offset, size_t lengt return MDBX_SUCCESS; } -MDBX_INTERNAL int osal_check_fs_rdonly(mdbx_filehandle_t handle, const pathchar_t *pathname, int err) { +int osal_check_fs_rdonly(mdbx_filehandle_t handle, const pathchar_t *pathname, int err) { #if defined(_WIN32) || defined(_WIN64) (void)pathname; (void)err; @@ -29380,7 +29564,7 @@ MDBX_INTERNAL int osal_check_fs_rdonly(mdbx_filehandle_t handle, const pathchar_ return MDBX_SUCCESS; } -MDBX_INTERNAL int osal_check_fs_incore(mdbx_filehandle_t handle) { +int osal_check_fs_incore(mdbx_filehandle_t handle) { #if defined(_WIN32) || defined(_WIN64) (void)handle; #else @@ -29421,7 +29605,7 @@ MDBX_INTERNAL int osal_check_fs_incore(mdbx_filehandle_t handle) { return MDBX_RESULT_FALSE; } -MDBX_INTERNAL int osal_check_fs_local(mdbx_filehandle_t handle, int flags) { +int osal_check_fs_local(mdbx_filehandle_t handle, int flags) { #if defined(_WIN32) || defined(_WIN64) if (globals.running_under_Wine && !(flags & MDBX_EXCLUSIVE)) return ERROR_NOT_CAPABLE /* workaround for Wine */; @@ -29458,7 +29642,7 @@ MDBX_INTERNAL int osal_check_fs_local(mdbx_filehandle_t handle, int flags) { return MDBX_EREMOTE; } else if (rc != STATUS_OBJECT_NOT_EXTERNALLY_BACKED && rc != STATUS_INVALID_DEVICE_REQUEST && rc != STATUS_NOT_SUPPORTED) - return ntstatus2errcode(rc); + return osal_ntstatus2errcode(rc); } if (imports.GetVolumeInformationByHandleW && imports.GetFinalPathNameByHandleW) { @@ -29644,7 +29828,7 @@ MDBX_INTERNAL int osal_check_fs_local(mdbx_filehandle_t handle, int flags) { #endif /* ST/MNT_LOCAL */ #ifdef ST_EXPORTED - if ((st_flags & ST_EXPORTED) != 0 && !(flags & (MDBX_RDONLY | MDBX_EXCLUSIVE)))) + if ((st_flags & ST_EXPORTED) != 0 && !(flags & (MDBX_RDONLY | MDBX_EXCLUSIVE))) return MDBX_RESULT_TRUE; #elif defined(MNT_EXPORTED) if ((mnt_flags & MNT_EXPORTED) != 0 && !(flags & (MDBX_RDONLY | MDBX_EXCLUSIVE))) @@ -29699,8 +29883,8 @@ static int check_mmap_limit(const size_t limit) { return MDBX_SUCCESS; } -MDBX_INTERNAL int osal_mmap(const int flags, osal_mmap_t *map, size_t size, const size_t limit, const unsigned options, - const pathchar_t *pathname4logging) { +int osal_mmap(const int flags, osal_mmap_t *map, size_t size, const size_t limit, const unsigned options, + const pathchar_t *pathname4logging) { assert(size <= limit); map->limit = 0; map->current = 0; @@ -29737,9 +29921,9 @@ MDBX_INTERNAL int osal_mmap(const int flags, osal_mmap_t *map, size_t size, cons if (unlikely(err != MDBX_SUCCESS)) return err; - if ((flags & MDBX_RDONLY) == 0 && (options & MMAP_OPTION_TRUNCATE) != 0) { - err = osal_ftruncate(map->fd, size); - VERBOSE("ftruncate %zu, err %d", size, err); + if ((flags & MDBX_RDONLY) == 0 && (options & MMAP_OPTION_SETLENGTH) != 0) { + err = osal_fsetsize(map->fd, size); + VERBOSE("osal_fsetsize %zu, err %d", size, err); if (err != MDBX_SUCCESS) return err; map->filesize = size; @@ -29775,7 +29959,7 @@ MDBX_INTERNAL int osal_mmap(const int flags, osal_mmap_t *map, size_t size, cons (flags & MDBX_RDONLY) ? PAGE_READONLY : PAGE_READWRITE, /* AllocationAttributes */ SEC_RESERVE, map->fd); if (!NT_SUCCESS(err)) - return ntstatus2errcode(err); + return osal_ntstatus2errcode(err); SIZE_T ViewSize = (flags & MDBX_RDONLY) ? 0 : globals.running_under_Wine ? size : limit; err = NtMapViewOfSection(map->section, GetCurrentProcess(), &map->base, @@ -29790,7 +29974,7 @@ MDBX_INTERNAL int osal_mmap(const int flags, osal_mmap_t *map, size_t size, cons NtClose(map->section); map->section = 0; map->base = nullptr; - return ntstatus2errcode(err); + return osal_ntstatus2errcode(err); } assert(map->base != MAP_FAILED); @@ -29852,7 +30036,7 @@ MDBX_INTERNAL int osal_mmap(const int flags, osal_mmap_t *map, size_t size, cons return MDBX_SUCCESS; } -MDBX_INTERNAL int osal_munmap(osal_mmap_t *map) { +void osal_munmap(osal_mmap_t *map) { VALGRIND_MAKE_MEM_NOACCESS(map->base, map->current); /* Unpoisoning is required for ASAN to avoid false-positive diagnostic * when this memory will re-used by malloc or another mmapping. @@ -29860,25 +30044,24 @@ MDBX_INTERNAL int osal_munmap(osal_mmap_t *map) { MDBX_ASAN_UNPOISON_MEMORY_REGION(map->base, (map->filesize && map->filesize < map->limit) ? map->filesize : map->limit); #if defined(_WIN32) || defined(_WIN64) - if (map->section) + if (map->section) { NtClose(map->section); - NTSTATUS rc = NtUnmapViewOfSection(GetCurrentProcess(), map->base); - if (!NT_SUCCESS(rc)) - ntstatus2errcode(rc); -#else - if (unlikely(munmap(map->base, map->limit))) { - assert(errno != 0); - return errno; + map->section = 0; } + NTSTATUS err = NtUnmapViewOfSection(GetCurrentProcess(), map->base); + if (!NT_SUCCESS(err)) + ERROR("Unexpected NtUnmapViewOfSection(%p, %zi) error %d", map->base, map->limit, osal_ntstatus2errcode(err)); +#else + if (unlikely(munmap(map->base, map->limit))) + ERROR("Unexpected munmap(%p, %zi) error %d", map->base, map->limit, errno); #endif /* ! Windows */ map->limit = 0; map->current = 0; map->base = nullptr; - return MDBX_SUCCESS; } -MDBX_INTERNAL int osal_mresize(const int flags, osal_mmap_t *map, size_t size, size_t limit) { +int osal_mresize(const int flags, osal_mmap_t *map, size_t size, size_t limit) { int rc = osal_filesize(map->fd, &map->filesize); VERBOSE("flags 0x%x, size %zu, limit %zu, filesize %" PRIu64, flags, size, limit, map->filesize); assert(size <= limit); @@ -29904,7 +30087,7 @@ MDBX_INTERNAL int osal_mresize(const int flags, osal_mmap_t *map, size_t size, s SectionSize.QuadPart = size; status = imports.NtExtendSection(map->section, &SectionSize); if (!NT_SUCCESS(status)) - return ntstatus2errcode(status); + return osal_ntstatus2errcode(status); map->current = size; if (map->filesize < size) map->filesize = size; @@ -29924,11 +30107,11 @@ MDBX_INTERNAL int osal_mresize(const int flags, osal_mmap_t *map, size_t size, s if (status == (NTSTATUS) /* STATUS_CONFLICTING_ADDRESSES */ 0xC0000018) return MDBX_UNABLE_EXTEND_MAPSIZE; if (!NT_SUCCESS(status)) - return ntstatus2errcode(status); + return osal_ntstatus2errcode(status); status = NtFreeVirtualMemory(GetCurrentProcess(), &BaseAddress, &RegionSize, MEM_RELEASE); if (!NT_SUCCESS(status)) - return ntstatus2errcode(status); + return osal_ntstatus2errcode(status); } /* Windows unable: @@ -29948,7 +30131,7 @@ MDBX_INTERNAL int osal_mresize(const int flags, osal_mmap_t *map, size_t size, s MDBX_ASAN_UNPOISON_MEMORY_REGION(map->base, map->limit); status = NtUnmapViewOfSection(GetCurrentProcess(), map->base); if (!NT_SUCCESS(status)) - return ntstatus2errcode(status); + return osal_ntstatus2errcode(status); status = NtClose(map->section); map->section = nullptr; PVOID ReservedAddress = nullptr; @@ -29956,7 +30139,7 @@ MDBX_INTERNAL int osal_mresize(const int flags, osal_mmap_t *map, size_t size, s if (!NT_SUCCESS(status)) { bailout_ntstatus: - err = ntstatus2errcode(status); + err = osal_ntstatus2errcode(status); map->base = nullptr; map->current = map->limit = 0; if (ReservedAddress) { @@ -29984,7 +30167,7 @@ MDBX_INTERNAL int osal_mresize(const int flags, osal_mmap_t *map, size_t size, s } if ((flags & MDBX_RDONLY) == 0 && map->filesize != size) { - err = osal_ftruncate(map->fd, size); + err = osal_fsetsize(map->fd, size); if (err == MDBX_SUCCESS) map->filesize = size; /* ignore error, because Windows unable shrink file @@ -30062,10 +30245,15 @@ retry_mapview:; rc = MDBX_EPERM; map->current = (map->filesize > limit) ? limit : (size_t)map->filesize; } else { - if (size > map->filesize || (size < map->filesize && (flags & txn_shrink_allowed))) { - rc = osal_ftruncate(map->fd, size); - VERBOSE("ftruncate %zu, err %d", size, rc); - if (rc != MDBX_SUCCESS) + if (map->filesize != size) { + if (size > map->filesize) { + rc = osal_fsetsize(map->fd, size); + VERBOSE("osal_fsetsize-%s %zu, err %d", "extend", size, rc); + } else if (flags & txn_shrink_allowed) { + rc = osal_fsetsize(map->fd, size); + VERBOSE("osal_fsetsize-%s %zu, err %d", "shrink", size, rc); + } + if (unlikely(rc != MDBX_SUCCESS)) return rc; map->filesize = size; } @@ -30252,7 +30440,7 @@ retry_mapview:; /*----------------------------------------------------------------------------*/ -__cold MDBX_INTERNAL void osal_jitter(bool tiny) { +__cold void osal_jitter(bool tiny) { for (;;) { #if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__x86_64__) unsigned salt = 5296013u * (unsigned)__rdtsc(); @@ -30270,7 +30458,7 @@ __cold MDBX_INTERNAL void osal_jitter(bool tiny) { break; #if defined(_WIN32) || defined(_WIN64) if (coin < 43 * 2 / 3) - SwitchToThread(); + osal_yield(); else { static HANDLE timer; if (!timer) @@ -30285,7 +30473,7 @@ __cold MDBX_INTERNAL void osal_jitter(bool tiny) { break; } #else - sched_yield(); + osal_yield(); if (coin > 43 * 2 / 3) usleep(coin); #endif @@ -30321,7 +30509,7 @@ __cold static clockid_t choice_monoclock(void) { #define posix_clockid CLOCK_REALTIME #endif -MDBX_INTERNAL uint64_t osal_16dot16_to_monotime(uint32_t seconds_16dot16) { +uint64_t osal_16dot16_to_monotime(uint32_t seconds_16dot16) { #if defined(_WIN32) || defined(_WIN64) const uint64_t ratio = performance_frequency.QuadPart; #elif defined(__APPLE__) || defined(__MACH__) @@ -30334,7 +30522,7 @@ MDBX_INTERNAL uint64_t osal_16dot16_to_monotime(uint32_t seconds_16dot16) { } static uint64_t monotime_limit; -MDBX_INTERNAL uint32_t osal_monotime_to_16dot16(uint64_t monotime) { +uint32_t osal_monotime_to_16dot16(uint64_t monotime) { if (unlikely(monotime > monotime_limit)) return UINT32_MAX; @@ -30349,7 +30537,7 @@ MDBX_INTERNAL uint32_t osal_monotime_to_16dot16(uint64_t monotime) { return ret; } -MDBX_INTERNAL uint64_t osal_monotime(void) { +uint64_t osal_monotime(void) { #if defined(_WIN32) || defined(_WIN64) LARGE_INTEGER counter; if (QueryPerformanceCounter(&counter)) @@ -30364,7 +30552,7 @@ MDBX_INTERNAL uint64_t osal_monotime(void) { return 0; } -MDBX_INTERNAL uint64_t osal_cputime(size_t *optional_page_faults) { +uint64_t osal_cputime(size_t *optional_page_faults) { #if defined(_WIN32) || defined(_WIN64) if (optional_page_faults) { PROCESS_MEMORY_COUNTERS pmc; @@ -30843,10 +31031,12 @@ __cold static bin128_t osal_bootid(void) { case KSTAT_DATA_UINT32: bootid_collect(&uuid, &kn->value, sizeof(int32_t)); got_boottime = true; + break; case KSTAT_DATA_INT64: case KSTAT_DATA_UINT64: bootid_collect(&uuid, &kn->value, sizeof(int64_t)); got_boottime = true; + break; } } } @@ -31029,7 +31219,7 @@ __cold int mdbx_get_sysraminfo(intptr_t *page_size, intptr_t *total_pages, intpt #include #endif /* Windows */ -MDBX_INTERNAL bin128_t osal_guid(const MDBX_env *env) { +bin128_t osal_guid(const MDBX_env *env) { struct { uint64_t begin, end, cputime; uintptr_t thread, pid; @@ -31160,7 +31350,7 @@ void osal_ctor(void) { void osal_dtor(void) {} /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 __cold int MDBX_PRINTF_ARGS(2, 3) bad_page(const page_t *mp, const char *fmt, ...) { if (LOG_ENABLED(MDBX_LOG_ERROR)) { @@ -31641,7 +31831,7 @@ pgr_t page_get_large(const MDBX_cursor *const mc, const pgno_t pgno, const txnid return page_get_inline(P_ILL_BITS | P_BRANCH | P_LEAF | P_DUPFIX, mc, pgno, front); } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 int iov_init(MDBX_txn *const txn, iov_ctx_t *ctx, size_t items, size_t npages, mdbx_filehandle_t fd, bool check_coherence) { @@ -31725,19 +31915,22 @@ static void iov_callback4dirtypages(iov_ctx_t *ctx, size_t offset, void *data, s #ifndef MDBX_FORCE_CHECK_MMAP_COHERENCY #define MDBX_FORCE_CHECK_MMAP_COHERENCY 0 #endif /* MDBX_FORCE_CHECK_MMAP_COHERENCY */ - if ((MDBX_FORCE_CHECK_MMAP_COHERENCY || ctx->coherency_timestamp != UINT64_MAX) && - unlikely(memcmp(wp, rp, bytes))) { - ctx->coherency_timestamp = 0; - env->lck->pgops.incoherence.weak = - (env->lck->pgops.incoherence.weak >= INT32_MAX) ? INT32_MAX : env->lck->pgops.incoherence.weak + 1; - WARNING("catch delayed/non-arrived page %" PRIaPGNO " %s", wp->pgno, - "(workaround for incoherent flaw of unified page/buffer cache)"); - do - if (coherency_timeout(&ctx->coherency_timestamp, wp->pgno, env) != MDBX_RESULT_TRUE) { - ctx->err = MDBX_PROBLEM; - break; - } - while (unlikely(memcmp(wp, rp, bytes))); + if ((MDBX_FORCE_CHECK_MMAP_COHERENCY || unlikely(ctx->coherency_timestamp != UINT64_MAX))) { + VALGRIND_MAKE_MEM_DEFINED(wp, bytes); + MDBX_ASAN_UNPOISON_MEMORY_REGION(wp, bytes); + if (unlikely(memcmp(wp, rp, bytes))) { + ctx->coherency_timestamp = 0; + env->lck->pgops.incoherence.weak = + (env->lck->pgops.incoherence.weak >= INT32_MAX) ? INT32_MAX : env->lck->pgops.incoherence.weak + 1; + WARNING("catch delayed/non-arrived page %" PRIaPGNO " %s", wp->pgno, + "(workaround for incoherent flaw of unified page/buffer cache)"); + do + if (coherency_timeout(&ctx->coherency_timestamp, wp->pgno, env) != MDBX_RESULT_TRUE) { + ctx->err = MDBX_PROBLEM; + break; + } + while (unlikely(memcmp(wp, rp, bytes))); + } } } @@ -31824,7 +32017,7 @@ int iov_page(MDBX_txn *txn, iov_ctx_t *ctx, page_t *dp, size_t npages) { return MDBX_SUCCESS; } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 static inline tree_t *outer_tree(MDBX_cursor *mc) { cASSERT(mc, (mc->flags & z_inner) != 0); @@ -32566,7 +32759,7 @@ size_t page_subleaf2_reserve(const MDBX_env *env, size_t host_page_room, size_t return reserve + (subpage_len & 1); } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 pnl_t pnl_alloc(size_t size) { size_t bytes = pnl_size2bytes(size); @@ -32800,7 +32993,7 @@ __hot __noinline size_t pnl_search_nochk(const pnl_t pnl, pgno_t pgno) { return it - begin + 1; } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 #if MDBX_ENABLE_REFUND static void refund_reclaimed(MDBX_txn *txn) { @@ -33010,7 +33203,7 @@ bool txn_refund(MDBX_txn *txn) { #endif /* MDBX_ENABLE_REFUND */ /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 void spill_remove(MDBX_txn *txn, size_t idx, size_t npages) { tASSERT(txn, idx > 0 && idx <= MDBX_PNL_GETSIZE(txn->tw.spilled.list) && txn->tw.spilled.least_removed > 0); @@ -33439,7 +33632,7 @@ __cold int spill_slowpath(MDBX_txn *const txn, MDBX_cursor *const m0, const intp : MDBX_TXN_FULL; } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 int tbl_setup(const MDBX_env *env, volatile kvx_t *const kvx, const tree_t *const db) { osal_memory_fence(mo_AcquireRelease, false); @@ -33544,7 +33737,7 @@ int tbl_fetch(MDBX_txn *txn, size_t dbi) { return MDBX_SUCCESS; } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 typedef struct rthc_entry { MDBX_env *env; @@ -33916,25 +34109,27 @@ __cold static int rthc_drown(MDBX_env *const env) { int rc = MDBX_SUCCESS; MDBX_env *inprocess_neighbor = nullptr; if (likely(env->lck_mmap.lck && current_pid == env->pid)) { - reader_slot_t *const begin = &env->lck_mmap.lck->rdt[0]; - reader_slot_t *const end = &env->lck_mmap.lck->rdt[env->max_readers]; - TRACE("== %s env %p pid %d, readers %p ...%p, current-pid %d", (current_pid == env->pid) ? "cleanup" : "skip", - __Wpedantic_format_voidptr(env), env->pid, __Wpedantic_format_voidptr(begin), __Wpedantic_format_voidptr(end), - current_pid); - bool cleaned = false; - for (reader_slot_t *r = begin; r < end; ++r) { - if (atomic_load32(&r->pid, mo_Relaxed) == current_pid) { - atomic_store32(&r->pid, 0, mo_AcquireRelease); - TRACE("== cleanup %p", __Wpedantic_format_voidptr(r)); - cleaned = true; - } - } - if (cleaned) - atomic_store32(&env->lck_mmap.lck->rdt_refresh_flag, true, mo_Relaxed); rc = rthc_uniq_check(&env->lck_mmap, &inprocess_neighbor); - if (!inprocess_neighbor && env->registered_reader_pid && env->lck_mmap.fd != INVALID_HANDLE_VALUE) { - int err = lck_rpid_clear(env); - rc = rc ? rc : err; + if (!inprocess_neighbor) { + reader_slot_t *const begin = &env->lck_mmap.lck->rdt[0]; + reader_slot_t *const end = &env->lck_mmap.lck->rdt[env->max_readers]; + TRACE("== %s env %p pid %d, readers %p ...%p, current-pid %d", (current_pid == env->pid) ? "cleanup" : "skip", + __Wpedantic_format_voidptr(env), env->pid, __Wpedantic_format_voidptr(begin), + __Wpedantic_format_voidptr(end), current_pid); + bool cleaned = false; + for (reader_slot_t *r = begin; r < end; ++r) { + if (atomic_load32(&r->pid, mo_Relaxed) == current_pid) { + atomic_store32(&r->pid, 0, mo_AcquireRelease); + TRACE("== cleanup %p", __Wpedantic_format_voidptr(r)); + cleaned = true; + } + } + if (cleaned) + atomic_store32(&env->lck_mmap.lck->rdt_refresh_flag, true, mo_Relaxed); + if (env->registered_reader_pid && env->lck_mmap.fd != INVALID_HANDLE_VALUE) { + int err = lck_rpid_clear(env); + rc = rc ? rc : err; + } } } int err = lck_destroy(env, inprocess_neighbor, current_pid); @@ -34056,7 +34251,9 @@ __cold void rthc_dtor(const uint32_t current_pid) { MDBX_env *const env = rthc_table[i].env; if (env->pid != current_pid) continue; - if (!(env->flags & ENV_TXKEY)) + if (!env->lck_mmap.lck || env->lck_mmap.base == MAP_FAILED) + continue; + if (!(env->flags & ENV_TXKEY) || !env->lck_mmap.lck) continue; env->flags -= ENV_TXKEY; reader_slot_t *const begin = &env->lck_mmap.lck->rdt[0]; @@ -34095,7 +34292,7 @@ __cold void rthc_dtor(const uint32_t current_pid) { /// \copyright SPDX-License-Identifier: Apache-2.0 /// \note Please refer to the COPYRIGHT file for explanations license change, /// credits and acknowledgments. -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 static MDBX_cursor *cursor_clone(const MDBX_cursor *csrc, cursor_couple_t *couple) { cASSERT(csrc, csrc->txn->txnid >= csrc->txn->env->lck->cached_oldest.weak); @@ -34689,8 +34886,8 @@ static int page_merge(MDBX_cursor *csrc, MDBX_cursor *cdst) { cASSERT(cdst, cdst->top > 0); page_t *const top_page = cdst->pg[cdst->top]; const indx_t top_indx = cdst->ki[cdst->top]; - const int save_top = cdst->top; const uint16_t save_height = cdst->tree->height; + const int save_top = cdst->top; cursor_pop(cdst); rc = tree_rebalance(cdst); if (unlikely(rc != MDBX_SUCCESS)) @@ -34710,9 +34907,10 @@ static int page_merge(MDBX_cursor *csrc, MDBX_cursor *cdst) { } cASSERT(cdst, page_numkeys(top_page) == dst_nkeys + src_nkeys); - + const int new_top = save_top - save_height + cdst->tree->height; if (unlikely(pagetype != page_type(top_page))) { /* LY: LEAF-page becomes BRANCH, unable restore cursor's stack */ + ERROR("unexpected top-page type 0x%x, expect 0x%x", page_type(top_page), pagetype); goto bailout; } @@ -34723,9 +34921,10 @@ static int page_merge(MDBX_cursor *csrc, MDBX_cursor *cdst) { return MDBX_SUCCESS; } - const int new_top = save_top - save_height + cdst->tree->height; if (unlikely(new_top < 0 || new_top >= cdst->tree->height)) { /* LY: out of range, unable restore cursor's stack */ + ERROR("cursor top-new %i is out of range %u..%u (top-before %i, height-before %i, height-new %i)", new_top, 0, + cdst->tree->height, save_top, save_height, cdst->tree->height); goto bailout; } @@ -34738,10 +34937,7 @@ static int page_merge(MDBX_cursor *csrc, MDBX_cursor *cdst) { return MDBX_SUCCESS; } - page_t *const stub_page = (page_t *)(~(uintptr_t)top_page); - const indx_t stub_indx = top_indx; - if (save_height > cdst->tree->height && ((cdst->pg[save_top] == top_page && cdst->ki[save_top] == top_indx) || - (cdst->pg[save_top] == stub_page && cdst->ki[save_top] == stub_indx))) { + if (save_height > cdst->tree->height && cdst->pg[save_top] == top_page && cdst->ki[save_top] == top_indx) { /* LY: restore cursor stack */ cdst->pg[new_top] = top_page; cdst->ki[new_top] = top_indx; @@ -34756,7 +34952,12 @@ static int page_merge(MDBX_cursor *csrc, MDBX_cursor *cdst) { } bailout: - /* LY: unable restore cursor's stack */ + ERROR("unable restore %scursor stack after merge; " + " new: height %i top %i top-idx %i top-page %p;" + " before: height %i top %i, top-indx %i top-page %p", + is_inner(cdst) ? "sub-" : "", cdst->tree->height, new_top, cdst->ki[save_top], + __Wpedantic_format_voidptr(cdst->pg[save_top]), save_height, save_top, top_indx, + __Wpedantic_format_voidptr(top_page)); be_poor(cdst); return MDBX_CURSOR_FULL; } @@ -35091,18 +35292,17 @@ int page_split(MDBX_cursor *mc, const MDBX_val *const newkey, MDBX_val *const ne cASSERT(mc, !is_branch(mp) || newindx > 0); MDBX_val sepkey = {nullptr, 0}; - /* It is reasonable and possible to split the page at the begin */ + /* It is reasonable and possible to split the page at the begin? */ if (unlikely(newindx < minkeys)) { split_indx = minkeys; if (newindx == 0 && !(naf & MDBX_SPLIT_REPLACE)) { split_indx = 0; - /* Checking for ability of splitting by the left-side insertion - * of a pure page with the new key */ - for (intptr_t i = 0; i < mc->top; ++i) + /* Checking for ability of splitting by the left-side insertion of a pure page with the new key. */ + for (intptr_t i = mc->top; --i >= 0;) if (mc->ki[i]) { sepkey = get_key(page_node(mc->pg[i], mc->ki[i])); - if (mc->clc->k.cmp(newkey, &sepkey) >= 0) - split_indx = minkeys; + eASSERT(env, mc->clc->k.cmp(newkey, &sepkey) >= 0); + split_indx = minkeys; break; } if (split_indx == 0) { @@ -35647,7 +35847,7 @@ int tree_propagate_key(MDBX_cursor *mc, const MDBX_val *key) { /// \copyright SPDX-License-Identifier: Apache-2.0 /// \note Please refer to the COPYRIGHT file for explanations license change, /// credits and acknowledgments. -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 /* Search for the lowest key under the current branch page. * This just bypasses a numkeys check in the current page @@ -35783,7 +35983,7 @@ __hot __noinline int tree_search_finalize(MDBX_cursor *mc, const MDBX_val *key, return MDBX_SUCCESS; } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 static inline size_t txl_size2bytes(const size_t size) { assert(size > 0 && size <= txl_max * 2); @@ -35880,7 +36080,7 @@ __hot bool txl_contain(const txl_t txl, txnid_t id) { return false; } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 __hot txnid_t txn_snapshot_oldest(const MDBX_txn *const txn) { return mvcc_shapshot_oldest(txn->env, txn->tw.troika.txnid[txn->tw.troika.prefer_steady]); @@ -35960,7 +36160,8 @@ void txn_merge(MDBX_txn *const parent, MDBX_txn *const txn, const size_t parent_ (parent->parent ? parent->parent->tw.dirtyroom : parent->env->options.dp_limit)); } - /* Remove reclaimed pages from parent's dirty list */ + /* Remove reclaimed pages from parent's dirty list. + * Here the nested->tw.repnl was already moved into parent->tw.repnl ans space was reserved via retired_delta. */ const pnl_t reclaimed_list = parent->tw.repnl; dpl_sift(parent, reclaimed_list, false); @@ -36016,6 +36217,7 @@ void txn_merge(MDBX_txn *const parent, MDBX_txn *const txn, const size_t parent_ DEBUG("reclaim retired parent's %u -> %zu %s page %" PRIaPGNO, npages, l, kind, pgno); int err = pnl_insert_span(&parent->tw.repnl, pgno, l); + /* The space for additions to parent->tw.repnl was already reserverd via the retired_delta. */ ENSURE(txn->env, err == MDBX_SUCCESS); } MDBX_PNL_SETSIZE(parent->tw.retired_pages, w); @@ -36809,6 +37011,9 @@ int txn_end(MDBX_txn *txn, unsigned mode) { eASSERT(env, pnl_check_allocated(txn->tw.repnl, txn->geo.first_unallocated - MDBX_ENABLE_REFUND)); eASSERT(env, memcmp(&txn->tw.troika, &parent->tw.troika, sizeof(troika_t)) == 0); + if ((mode & TXN_END_UPDATE) == 0) + dbi_update(txn, false); + txn->owner = 0; if (txn->tw.gc.retxl) { eASSERT(env, MDBX_PNL_GETSIZE(txn->tw.gc.retxl) >= (uintptr_t)parent->tw.gc.retxl); @@ -36961,9 +37166,9 @@ int txn_unpark(MDBX_txn *txn) { return err ? err : MDBX_OUSTED; } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 -MDBX_MAYBE_UNUSED MDBX_NOTHROW_CONST_FUNCTION MDBX_INTERNAL unsigned log2n_powerof2(size_t value_uintptr) { +MDBX_MAYBE_UNUSED MDBX_NOTHROW_CONST_FUNCTION unsigned log2n_powerof2(size_t value_uintptr) { assert(value_uintptr > 0 && value_uintptr < INT32_MAX && is_powerof2(value_uintptr)); assert((value_uintptr & -(intptr_t)value_uintptr) == value_uintptr); const uint32_t value_uint32 = (uint32_t)value_uintptr; @@ -36982,7 +37187,7 @@ MDBX_MAYBE_UNUSED MDBX_NOTHROW_CONST_FUNCTION MDBX_INTERNAL unsigned log2n_power #endif } -MDBX_NOTHROW_CONST_FUNCTION MDBX_INTERNAL uint64_t rrxmrrxmsx_0(uint64_t v) { +MDBX_NOTHROW_CONST_FUNCTION uint64_t rrxmrrxmsx_0(uint64_t v) { /* Pelle Evensen's mixer, https://bit.ly/2HOfynt */ v ^= (v << 39 | v >> 25) ^ (v << 14 | v >> 50); v *= UINT64_C(0xA24BAED4963EE407); @@ -36991,7 +37196,7 @@ MDBX_NOTHROW_CONST_FUNCTION MDBX_INTERNAL uint64_t rrxmrrxmsx_0(uint64_t v) { return v ^ v >> 28; } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 typedef struct walk_ctx { void *userctx; @@ -37279,7 +37484,7 @@ __cold int walk_pages(MDBX_txn *txn, walk_func *visitor, void *user, walk_option return rc; } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 #if defined(_WIN32) || defined(_WIN64) @@ -37451,11 +37656,11 @@ __dll_export const struct MDBX_version_info mdbx_version = { 0, 13, - 7, + 12, 0, "", /* pre-release suffix of SemVer - 0.13.7 */ - {"2025-07-30T11:44:04+03:00", "7777cbdf5aa4c1ce85ff902a4c3e6170edd42495", "566b0f93c7c9a3bdffb8fb3dc0ce8ca42641bd72", "v0.13.7-0-g566b0f93"}, + 0.13.12 */ + {"2026-04-30T16:36:24+03:00", "f5574b87cc64fa7a3a6b21ba33809258498d5f17", "f619d43dfbc36cbc9a1832503ce43f2e5223996e", "v0.13.12-0-gf619d43d"}, sourcery}; __dll_export diff --git a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx.c++ b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx.c++ index 27220d53088..b38b6e8bbfc 100644 --- a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx.c++ +++ b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx.c++ @@ -1,8 +1,8 @@ /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 /* clang-format off */ -#define MDBX_BUILD_SOURCERY 6b5df6869d2bf5419e3a8189d9cc849cc9911b9c8a951b9750ed0a261ce43724_v0_13_7_0_g566b0f93 +#define MDBX_BUILD_SOURCERY a575a490fc080ca11e89ff6db9f0bd38aa830959905998cac0e45274b9e6bb0e_v0_13_12_0_gf619d43d #define LIBMDBX_INTERNALS #define MDBX_DEPRECATED @@ -1171,6 +1171,14 @@ typedef char pathchar_t; #define MDBX_PRIsPATH "s" #endif +MDBX_MAYBE_UNUSED static inline bool osal_yield(void) { +#if defined(_WIN32) || defined(_WIN64) + return SleepEx(0, true) == WAIT_IO_COMPLETION; +#else + return sched_yield() != 0; +#endif +} + typedef struct osal_mmap { union { void *base; @@ -1190,6 +1198,8 @@ typedef struct osal_mmap { #define MDBX_HAVE_PWRITEV 0 +MDBX_INTERNAL int osal_waitstatus2errcode(DWORD result); + #elif defined(__ANDROID_API__) #if __ANDROID_API__ < 24 @@ -1433,7 +1443,7 @@ enum osal_syncmode_bits { }; MDBX_INTERNAL int osal_fsync(mdbx_filehandle_t fd, const enum osal_syncmode_bits mode_bits); -MDBX_INTERNAL int osal_ftruncate(mdbx_filehandle_t fd, uint64_t length); +MDBX_INTERNAL int osal_fsetsize(mdbx_filehandle_t fd, const uint64_t length); MDBX_INTERNAL int osal_fseek(mdbx_filehandle_t fd, uint64_t pos); MDBX_INTERNAL int osal_filesize(mdbx_filehandle_t fd, uint64_t *length); @@ -1469,11 +1479,11 @@ MDBX_INTERNAL int osal_removedirectory(const pathchar_t *pathname); MDBX_INTERNAL int osal_is_pipe(mdbx_filehandle_t fd); MDBX_INTERNAL int osal_lockfile(mdbx_filehandle_t fd, bool wait); -#define MMAP_OPTION_TRUNCATE 1 +#define MMAP_OPTION_SETLENGTH 1 #define MMAP_OPTION_SEMAPHORE 2 MDBX_INTERNAL int osal_mmap(const int flags, osal_mmap_t *map, size_t size, const size_t limit, const unsigned options, const pathchar_t *pathname4logging); -MDBX_INTERNAL int osal_munmap(osal_mmap_t *map); +MDBX_INTERNAL void osal_munmap(osal_mmap_t *map); #define MDBX_MRESIZE_MAY_MOVE 0x00000100 #define MDBX_MRESIZE_MAY_UNMAP 0x00000200 MDBX_INTERNAL int osal_mresize(const int flags, osal_mmap_t *map, size_t size, size_t limit); @@ -1876,7 +1886,8 @@ MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline uint32_t osal_bswap32 ((defined(_POSIX_THREAD_ROBUST_PRIO_INHERIT) && _POSIX_THREAD_ROBUST_PRIO_INHERIT > 0) || \ (defined(_POSIX_THREAD_ROBUST_PRIO_PROTECT) && _POSIX_THREAD_ROBUST_PRIO_PROTECT > 0) || \ defined(PTHREAD_MUTEX_ROBUST) || defined(PTHREAD_MUTEX_ROBUST_NP)) && \ - (!defined(__GLIBC__) || __GLIBC_PREREQ(2, 10) /* troubles with Robust mutexes before 2.10 */) + (!defined(__GLIBC__) || __GLIBC_PREREQ(2, 10) /* troubles with Robust mutexes before 2.10 */) && \ + !defined(__OHOS__) /* Harmony OS doesn't support robust mutexes at the end of 2025 */ #define MDBX_LOCKING MDBX_LOCKING_POSIX2008 #else #define MDBX_LOCKING MDBX_LOCKING_POSIX2001 @@ -1931,6 +1942,22 @@ MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline uint32_t osal_bswap32 #error MDBX_USE_COPYFILERANGE must be defined as 0 or 1 #endif /* MDBX_USE_COPYFILERANGE */ +/** Advanced: Using posix_fallocate() or fcntl(F_PREALLOCATE) on OSX (autodetection by default). */ +#ifndef MDBX_USE_FALLOCATE +#if defined(__APPLE__) +#define MDBX_USE_FALLOCATE 0 /* Too slow and unclean, but not required to prevent SIGBUS */ +#elif (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L) || (__GLIBC_PREREQ(2, 10) && defined(_GNU_SOURCE)) +#define MDBX_USE_FALLOCATE 1 +#else +#define MDBX_USE_FALLOCATE 0 +#endif +#define MDBX_USE_FALLOCATE_CONFIG "AUTO=" MDBX_STRINGIFY(MDBX_USE_FALLOCATE) +#elif !(MDBX_USE_FALLOCATE == 0 || MDBX_USE_FALLOCATE == 1) +#error MDBX_USE_FALLOCATE must be defined as 0 or 1 +#else +#define MDBX_USE_FALLOCATE_CONFIG MDBX_STRINGIFY(MDBX_USE_FALLOCATE) +#endif /* MDBX_USE_FALLOCATE */ + //------------------------------------------------------------------------------ #ifndef MDBX_CPU_WRITEBACK_INCOHERENT @@ -3212,7 +3239,7 @@ MDBX_NOTHROW_CONST_FUNCTION MDBX_MAYBE_UNUSED static inline pgno_t pgno_sub(size return int64pgno((int64_t)base - (int64_t)subtrahend); } /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2020-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2020-2026 /// /// \brief Non-inline part of the libmdbx C++ API /// @@ -3641,15 +3668,16 @@ __cold void error::throw_exception() const { bool slice::is_printable(bool disable_utf8) const noexcept { enum : byte { - LS = 4, // shift for UTF8 sequence length - P_ = 1 << LS, // printable ASCII flag - N_ = 0, // non-printable ASCII - second_range_mask = P_ - 1, // mask for range flag - r80_BF = 0, // flag for UTF8 2nd byte range - rA0_BF = 1, // flag for UTF8 2nd byte range - r80_9F = 2, // flag for UTF8 2nd byte range - r90_BF = 3, // flag for UTF8 2nd byte range - r80_8F = 4, // flag for UTF8 2nd byte range + LS = 4, // shift for UTF8 sequence length + P_ = 1 << LS, // printable ASCII flag + X_ = 1 << (LS - 1), // printable extended ASCII flag + N_ = 0, // non-printable ASCII + r80_BF = 0, // flag for UTF8 2nd byte range + rA0_BF = 1, // flag for UTF8 2nd byte range + r80_9F = 2, // flag for UTF8 2nd byte range + r90_BF = 3, // flag for UTF8 2nd byte range + r80_8F = 4, // flag for UTF8 2nd byte range + second_range_mask = 7, // mask for range flag // valid utf-8 byte sequences // http://www.unicode.org/versions/Unicode6.0.0/ch03.pdf - page 94 @@ -3679,14 +3707,14 @@ bool slice::is_printable(bool disable_utf8) const noexcept { P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, // 50 P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, // 60 P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, N_, // 70 - N_, N_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, N_, P_, N_, // 80 - N_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, N_, P_, P_, // 90 - P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, // a0 - P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, // b0 - P_, P_, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, // c0 + N_, N_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, N_, X_, N_, // 80 + N_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, N_, X_, X_, // 90 + X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, // a0 + X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, // b0 + X_, X_, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, // c0 C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, // df E0, E1, E1, E1, E1, E1, E1, E1, E1, E1, E1, E1, E1, ED, EE, EE, // e0 - F0, F1, F1, F1, F4, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_ // f0 + F0, F1, F1, F1, F4, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_ // f0 }; if (MDBX_UNLIKELY(length() < 1)) @@ -3696,7 +3724,7 @@ bool slice::is_printable(bool disable_utf8) const noexcept { const auto end = src + length(); if (MDBX_UNLIKELY(disable_utf8)) { do - if (MDBX_UNLIKELY((P_ & map[*src]) == 0)) + if (MDBX_UNLIKELY(((P_ | X_) & map[*src]) == 0)) MDBX_CXX20_UNLIKELY return false; while (++src < end); return true; @@ -4387,11 +4415,11 @@ bool from_base64::is_erroneous() const noexcept { #pragma warning(disable : 4251) #endif /* MSVC */ -MDBX_INSTALL_API_TEMPLATE(LIBMDBX_API_TYPE, buffer); +MDBX_INSTALL_API_TEMPLATE(LIBMDBX_API_TYPE, buffer); -#if defined(__cpp_lib_memory_resource) && __cpp_lib_memory_resource >= 201603L && _GLIBCXX_USE_CXX11_ABI -MDBX_INSTALL_API_TEMPLATE(LIBMDBX_API_TYPE, buffer); -#endif /* __cpp_lib_memory_resource >= 201603L */ +#if MDBX_CXX_HAS_POLYMORPHIC_ALLOCATOR +MDBX_INSTALL_API_TEMPLATE(LIBMDBX_API_TYPE, buffer); +#endif /* MDBX_CXX_HAS_POLYMORPHIC_ALLOCATOR */ #if defined(_MSC_VER) #pragma warning(pop) @@ -4524,12 +4552,12 @@ __cold env &env::copy(const MDBX_STD_FILESYSTEM_PATH &destination, bool compacti __cold path env::get_path() const { #if defined(_WIN32) || defined(_WIN64) - const wchar_t *c_wstr; + const wchar_t *c_wstr = nullptr; error::success_or_throw(::mdbx_env_get_pathW(handle_, &c_wstr)); static_assert(sizeof(path::value_type) == sizeof(wchar_t), "Oops"); return path(c_wstr); #else - const char *c_str; + const char *c_str = nullptr; error::success_or_throw(::mdbx_env_get_path(handle_, &c_str)); static_assert(sizeof(path::value_type) == sizeof(char), "Oops"); return path(c_str); @@ -4825,13 +4853,13 @@ __cold ::std::ostream &operator<<(::std::ostream &out, const slice &it) { else if (it.empty()) out << "EMPTY->" << it.data(); else { - const slice root(it.head(std::min(it.length(), size_t(64)))); + const slice head(it.head(std::min(it.length(), size_t(64)))); out << it.length() << "."; - if (root.is_printable()) - (out << "\"").write(root.char_ptr(), root.length()) << "\""; + if (head.is_printable()) + (out << "\"").write(head.char_ptr(), head.length()) << "\""; else - out << root.encode_base58(); - if (root.length() < it.length()) + out << to_hex(head); + if (head.length() < it.length()) out << "..."; } return out << "}"; diff --git a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx.h b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx.h index 90835d1b9e9..4e26f369c8c 100644 --- a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx.h +++ b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx.h @@ -1,6 +1,9 @@ /** -_libmdbx_ (aka MDBX) is an extremely fast, compact, powerful, embeddable, +\file mdbx.h +\brief The libmdbx C API header file. + +\details _libmdbx_ (aka MDBX) is an extremely fast, compact, powerful, embeddable, transactional [key-value store](https://en.wikipedia.org/wiki/Key-value_database), with [Apache 2.0 license](./LICENSE). _MDBX_ has a specific set of properties and capabilities, @@ -9,7 +12,7 @@ focused on creating unique lightweight solutions with extraordinary performance. _libmdbx_ is superior to [LMDB](https://bit.ly/26ts7tL) in terms of features and reliability, not inferior in performance. In comparison to LMDB, _libmdbx_ makes many things just work perfectly, not silently and catastrophically -break down. _libmdbx_ supports Linux, Windows, MacOS, OSX, iOS, Android, +break down. _libmdbx_ supports Linux, Windows, MacOS, OSX, Harmony, iOS, Android, FreeBSD, DragonFly, Solaris, OpenSolaris, OpenIndiana, NetBSD, OpenBSD and other systems compliant with POSIX.1-2008. @@ -18,24 +21,36 @@ C++ API description and links to the origin git repo with the source code. Questions, feedback and suggestions are welcome to the Telegram' group https://t.me/libmdbx. -Donations are welcome to ETH `0xD104d8f8B2dC312aaD74899F83EBf3EEBDC1EA3A`. +Donations are welcome to ETH `0xD104d8f8B2dC312aaD74899F83EBf3EEBDC1EA3A`, +BTC `bc1qzvl9uegf2ea6cwlytnanrscyv8snwsvrc0xfsu`, SOL `FTCTgbHajoLVZGr8aEFWMzx3NDMyS5wXJgfeMTmJznRi`. Всё будет хорошо! -\note The origin has been migrated to -[GitFlic](https://gitflic.ru/project/erthink/libmdbx) since on 2022-04-15 the -Github administration, without any warning nor explanation, deleted libmdbx -along with a lot of other projects, simultaneously blocking access for many -developers. For the same reason ~~Github~~ is blacklisted forever. +The _libmdbx_ project has been completely relocated to the jurisdiction of the Russian Federation. +\note _libmdbx_ is still open and provided with first-class free support. \section copyright LICENSE & COPYRIGHT \copyright SPDX-License-Identifier: Apache-2.0 -\note Please refer to the COPYRIGHT file for explanations license change, -credits and acknowledgments. -\author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +Please refer to the COPYRIGHT file for explanations license change, credits and acknowledgments. +\author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 *******************************************************************************/ #pragma once +/* + * Tested with, since 2026: + * - Elbrus LCC >= 1.28 (http://www.mcst.ru/lcc); + * - GNU C >= 11.3; + * - CLANG >= 14.0; + * - MSVC >= 19.44 (Visual Studio 2022 toolchain v143), + * before 2026: + * - Elbrus LCC >= 1.23 (http://www.mcst.ru/lcc); + * - GNU C >= 4.8; + * - CLANG >= 3.9; + * - MSVC >= 14.0 (Visual Studio 2015), + * but 19.2x could hang due optimizer bug; + * - AppleClang. + */ + #ifndef LIBMDBX_H #define LIBMDBX_H @@ -2525,8 +2540,7 @@ typedef enum MDBX_env_delete_mode { /** \brief Make sure that the environment is not being used by other * processes, or return an error otherwise. */ MDBX_ENV_ENSURE_UNUSED = 1, - /** \brief Wait until other processes closes the environment before deletion. - */ + /** \brief Wait until other processes closes the environment before deletion. */ MDBX_ENV_WAIT_FOR_UNUSED = 2, } MDBX_env_delete_mode_t; @@ -4992,7 +5006,7 @@ LIBMDBX_API int mdbx_get_equal_or_great(const MDBX_txn *txn, MDBX_dbi dbi, MDBX_ * \retval MDBX_EINVAL An invalid parameter was specified. */ LIBMDBX_API int mdbx_put(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, MDBX_val *data, MDBX_put_flags_t flags); -/** \brief Replace items in a table. +/** \brief Replaces item in a table. * \ingroup c_crud * * This function allows to update or delete an existing value at the same time @@ -5032,13 +5046,70 @@ LIBMDBX_API int mdbx_put(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, MDBX_ * combination for selection particular item from * multi-value/duplicates. * + * \see mdbx_replace_ex() * \see \ref c_crud_hints "Quick reference for Insert/Update/Delete operations" * * \returns A non-zero error value on failure and 0 on success. */ LIBMDBX_API int mdbx_replace(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, MDBX_val *new_data, MDBX_val *old_data, MDBX_put_flags_t flags); +/** \brief A data preservation callback for using within \ref mdbx_replace_ex(). + * \ingroup c_crud */ typedef int (*MDBX_preserve_func)(void *context, MDBX_val *target, const void *src, size_t bytes); + +/** \brief Replaces item in a table using preservation callback for an original data. + * \ingroup c_crud + * + * This function allows to update or delete an existing value at the same time + * as the previous value is retrieved. If the argument new_data equal is NULL + * zero, the removal is performed, otherwise the update/insert. + * + * The current value may be in an already changed (aka dirty) page. In this + * case, the page will be overwritten during the update, and the old value will + * be lost. In such cases, the given preservation callback will be used to save + * the source data, that, at your discretion, can perform copying, other necessary + * actions, or return a special error code. + * + * If an original data needs to be saved then the passed preservation callback will be called + * with `old_data` as a `target` parameter, and `src` with `bytes` for original data. + * Such callback should check necessary conditions, perform appropriate action and return + * corresponding error code, which will be returned from function as is. + * For example, for behavior similar to \ref mdbx_replace(), the callback should check if there + * provided buffer size is enough, then either copy the data or return \ref MDBX_RESULT_TRUE. + * + * For tables with non-unique keys (i.e. with \ref MDBX_DUPSORT flag), + * another use case is also possible, when by old_data argument selects a + * specific item from multi-value/duplicates with the same key for deletion or + * update. To select this scenario in flags should simultaneously specify + * \ref MDBX_CURRENT and \ref MDBX_NOOVERWRITE. This combination is chosen + * because it makes no sense, and thus allows you to identify the request of + * such a scenario. + * + * \param [in] txn A transaction handle returned + * by \ref mdbx_txn_begin(). + * \param [in] dbi A table handle returned by \ref mdbx_dbi_open(). + * \param [in] key The key to store in the table. + * \param [in] new_data The data to store, if NULL then deletion will + * be performed. + * \param [in,out] old_data The buffer for retrieve previous value as describe + * above. + * \param [in] flags Special options for this operation. + * This parameter must be set to 0 or by bitwise + * OR'ing together one or more of the values + * described in \ref mdbx_put() description above, + * and additionally + * (\ref MDBX_CURRENT | \ref MDBX_NOOVERWRITE) + * combination for selection particular item from + * multi-value/duplicates. + * \param [in] preserver The callback to preserve an original data in case + * it is on a dirty page and could be overwritten. + * \param [in] preserver_context The optional context pointer for use within the preserving callback. + * + * \see mdbx_replace_ex() + * \see MDBX_preserve_func + * \see \ref c_crud_hints "Quick reference for Insert/Update/Delete operations" + * + * \returns A non-zero error value on failure and 0 on success. */ LIBMDBX_API int mdbx_replace_ex(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, MDBX_val *new_data, MDBX_val *old_data, MDBX_put_flags_t flags, MDBX_preserve_func preserver, void *preserver_context); diff --git a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx.h++ b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx.h++ index 2d5f62b17d5..4f13dfd28cd 100644 --- a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx.h++ +++ b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx.h++ @@ -1,31 +1,45 @@ -/// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2020-2025 +/// \file mdbx.h++ +/// \brief The libmdbx C++ API header file. /// -/// Donations are welcome to ETH `0xD104d8f8B2dC312aaD74899F83EBf3EEBDC1EA3A`. -/// Всё будет хорошо! +/// \details _libmdbx_ (aka MDBX) is an extremely fast, compact, powerful, embeddable, +/// transactional [key-value +/// store](https://en.wikipedia.org/wiki/Key-value_database), with [Apache 2.0 +/// license](./LICENSE). _MDBX_ has a specific set of properties and capabilities, +/// focused on creating unique lightweight solutions with extraordinary performance. /// -/// \file mdbx.h++ -/// \brief The libmdbx C++ API header file. +/// Please visit https://libmdbx.dqdkfa.ru for more information, documentation, +/// C++ API description and links to the origin git repo with the source code. +/// Questions, feedback and suggestions are welcome to the Telegram' group +/// https://t.me/libmdbx. /// -/// Tested with: -/// - Elbrus LCC >= 1.23 (http://www.mcst.ru/lcc); -/// - GNU C++ >= 4.8; -/// - clang >= 3.9; -/// - MSVC >= 14.0 (Visual Studio 2015), -/// but 19.2x could hang due optimizer bug; -/// - AppleClang, but without C++20 concepts. +/// Donations are welcome to ETH `0xD104d8f8B2dC312aaD74899F83EBf3EEBDC1EA3A`, +/// BTC `bc1qzvl9uegf2ea6cwlytnanrscyv8snwsvrc0xfsu`, SOL `FTCTgbHajoLVZGr8aEFWMzx3NDMyS5wXJgfeMTmJznRi`. +/// Всё будет хорошо! /// - +/// The libmdbx project has been completely relocated to the jurisdiction of the Russian Federation. +/// \note _libmdbx_ is still open and provided with first-class free support. /// -/// The origin has been migrated to https://gitflic.ru/project/erthink/libmdbx -/// since on 2022-04-15 the Github administration, without any warning nor -/// explanation, deleted libmdbx along with a lot of other projects, -/// simultaneously blocking access for many developers. -/// For the same reason Github is blacklisted forever. +/// \copyright SPDX-License-Identifier: Apache-2.0 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2020-2026 /// #pragma once +// +// Tested with, since 2026: +// - Elbrus LCC >= 1.28 (http://www.mcst.ru/lcc); +// - GNU C++ >= 11.3; +// - CLANG >= 14.0; +// - MSVC >= 19.44 (Visual Studio 2022 toolchain v143), +// before 2026: +// - Elbrus LCC >= 1.23 (http://www.mcst.ru/lcc); +// - GNU C++ >= 4.8; +// - CLANG >= 3.9; +// - MSVC >= 14.0 (Visual Studio 2015), +// but 19.2x could hang due optimizer bug; +// - AppleClang, but without C++20 concepts. +// + /* Workaround for modern libstdc++ with CLANG < 4.x */ #if defined(__SIZEOF_INT128__) && !defined(__GLIBCXX_TYPE_INT_N_0) && defined(__clang__) && __clang_major__ < 4 #define __GLIBCXX_BITSIZE_INT_N_0 128 @@ -108,13 +122,11 @@ #endif #if !defined(_MSC_VER) || defined(__clang__) -/* adequate compilers */ -#define MDBX_EXTERN_API_TEMPLATE(API_ATTRIBUTES, API_TYPENAME) extern template class API_ATTRIBUTES API_TYPENAME -#define MDBX_INSTALL_API_TEMPLATE(API_ATTRIBUTES, API_TYPENAME) template class API_TYPENAME +#define MDBX_EXTERN_API_TEMPLATE(API_ATTRIBUTES, ...) extern template class API_ATTRIBUTES __VA_ARGS__ +#define MDBX_INSTALL_API_TEMPLATE(API_ATTRIBUTES, ...) template class __VA_ARGS__ #else -/* stupid microsoft showing off */ -#define MDBX_EXTERN_API_TEMPLATE(API_ATTRIBUTES, API_TYPENAME) extern template class API_TYPENAME -#define MDBX_INSTALL_API_TEMPLATE(API_ATTRIBUTES, API_TYPENAME) template class API_ATTRIBUTES API_TYPENAME +#define MDBX_EXTERN_API_TEMPLATE(API_ATTRIBUTES, ...) extern template class __VA_ARGS__ +#define MDBX_INSTALL_API_TEMPLATE(API_ATTRIBUTES, ...) template class API_ATTRIBUTES __VA_ARGS__ #endif #if __cplusplus >= 201103L @@ -307,18 +319,27 @@ namespace mdbx { /// \defgroup cxx_api C++ API /// @{ -// Functions whose signature depends on the `mdbx::byte` type -// must be strictly defined as inline! +/// \brief The byte-like type that don't presumes aliases for pointers as does the `char`. +/// \details Essentially, to enable all kinds of an compiler optimization, we need just +/// the `unsigned char * restrict` type in C99 terms, i.e. the non-aliasing pointer to `unsigned char`. +/// However, C++ still doesn't have `restrict` keyword not `non-aliases` type attribute, but a char-pointers may be +/// aliased. +/// +/// On the other hand, while `uint8_t` is provided and `CHAR_BIT = 8` the `char8_t *` actually act the same as the C99 +/// `unsigned char * restrict`. So using `char8_t` should not be an issue, since both the `CHAR_BIT = 8` and `uint8_t +/// `are required. +/// +/// At the same time, the approach of using `char8_t` has several advantages: +/// - the `restrict` attribute is defined on level of the base type and is inherited by any derived pointer type; +/// - some compilers treat `__restrict` as an attribute of an instance of a type (i.e. a variable, a specific +/// pointer), but not a type attribute. +/// +/// Nonetheless, I should think about switching to the `uint8_t * __restrict__` for byte pointers. +/// \note Functions whose signature depends on the `mdbx::byte` type must be strictly defined as inline! #if defined(DOXYGEN) || (defined(__cpp_char8_t) && __cpp_char8_t >= 201811) -// To enable all kinds of an compiler optimizations we use a byte-like type -// that don't presumes aliases for pointers as does the `char` type and its -// derivatives/typedefs. -// Please see https://libmdbx.dqdkfa.ru/dead-github/issues/263 -// for reasoning of the use of `char8_t` type and switching to `__restrict__`. using byte = char8_t; #else -// Avoid `std::byte` since it doesn't add features but inconvenient -// restrictions. +// Avoid `std::byte` since it doesn't add features but inconvenient restrictions. using byte = unsigned char; #endif /* __cpp_char8_t >= 201811*/ @@ -351,14 +372,22 @@ static MDBX_CXX20_CONSTEXPR int memcmp(const void *a, const void *b, size_t byte /// but it is recommended to use \ref polymorphic_allocator. using legacy_allocator = ::std::string::allocator_type; -#if defined(DOXYGEN) || \ - (defined(__cpp_lib_memory_resource) && __cpp_lib_memory_resource >= 201603L && _GLIBCXX_USE_CXX11_ABI) +#ifndef MDBX_CXX_POLYMORPHIC_ALLOCATOR +#if defined(DOXYGEN) || (defined(__cpp_lib_memory_resource) && __cpp_lib_memory_resource >= 201603L && \ + (!defined(_GLIBCXX_USE_CXX11_ABI) || _GLIBCXX_USE_CXX11_ABI)) +#define MDBX_CXX_HAS_POLYMORPHIC_ALLOCATOR 1 +#else +#define MDBX_CXX_HAS_POLYMORPHIC_ALLOCATOR 0 +#endif +#endif /* MDBX_CXX_HAS_POLYMORPHIC_ALLOCATOR */ + +#if MDBX_CXX_HAS_POLYMORPHIC_ALLOCATOR /// \brief Default polymorphic allocator for modern code. using polymorphic_allocator = ::std::pmr::string::allocator_type; using default_allocator = polymorphic_allocator; #else using default_allocator = legacy_allocator; -#endif /* __cpp_lib_memory_resource >= 201603L */ +#endif /* MDBX_CXX_HAS_POLYMORPHIC_ALLOCATOR */ struct slice; struct default_capacity_policy; @@ -641,7 +670,7 @@ struct LIBMDBX_API_TYPE slice : public ::MDBX_val { /// \todo key-to-value (parse/unpack) functions /// \todo template key(X); for decoding keys while reading - enum { max_length = MDBX_MAXDATASIZE }; + enum : size_t { max_length = MDBX_MAXDATASIZE }; /// \brief Create an empty slice. MDBX_CXX11_CONSTEXPR slice() noexcept; @@ -852,8 +881,6 @@ struct LIBMDBX_API_TYPE slice : public ::MDBX_val { /// break a code group of characters. MDBX_NOTHROW_PURE_FUNCTION inline bool is_base64(bool ignore_spaces = false) const noexcept; - inline void swap(slice &other) noexcept; - #if defined(DOXYGEN) || (defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L) template void swap(::std::basic_string_view &view) noexcept { static_assert(sizeof(CHAR) == 1, "Must be single byte characters"); @@ -1002,7 +1029,12 @@ struct LIBMDBX_API_TYPE slice : public ::MDBX_val { MDBX_CXX11_CONSTEXPR bool is_valid() const noexcept { return !(iov_base == nullptr && iov_len != 0); } /// \brief Build an invalid slice which non-zero length and refers to null address. - MDBX_CXX14_CONSTEXPR static slice invalid() noexcept { return slice(size_t(-1)); } + MDBX_CXX14_CONSTEXPR static slice invalid() noexcept { + return slice(/* using special constructor without length checking */ ~size_t(0)); + } + + /// \brief Build a null slice which zero length and refers to null address. + MDBX_CXX14_CONSTEXPR static slice null() noexcept { return slice(nullptr, size_t(0)); } template MDBX_CXX14_CONSTEXPR POD as_pod() const { static_assert(::std::is_standard_layout::value && !::std::is_pointer::value, @@ -1073,11 +1105,7 @@ template struct move_assign_alloc { static MDBX_CXX20_CONSTEXPR bool is_moveable(T *target, T &source) noexcept { return allocator_is_always_equal() || target->get_allocator() == source.get_allocator(); } - static MDBX_CXX20_CONSTEXPR void propagate(T *target, T &source) noexcept { - assert(target->get_allocator() != source.get_allocator()); - (void)target; - (void)source; - } + static MDBX_CXX20_CONSTEXPR void propagate(T *, T &) noexcept {} }; template struct move_assign_alloc { @@ -1085,8 +1113,7 @@ template struct move_assign_alloc { return allocator_is_always_equal() || ::std::is_nothrow_move_assignable::value; } static constexpr bool is_moveable(T *, T &) noexcept { return true; } - static MDBX_CXX20_CONSTEXPR void propagate(T *target, T &source) { - assert(target->get_allocator() != source.get_allocator()); + static MDBX_CXX20_CONSTEXPR void propagate(T *target, T &source) noexcept { target->get_allocator() = ::std::move(source.get_allocator()); } }; @@ -1097,11 +1124,7 @@ struct copy_assign_alloc; template struct copy_assign_alloc { static constexpr bool is_nothrow() noexcept { return false; } - static MDBX_CXX20_CONSTEXPR void propagate(T *target, const T &source) noexcept { - assert(target->get_allocator() != source.get_allocator()); - (void)target; - (void)source; - } + static MDBX_CXX20_CONSTEXPR void propagate(T *, const T &) noexcept {} }; template struct copy_assign_alloc { @@ -1127,14 +1150,14 @@ struct swap_alloc; template struct swap_alloc { static constexpr bool is_nothrow() noexcept { return allocator_is_always_equal(); } - static MDBX_CXX20_CONSTEXPR void propagate(T *target, T &source) noexcept(is_nothrow()) { + static MDBX_CXX20_CONSTEXPR void propagate(T &left, T &right) noexcept(is_nothrow()) { if MDBX_IF_CONSTEXPR (!allocator_is_always_equal()) { - if (MDBX_UNLIKELY(target->get_allocator() != source.get_allocator())) + if (MDBX_UNLIKELY(left.get_allocator() != right.get_allocator())) MDBX_CXX20_UNLIKELY throw_allocators_mismatch(); } else { /* gag for buggy compilers */ - (void)target; - (void)source; + (void)left; + (void)right; } } }; @@ -1147,14 +1170,14 @@ template struct swap_alloc { #endif /* __cpp_lib_is_swappable >= 201603L */ (::std::is_nothrow_move_constructible::value && ::std::is_nothrow_move_assignable::value); } - static MDBX_CXX20_CONSTEXPR void propagate(T *target, T &source) noexcept(is_nothrow()) { + static MDBX_CXX20_CONSTEXPR void propagate(T &left, T &right) noexcept(is_nothrow()) { if MDBX_IF_CONSTEXPR (!allocator_is_always_equal()) { - if (MDBX_UNLIKELY(target->get_allocator() != source.get_allocator())) - MDBX_CXX20_UNLIKELY ::std::swap(*target, source); + if (MDBX_UNLIKELY(left.get_allocator() != right.get_allocator())) + MDBX_CXX20_UNLIKELY ::std::swap(left.get_allocator(), right.get_allocator()); } else { /* gag for buggy compilers */ - (void)target; - (void)source; + (void)left; + (void)right; } } }; @@ -1177,9 +1200,13 @@ struct default_capacity_policy { return (value + pettiness_threshold - 1) & pettiness_mask; } - static MDBX_CXX11_CONSTEXPR size_t advise(const size_t current, const size_t wanna) { + static MDBX_CXX11_CONSTEXPR size_t advise(const size_t current, const size_t wanna, const size_t inplace) { static_assert(max_reserve % pettiness_threshold == 0, "max_reserve must be a multiple of pettiness_threshold"); static_assert(max_reserve / 3 > pettiness_threshold, "max_reserve must be > pettiness_threshold * 3"); + + if (wanna <= inplace && (current <= inplace || current >= std::max(inplace + inplace, size_t(pettiness_threshold)))) + return inplace; + if (wanna > current) /* doubling capacity, but don't made reserve more than max_reserve */ return round(wanna + ::std::min(size_t(max_reserve), current)); @@ -1214,7 +1241,7 @@ struct LIBMDBX_API to_hex { /// \brief Returns a buffer with a hexadecimal dump of a passed slice. template buffer as_buffer(const ALLOCATOR &allocator = ALLOCATOR()) const { - return make_buffer(*this, allocator); + return make_buffer(*this, allocator); } /// \brief Returns the buffer size in bytes needed for hexadecimal @@ -1262,7 +1289,7 @@ struct LIBMDBX_API to_base58 { /// [Base58](https://en.wikipedia.org/wiki/Base58) dump of a passed slice. template buffer as_buffer(const ALLOCATOR &allocator = ALLOCATOR()) const { - return make_buffer(*this, allocator); + return make_buffer(*this, allocator); } /// \brief Returns the buffer size in bytes needed for @@ -1308,7 +1335,7 @@ struct LIBMDBX_API to_base64 { /// [Base64](https://en.wikipedia.org/wiki/Base64) dump of a passed slice. template buffer as_buffer(const ALLOCATOR &allocator = ALLOCATOR()) const { - return make_buffer(*this, allocator); + return make_buffer(*this, allocator); } /// \brief Returns the buffer size in bytes needed for @@ -1359,7 +1386,7 @@ struct LIBMDBX_API from_hex { /// \brief Decodes hexadecimal dump from a passed slice to returned buffer. template buffer as_buffer(const ALLOCATOR &allocator = ALLOCATOR()) const { - return make_buffer(*this, allocator); + return make_buffer(*this, allocator); } /// \brief Returns the number of bytes needed for conversion @@ -1399,7 +1426,7 @@ struct LIBMDBX_API from_base58 { /// passed slice to returned buffer. template buffer as_buffer(const ALLOCATOR &allocator = ALLOCATOR()) const { - return make_buffer(*this, allocator); + return make_buffer(*this, allocator); } /// \brief Returns the number of bytes needed for conversion @@ -1442,7 +1469,7 @@ struct LIBMDBX_API from_base64 { /// passed slice to returned buffer. template buffer as_buffer(const ALLOCATOR &allocator = ALLOCATOR()) const { - return make_buffer(*this, allocator); + return make_buffer(*this, allocator); } /// \brief Returns the number of bytes needed for conversion @@ -1487,7 +1514,6 @@ public: private: friend class txn; - using swap_alloc = allocation_aware_details::swap_alloc; struct silo /* Empty Base Class Optimization */ : public allocator_type { MDBX_CXX20_CONSTEXPR const allocator_type &get_allocator() const noexcept { return *this; } MDBX_CXX20_CONSTEXPR allocator_type &get_allocator() noexcept { return *this; } @@ -1503,12 +1529,19 @@ private: const size_t n = (bytes + unit - 1) / unit; return ::std::make_pair(allocator_traits::allocate(get_allocator(), n), n * unit); } + MDBX_CXX20_CONSTEXPR void deallocate_storage(allocator_pointer ptr, size_t bytes) { constexpr size_t unit = sizeof(typename allocator_type::value_type); assert(ptr && bytes >= sizeof(bin) && bytes >= unit && bytes % unit == 0); allocator_traits::deallocate(get_allocator(), ptr, bytes / unit); } + MDBX_CXX20_CONSTEXPR ::std::pair provide_storage(size_t bytes) { + const size_t capacity = bin::advise_capacity(0, bytes); + return bin::is_suitable_for_inplace(capacity) ? ::std::pair(nullptr, capacity) + : allocate_storage(capacity); + } + static MDBX_CXX17_CONSTEXPR void *to_address(allocator_pointer ptr) noexcept { #if defined(__cpp_lib_to_address) && __cpp_lib_to_address >= 201711L return static_cast(::std::to_address(ptr)); @@ -1516,6 +1549,7 @@ private: return static_cast(::std::addressof(*ptr)); #endif /* __cpp_lib_to_address */ } + static MDBX_CXX17_CONSTEXPR const void *to_address(allocator_const_pointer ptr) noexcept { #if defined(__cpp_lib_to_address) && __cpp_lib_to_address >= 201711L return static_cast(::std::to_address(ptr)); @@ -1524,11 +1558,16 @@ private: #endif /* __cpp_lib_to_address */ } +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4324) /* structure was padded due to alignment specifier */ +#endif /* _MSC_VER */ + union alignas(max_align_t) bin { struct stub_allocated_holder /* используется только для вычисления (минимального необходимого) размера, с учетом выравнивания */ { - allocator_pointer ptr_; + allocator_pointer stub_ptr_; size_t stub_capacity_bytes_; }; @@ -1549,18 +1588,20 @@ private: struct inplace_flag_holder { byte buffer_[inplace_size - sizeof(byte)]; byte lastbyte_; + MDBX_CXX11_CONSTEXPR inplace_flag_holder(byte signature) : lastbyte_(signature) {}; }; allocator_pointer allocated_ptr_; capacity_holder capacity_; inplace_flag_holder inplace_; + static constexpr size_t inplace_capacity() noexcept { return sizeof(inplace_flag_holder::buffer_); } static constexpr bool is_suitable_for_inplace(size_t capacity_bytes) noexcept { static_assert((size_t(reservation_policy::inplace_storage_size_rounding) & (size_t(reservation_policy::inplace_storage_size_rounding) - 1)) == 0, "CAPACITY_POLICY::inplace_storage_size_rounding must be power of 2"); static_assert(sizeof(bin) == sizeof(inplace_) && sizeof(bin) == sizeof(capacity_), "WTF?"); - return capacity_bytes < sizeof(bin); + return capacity_bytes <= inplace_capacity(); } constexpr bool is_inplace() const noexcept { @@ -1578,88 +1619,43 @@ private: /* properly destroy allocator::pointer */ allocated_ptr_.~allocator_pointer(); } - if (::std::is_trivial::value) - /* workaround for "uninitialized" warning from some compilers */ - memset(&allocated_ptr_, 0, sizeof(allocated_ptr_)); inplace_.lastbyte_ = lastbyte_inplace_signature; MDBX_CONSTEXPR_ASSERT(is_inplace() && address() == inplace_.buffer_ && is_suitable_for_inplace(capacity())); return address(); } template - MDBX_CXX17_CONSTEXPR byte *make_allocated(allocator_pointer ptr, size_t capacity_bytes) noexcept { - MDBX_CONSTEXPR_ASSERT(inplace_signature_limit > capacity_bytes); + MDBX_CXX17_CONSTEXPR byte *make_allocated(const ::std::pair &pair) noexcept { + MDBX_CONSTEXPR_ASSERT(inplace_signature_limit > pair.second); if (construct_ptr) { - /* properly construct allocator::pointer */ - new (&allocated_ptr_) allocator_pointer(ptr); - capacity_.bytes_ = capacity_bytes; - } else { - MDBX_CONSTEXPR_ASSERT(is_allocated()); - allocated_ptr_ = ptr; - capacity_.bytes_ = capacity_bytes; - } - MDBX_CONSTEXPR_ASSERT(is_allocated() && address() == to_address(ptr) && capacity() == capacity_bytes); - return address(); - } - - MDBX_CXX20_CONSTEXPR bin(size_t capacity_bytes = 0) noexcept { - MDBX_CONSTEXPR_ASSERT(is_suitable_for_inplace(capacity_bytes)); - make_inplace(); - (void)capacity_bytes; - } - MDBX_CXX20_CONSTEXPR bin(allocator_pointer ptr, size_t capacity_bytes) noexcept { - MDBX_CONSTEXPR_ASSERT(!is_suitable_for_inplace(capacity_bytes)); - make_allocated(ptr, capacity_bytes); - } - MDBX_CXX20_CONSTEXPR ~bin() { - if (is_allocated()) - /* properly destroy allocator::pointer */ - allocated_ptr_.~allocator_pointer(); - } - MDBX_CXX20_CONSTEXPR bin(bin &&ditto) noexcept { - if (ditto.is_inplace()) { - // micro-optimization: don't use make_inplace<> here - // since memcpy() will copy the flag. - memcpy(&inplace_, &ditto.inplace_, sizeof(inplace_)); MDBX_CONSTEXPR_ASSERT(is_inplace()); + new (&allocated_ptr_) allocator_pointer(pair.first); } else { - new (&allocated_ptr_) allocator_pointer(::std::move(ditto.allocated_ptr_)); - capacity_.bytes_ = ditto.capacity_.bytes_; - ditto.make_inplace(); MDBX_CONSTEXPR_ASSERT(is_allocated()); + allocated_ptr_ = pair.first; } + capacity_.bytes_ = pair.second; + MDBX_CONSTEXPR_ASSERT(is_allocated() && address() == to_address(pair.first) && capacity() == pair.second); + return address(); } - MDBX_CXX17_CONSTEXPR bin &operator=(const bin &ditto) noexcept { - if (ditto.is_inplace()) { - // micro-optimization: don't use make_inplace<> here - // since memcpy() will copy the flag. - if (is_allocated()) - /* properly destroy allocator::pointer */ - allocated_ptr_.~allocator_pointer(); - memcpy(&inplace_, &ditto.inplace_, sizeof(inplace_)); - MDBX_CONSTEXPR_ASSERT(is_inplace()); - } else if (is_inplace()) - make_allocated(ditto.allocated_ptr_, ditto.capacity_.bytes_); - else - make_allocated(ditto.allocated_ptr_, ditto.capacity_.bytes_); - return *this; - } - - MDBX_CXX17_CONSTEXPR bin &operator=(bin &&ditto) noexcept { - operator=(const_cast(ditto)); - if (ditto.is_allocated()) - ditto.make_inplace(); - return *this; + MDBX_CXX11_CONSTEXPR bin() noexcept : inplace_(lastbyte_inplace_signature) { + if (::std::is_trivial::value) + /* workaround for "uninitialized" warning from some compilers */ + memset(&allocated_ptr_, 0, sizeof(allocated_ptr_)); } static MDBX_CXX20_CONSTEXPR size_t advise_capacity(const size_t current, const size_t wanna) { if (MDBX_UNLIKELY(wanna > max_capacity)) MDBX_CXX20_UNLIKELY throw_max_length_exceeded(); - const size_t advised = reservation_policy::advise(current, wanna); + const size_t advised = reservation_policy::advise(current, wanna, inplace_capacity()); assert(advised >= wanna); - return ::std::min(size_t(max_capacity), ::std::max(sizeof(bin) - 1, advised)); + return ::std::min(size_t(max_capacity), ::std::max(inplace_capacity(), advised)); + } + + constexpr bool is_inplace(const void *ptr) const noexcept { + return size_t(static_cast(ptr) - inplace_.buffer_) < inplace_capacity(); } constexpr const byte *address() const noexcept { @@ -1668,25 +1664,21 @@ private: MDBX_CXX17_CONSTEXPR byte *address() noexcept { return is_inplace() ? inplace_.buffer_ : static_cast(to_address(allocated_ptr_)); } - constexpr size_t capacity() const noexcept { return is_inplace() ? sizeof(bin) - 1 : capacity_.bytes_; } + constexpr size_t capacity() const noexcept { return is_inplace() ? inplace_capacity() : capacity_.bytes_; } } bin_; - MDBX_CXX20_CONSTEXPR void *init(size_t capacity) { - capacity = bin::advise_capacity(0, capacity); - if (bin_.is_suitable_for_inplace(capacity)) - new (&bin_) bin(); - else { - const auto pair = allocate_storage(capacity); - assert(pair.second >= capacity); - new (&bin_) bin(pair.first, pair.second); - } - return bin_.address(); - } +#ifdef _MSC_VER +#pragma warning(pop) +#endif /* _MSC_VER */ + + constexpr bool is_inplace(const void *ptr) const noexcept { return bin_.is_inplace(ptr); } MDBX_CXX20_CONSTEXPR void release() noexcept { if (bin_.is_allocated()) { deallocate_storage(bin_.allocated_ptr_, bin_.capacity_.bytes_); - bin_.template make_inplace(); + /* properly destroy allocator::pointer */ + bin_.allocated_ptr_.~allocator_pointer(); + bin_.inplace_.lastbyte_ = bin::lastbyte_inplace_signature; } } @@ -1724,13 +1716,13 @@ private: return new_place; } - if (!bin_.is_allocated()) { + if (bin_.is_inplace()) { const auto pair = allocate_storage(new_capacity); assert(pair.second >= new_capacity); byte *const new_place = static_cast(to_address(pair.first)) + wanna_headroom; if (MDBX_LIKELY(length)) MDBX_CXX20_LIKELY memcpy(new_place, content, length); - bin_.template make_allocated(pair.first, pair.second); + bin_.template make_allocated(pair); return new_place; } @@ -1739,7 +1731,7 @@ private: deallocate_storage(old_allocated, old_capacity); const auto pair = allocate_storage(new_capacity); assert(pair.second >= new_capacity); - byte *const new_place = bin_.template make_allocated(pair.first, pair.second) + wanna_headroom; + byte *const new_place = bin_.template make_allocated(pair) + wanna_headroom; if (MDBX_LIKELY(length)) MDBX_CXX20_LIKELY memcpy(new_place, content, length); if (!external_content) @@ -1762,33 +1754,90 @@ private: //-------------------------------------------------------------------------- + MDBX_CXX20_CONSTEXPR silo(const allocator_type &alloc = allocator_type()) noexcept : allocator_type(alloc) {} + MDBX_CXX20_CONSTEXPR silo(size_t capacity, const allocator_type &alloc = allocator_type()) : silo(alloc) { + if (!bin::is_suitable_for_inplace(capacity)) + bin_.template make_allocated(provide_storage(capacity)); + } + + silo(silo &&other) = delete; MDBX_CXX20_CONSTEXPR - silo() noexcept : allocator_type() { init(0); } - MDBX_CXX20_CONSTEXPR - silo(const allocator_type &alloc) noexcept : allocator_type(alloc) { init(0); } - MDBX_CXX20_CONSTEXPR silo(size_t capacity) { init(capacity); } - MDBX_CXX20_CONSTEXPR silo(size_t capacity, const allocator_type &alloc) : silo(alloc) { init(capacity); } + silo(silo &&other, bool is_reference = false) noexcept(::std::is_nothrow_move_constructible::value) + : allocator_type(::std::move(other.get_allocator())) { + if (!is_reference) { + if (other.bin_.is_inplace()) { + memcpy(&bin_, &other.bin_, sizeof(bin)); + MDBX_CONSTEXPR_ASSERT(bin_.is_inplace()); + } else { + new (&bin_.allocated_ptr_) allocator_pointer(::std::move(other.bin_.allocated_ptr_)); + bin_.capacity_.bytes_ = other.bin_.capacity_.bytes_; + /* properly destroy allocator::pointer. + * + * CoverityScan issues an erroneous warning here about using an uninitialized object. Which is not true, + * since in C++ (unlike Rust) an object remains initialized after a move-assignment operation; Moreover, + * a destructor will be called for such an object (this is explicitly stated in all C++ standards, starting + * from the 11th). */ + /* coverity[use_after_move] */ + other.bin_.allocated_ptr_.~allocator_pointer(); + other.bin_.inplace_.lastbyte_ = bin::lastbyte_inplace_signature; + MDBX_CONSTEXPR_ASSERT(bin_.is_allocated() && other.bin_.is_inplace()); + } + } + } - MDBX_CXX20_CONSTEXPR silo(silo &&ditto) noexcept(::std::is_nothrow_move_constructible::value) - : allocator_type(::std::move(ditto.get_allocator())), bin_(::std::move(ditto.bin_)) {} + MDBX_CXX17_CONSTEXPR bool move(silo &&other) noexcept { + if (other.bin_.is_inplace()) { + memcpy(&bin_, &other.bin_, sizeof(bin)); + MDBX_CONSTEXPR_ASSERT(bin_.is_inplace()); + return /* buffer's slice fixup is needed */ true; + } + new (&bin_.allocated_ptr_) allocator_pointer(::std::move(other.bin_.allocated_ptr_)); + bin_.capacity_.bytes_ = other.bin_.capacity_.bytes_; + /* properly destroy allocator::pointer. + * + * CoverityScan issues an erroneous warning here about using an uninitialized object. Which is not true, + * since in C++ (unlike Rust) an object remains initialized after a move-assignment operation; Moreover, + * a destructor will be called for such an object (this is explicitly stated in all C++ standards, starting + * from the 11th). */ + /* coverity[use_after_move] */ + other.bin_.allocated_ptr_.~allocator_pointer(); + other.bin_.inplace_.lastbyte_ = bin::lastbyte_inplace_signature; + MDBX_CONSTEXPR_ASSERT(bin_.is_allocated() && other.bin_.is_inplace()); + return false; + } - MDBX_CXX20_CONSTEXPR silo(size_t capacity, size_t headroom, const void *ptr, size_t length) : silo(capacity) { - assert(capacity >= headroom + length); - if (length) - put(headroom, ptr, length); + MDBX_CXX17_CONSTEXPR bool assign_move(silo &&other, bool is_reference) noexcept { + release(); + if (!allocation_aware_details::move_assign_alloc::is_moveable(this, other)) + allocation_aware_details::move_assign_alloc::propagate(this, other); + return is_reference ? false : move(std::move(other)); + } + + static MDBX_CXX20_CONSTEXPR std::pair + exchange(silo &left, const bool left_is_reference, silo &right, const bool right_is_reference) noexcept( + allocation_aware_details::swap_alloc::is_nothrow()) { + allocation_aware_details::swap_alloc::propagate(left, right); + bool left_need_fixup = false, right_need_fixup = false; + if (left_is_reference || right_is_reference) { + left_need_fixup = !right_is_reference && left.move(std::move(right)); + right_need_fixup = !left_is_reference && right.move(std::move(left)); + } else { + silo temp(std::move(left), false); + left_need_fixup = left.move(std::move(right)); + right_need_fixup = right.move(std::move(temp)); + } + return std::make_pair(left_need_fixup, right_need_fixup); } - // select_on_container_copy_construction() MDBX_CXX20_CONSTEXPR silo(size_t capacity, size_t headroom, const void *ptr, size_t length, - const allocator_type &alloc) + const allocator_type &alloc = allocator_type()) : silo(capacity, alloc) { assert(capacity >= headroom + length); if (length) put(headroom, ptr, length); } - MDBX_CXX20_CONSTEXPR silo(const void *ptr, size_t length) : silo(length, 0, ptr, length) {} - MDBX_CXX20_CONSTEXPR silo(const void *ptr, size_t length, const allocator_type &alloc) + MDBX_CXX20_CONSTEXPR silo(const void *ptr, size_t length, const allocator_type &alloc = allocator_type()) : silo(length, 0, ptr, length, alloc) {} ~silo() { release(); } @@ -1798,53 +1847,17 @@ private: MDBX_CXX20_CONSTEXPR void *assign(size_t headroom, const void *ptr, size_t length, size_t tailroom) { return reshape(headroom + length + tailroom, headroom, ptr, length); } - MDBX_CXX20_CONSTEXPR void *assign(const void *ptr, size_t length) { return assign(0, ptr, length, 0); } - MDBX_CXX20_CONSTEXPR silo &assign(const silo &ditto, size_t headroom, slice &content) { - assert(ditto.get() + headroom == content.byte_ptr()); - if MDBX_IF_CONSTEXPR (!allocation_aware_details::allocator_is_always_equal()) { - if (MDBX_UNLIKELY(get_allocator() != ditto.get_allocator())) - MDBX_CXX20_UNLIKELY { - release(); - allocation_aware_details::copy_assign_alloc::propagate(this, ditto); - } - } - content.iov_base = reshape(ditto.capacity(), headroom, content.data(), content.length()); - return *this; - } - - MDBX_CXX20_CONSTEXPR silo & - assign(silo &&ditto, size_t headroom, - slice &content) noexcept(allocation_aware_details::move_assign_alloc::is_nothrow()) { - assert(ditto.get() + headroom == content.byte_ptr()); - if (allocation_aware_details::move_assign_alloc::is_moveable(this, ditto)) { - release(); - allocation_aware_details::move_assign_alloc::propagate(this, ditto); - /* no reallocation nor copying required */ - bin_ = ::std::move(ditto.bin_); - assert(get() + headroom == content.byte_ptr()); - } else { - /* copy content since allocators are different */ - content.iov_base = reshape(ditto.capacity(), headroom, content.data(), content.length()); - ditto.release(); - } - return *this; - } + MDBX_CXX20_CONSTEXPR void *assign(const void *ptr, size_t length) { return assign(0, ptr, length, 0); } MDBX_CXX20_CONSTEXPR void *clear() { return reshape(0, 0, nullptr, 0); } MDBX_CXX20_CONSTEXPR void *clear_and_reserve(size_t whole_capacity, size_t headroom) { return reshape(whole_capacity, headroom, nullptr, 0); } + MDBX_CXX20_CONSTEXPR void resize(size_t capacity, size_t headroom, slice &content) { content.iov_base = reshape(capacity, headroom, content.iov_base, content.iov_len); } - MDBX_CXX20_CONSTEXPR void - swap(silo &ditto) noexcept(allocation_aware_details::swap_alloc::is_nothrow()) { - allocation_aware_details::swap_alloc::propagate(this, ditto); - ::std::swap(bin_, ditto.bin_); - } - - /* MDBX_CXX20_CONSTEXPR void shrink_to_fit() { TODO } */ MDBX_NOTHROW_PURE_FUNCTION MDBX_CXX11_CONSTEXPR size_t capacity() const noexcept { return bin_.capacity(); } MDBX_NOTHROW_PURE_FUNCTION MDBX_CXX11_CONSTEXPR const void *data(size_t offset = 0) const noexcept { @@ -1853,6 +1866,16 @@ private: MDBX_NOTHROW_PURE_FUNCTION MDBX_CXX11_CONSTEXPR void *data(size_t offset = 0) noexcept { return get(offset); } }; + MDBX_CXX14_CONSTEXPR void fixup_import(const typename silo::bin &src) noexcept { + auto ptr = slice_.byte_ptr(); + if (src.is_inplace(ptr)) { + MDBX_CONSTEXPR_ASSERT(silo_.bin_.is_inplace()); + intptr_t offset = &silo_.bin_.inplace_.buffer_[0] - &src.inplace_.buffer_[0]; + slice_.iov_base = ptr + offset; + MDBX_CONSTEXPR_ASSERT(is_freestanding()); + } + } + silo silo_; ::mdbx::slice slice_; @@ -1898,6 +1921,9 @@ public: using move_assign_alloc = allocation_aware_details::move_assign_alloc; using copy_assign_alloc = allocation_aware_details::copy_assign_alloc; + using swap_alloc = allocation_aware_details::swap_alloc; + + static constexpr bool is_swap_nothrow() noexcept { return swap_alloc::is_nothrow(); } /// \brief Returns the associated allocator. MDBX_CXX20_CONSTEXPR allocator_type get_allocator() const { return silo_.get_allocator(); } @@ -1905,10 +1931,18 @@ public: /// \brief Checks whether data chunk stored inside the buffer, otherwise /// buffer just refers to data located outside the buffer. MDBX_NOTHROW_PURE_FUNCTION MDBX_CXX20_CONSTEXPR bool is_freestanding() const noexcept { - static_assert(size_t(-long(max_length)) > max_length, "WTF?"); + static_assert(size_t(-intptr_t(max_length)) > max_length, "WTF?"); return size_t(byte_ptr() - silo_begin()) < silo_.capacity(); } + /// \brief Checks whether data chunk stored in place within the buffer instance itself, + /// without reference outside nor allocating additional memory resources, + /// which also implies buffer is freestanding. + MDBX_NOTHROW_PURE_FUNCTION MDBX_CXX20_CONSTEXPR bool is_inplace() const noexcept { + static_assert(size_t(-intptr_t(max_length)) > max_length, "WTF?"); + return silo_.is_inplace(slice_.data()); + } + /// \brief Checks whether the buffer just refers to data located outside /// the buffer, rather than stores it. MDBX_NOTHROW_PURE_FUNCTION MDBX_CXX20_CONSTEXPR bool is_reference() const noexcept { return !is_freestanding(); } @@ -1921,7 +1955,7 @@ public: /// \brief Returns the number of bytes that available in currently allocated /// storage ahead the currently beginning of data. MDBX_NOTHROW_PURE_FUNCTION MDBX_CXX20_CONSTEXPR size_t headroom() const noexcept { - return is_freestanding() ? slice_.byte_ptr() - silo_begin() : 0; + return is_freestanding() ? byte_ptr() - silo_begin() : 0; } /// \brief Returns the number of bytes that available in currently allocated @@ -2019,15 +2053,18 @@ public: MDBX_CXX20_CONSTEXPR buffer() noexcept = default; MDBX_CXX20_CONSTEXPR buffer(const allocator_type &allocator) noexcept : silo_(allocator) {} + MDBX_CXX20_CONSTEXPR buffer(const struct slice &src, bool make_reference, const allocator_type &allocator = allocator_type()) : silo_(allocator), slice_(src) { if (!make_reference) insulate(); } + MDBX_CXX20_CONSTEXPR buffer(const buffer &src, bool make_reference, const allocator_type &allocator = allocator_type()) : buffer(src.slice_, make_reference, allocator) {} + MDBX_CXX20_CONSTEXPR buffer(const void *ptr, size_t bytes, bool make_reference, const allocator_type &allocator = allocator_type()) : buffer(::mdbx::slice(ptr, bytes), make_reference, allocator) {} @@ -2039,17 +2076,23 @@ public: #if defined(DOXYGEN) || (defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L) template - buffer(const ::std::basic_string_view &view, bool make_reference, - const allocator_type &allocator = allocator_type()) + MDBX_CXX20_CONSTEXPR buffer(const ::std::basic_string_view &view, bool make_reference, + const allocator_type &allocator = allocator_type()) : buffer(::mdbx::slice(view), make_reference, allocator) {} #endif /* __cpp_lib_string_view >= 201606L */ MDBX_CXX20_CONSTEXPR - buffer(const struct slice &src, const allocator_type &allocator = allocator_type()) - : silo_(src.data(), src.length(), allocator), slice_(silo_.data(), src.length()) {} + buffer(const struct slice &src, const allocator_type &allocator = allocator_type()) : silo_(allocator), slice_(src) { + if (!src.empty()) + insulate(); + } + + MDBX_CXX20_CONSTEXPR + buffer(const buffer &src, const allocator_type &allocator) : buffer(src.slice_, allocator) {} MDBX_CXX20_CONSTEXPR - buffer(const buffer &src, const allocator_type &allocator = allocator_type()) : buffer(src.slice_, allocator) {} + buffer(const buffer &src) + : buffer(src.slice_, allocator_traits::select_on_container_copy_construction(src.get_allocator())) {} MDBX_CXX20_CONSTEXPR buffer(const void *ptr, size_t bytes, const allocator_type &allocator = allocator_type()) @@ -2071,20 +2114,22 @@ public: : buffer(::mdbx::slice(view), allocator) {} #endif /* __cpp_lib_string_view >= 201606L */ - buffer(size_t head_room, size_t tail_room, const allocator_type &allocator = allocator_type()) : silo_(allocator) { - slice_.iov_base = silo_.init(check_length(head_room, tail_room)); + buffer(size_t head_room, size_t tail_room, const allocator_type &allocator = allocator_type()) + : silo_(check_length(head_room, tail_room), allocator) { + slice_.iov_base = silo_.get(); assert(slice_.iov_len == 0); } - buffer(size_t capacity, const allocator_type &allocator = allocator_type()) : silo_(allocator) { - slice_.iov_base = silo_.init(check_length(capacity)); + buffer(size_t capacity, const allocator_type &allocator = allocator_type()) + : silo_(check_length(capacity), allocator) { + slice_.iov_base = silo_.get(); assert(slice_.iov_len == 0); } buffer(size_t head_room, const struct slice &src, size_t tail_room, const allocator_type &allocator = allocator_type()) - : silo_(allocator) { - slice_.iov_base = silo_.init(check_length(head_room, src.length(), tail_room)); + : silo_(check_length(head_room, src.length(), tail_room), allocator) { + slice_.iov_base = silo_.get(); slice_.iov_len = src.length(); memcpy(slice_.iov_base, src.data(), src.length()); } @@ -2095,7 +2140,24 @@ public: inline buffer(const ::mdbx::txn &txn, const struct slice &src, const allocator_type &allocator = allocator_type()); buffer(buffer &&src) noexcept(move_assign_alloc::is_nothrow()) - : silo_(::std::move(src.silo_)), slice_(::std::move(src.slice_)) {} + : silo_(::std::move(src.silo_), src.is_reference()), slice_(::std::move(src.slice_)) { + /* CoverityScan issues an erroneous warning here about using an uninitialized object. Which is not true, + * since in C++ (unlike Rust) an object remains initialized after a move-assignment operation; Moreover, + * a destructor will be called for such an object (this is explicitly stated in all C++ standards, starting from the + * 11th). */ + /* coverity[use_after_move] */ + fixup_import(src.silo_.bin_); + } + + /// \brief Build a null buffer which zero length and refers to null address. + MDBX_CXX14_CONSTEXPR static buffer null() noexcept { return buffer(slice::null()); } + + /// \brief Build an invalid buffer which non-zero length and refers to null address. + MDBX_CXX14_CONSTEXPR static buffer invalid() noexcept { + buffer r; + r.slice_ = slice::invalid(); + return r; + } MDBX_CXX11_CONSTEXPR const struct slice &slice() const noexcept { return slice_; } @@ -2254,8 +2316,12 @@ public: /// \brief Reserves storage space. void reserve(size_t wanna_headroom, size_t wanna_tailroom) { - wanna_headroom = ::std::min(::std::max(headroom(), wanna_headroom), wanna_headroom + pettiness_threshold); - wanna_tailroom = ::std::min(::std::max(tailroom(), wanna_tailroom), wanna_tailroom + pettiness_threshold); + wanna_headroom = ::std::min( + ::std::max(headroom(), wanna_headroom), + (wanna_headroom < max_length - pettiness_threshold) ? wanna_headroom + pettiness_threshold : wanna_headroom); + wanna_tailroom = ::std::min( + ::std::max(tailroom(), wanna_tailroom), + (wanna_tailroom < max_length - pettiness_threshold) ? wanna_tailroom + pettiness_threshold : wanna_tailroom); const size_t wanna_capacity = check_length(wanna_headroom, slice_.length(), wanna_tailroom); silo_.resize(wanna_capacity, wanna_headroom, slice_); assert(headroom() >= wanna_headroom && headroom() <= wanna_headroom + pettiness_threshold); @@ -2275,21 +2341,75 @@ public: } buffer &assign_freestanding(const void *ptr, size_t bytes) { - silo_.assign(static_cast(ptr), check_length(bytes)); - slice_.assign(silo_.data(), bytes); + slice_.assign(silo_.assign(static_cast(ptr), check_length(bytes)), bytes); return *this; } MDBX_CXX20_CONSTEXPR void swap(buffer &other) noexcept(swap_alloc::is_nothrow()) { - silo_.swap(other.silo_); - slice_.swap(other.slice_); + const auto pair = silo::exchange(silo_, is_reference(), other.silo_, other.is_reference()); + std::swap(slice_, other.slice_); + if (pair.first) + fixup_import(other.silo_.bin_); + if (pair.second) + other.fixup_import(silo_.bin_); + } + + MDBX_CXX20_CONSTEXPR friend void swap(buffer &left, buffer &right) noexcept(buffer::is_swap_nothrow()) { + left.swap(right); } static buffer clone(const buffer &src, const allocator_type &allocator = allocator_type()) { return buffer(src.headroom(), src.slice_, src.tailroom(), allocator); } - buffer &assign(const buffer &src, bool make_reference = false) { return assign(src.slice_, make_reference); } + MDBX_CXX20_CONSTEXPR buffer make_inplace_or_reference() const { + return buffer(slice(), !is_inplace(), allocator_traits::select_on_container_copy_construction(get_allocator())); + } + + MDBX_CXX20_CONSTEXPR buffer &assign(size_t headroom, const buffer &src, size_t tailroom) { + const size_t whole_capacity = check_length(headroom, src.length(), tailroom); + slice_.invalidate(); + if MDBX_IF_CONSTEXPR (!allocation_aware_details::template allocator_is_always_equal()) { + if (MDBX_UNLIKELY(silo_.get_allocator() != src.silo_.get_allocator())) + MDBX_CXX20_UNLIKELY { + silo_.release(); + allocation_aware_details::copy_assign_alloc::propagate(&silo_, src.silo_); + } + } + + slice_.iov_base = silo_.template reshape(whole_capacity, headroom, src.data(), src.length()); + slice_.iov_len = src.length(); + return *this; + } + + MDBX_CXX20_CONSTEXPR buffer &assign(const buffer &src, bool make_reference = false) { + slice_.invalidate(); + if MDBX_IF_CONSTEXPR (!allocation_aware_details::template allocator_is_always_equal()) { + if (MDBX_UNLIKELY(silo_.get_allocator() != src.silo_.get_allocator())) + MDBX_CXX20_UNLIKELY { + silo_.release(); + allocation_aware_details::copy_assign_alloc::propagate(&silo_, src.silo_); + } + } + + if (make_reference) { + silo_.release(); + slice_ = src.slice_; + } else { + slice_.iov_base = silo_.template reshape(src.length(), 0, src.data(), src.length()); + slice_.iov_len = src.length(); + } + return *this; + } + + MDBX_CXX20_CONSTEXPR buffer & + assign(buffer &&src) noexcept(allocation_aware_details::move_assign_alloc::is_nothrow()) { + const bool is_reference = src.is_reference(); + slice_ = std::move(src.slice_); + if (silo_.assign_move(std::move(src.silo_), is_reference)) + fixup_import(src.silo_.bin_); + return *this; + } buffer &assign(const void *ptr, size_t bytes, bool make_reference = false) { return make_reference ? assign_reference(ptr, bytes) : assign_freestanding(ptr, bytes); @@ -2410,7 +2530,7 @@ public: } /// \brief Reduces memory usage by freeing unused storage space. - void shrink_to_fit() { reserve(0, 0); } + void shrink_to_fit() { silo_.resize(length(), 0, slice_); } /// \brief Drops the first "n" bytes from the data chunk. /// \pre REQUIRES: `n <= size()` @@ -2486,6 +2606,14 @@ public: return *this; } + buffer &append(const byte c) { + if (MDBX_UNLIKELY(tailroom() < 1)) + MDBX_CXX20_UNLIKELY reserve_tailroom(1); + *end_byte_ptr() = c; + slice_.iov_len += 1; + return *this; + } + buffer &append(const struct slice &chunk) { return append(chunk.data(), chunk.size()); } buffer &add_header(const void *src, size_t bytes) { @@ -2629,7 +2757,7 @@ public: return buffer(src, make_reference); } - static buffer key_from(silo &&src) noexcept { return buffer(::std::move(src)); } + static buffer key_from(buffer &&src) noexcept { return buffer(::std::move(src)); } static buffer key_from_double(const double ieee754_64bit) { return wrap(::mdbx_key_from_double(ieee754_64bit)); } @@ -2708,11 +2836,13 @@ inline string make_string(const PRODUCER &producer, const ALLOCATOR & return result; } -MDBX_EXTERN_API_TEMPLATE(LIBMDBX_API_TYPE, buffer); +#if !(defined(__MINGW__) || defined(__MINGW32__) || defined(__MINGW64__)) || defined(LIBMDBX_EXPORTS) +MDBX_EXTERN_API_TEMPLATE(LIBMDBX_API_TYPE, buffer); -#if defined(__cpp_lib_memory_resource) && __cpp_lib_memory_resource >= 201603L && _GLIBCXX_USE_CXX11_ABI -MDBX_EXTERN_API_TEMPLATE(LIBMDBX_API_TYPE, buffer); -#endif /* __cpp_lib_memory_resource >= 201603L */ +#if MDBX_CXX_HAS_POLYMORPHIC_ALLOCATOR +MDBX_EXTERN_API_TEMPLATE(LIBMDBX_API_TYPE, buffer); +#endif /* MDBX_CXX_HAS_POLYMORPHIC_ALLOCATOR */ +#endif /* !MinGW || MDBX_EXPORTS */ /// \brief Combines data slice with boolean flag to represent result of certain operations. struct value_result { @@ -2982,6 +3112,30 @@ struct LIBMDBX_API_TYPE map_handle { operator bool() const noexcept { return dbi != 0; } operator MDBX_dbi() const { return dbi; } +#if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201907L + friend MDBX_CXX11_CONSTEXPR auto operator<=>(const map_handle &a, const map_handle &b) noexcept { + return a.dbi <=> b.dbi; + } +#endif /* __cpp_impl_three_way_comparison */ + friend MDBX_CXX14_CONSTEXPR bool operator==(const map_handle &a, const map_handle &b) noexcept { + return a.dbi == b.dbi; + } + friend MDBX_CXX14_CONSTEXPR bool operator<(const map_handle &a, const map_handle &b) noexcept { + return a.dbi < b.dbi; + } + friend MDBX_CXX14_CONSTEXPR bool operator>(const map_handle &a, const map_handle &b) noexcept { + return a.dbi > b.dbi; + } + friend MDBX_CXX14_CONSTEXPR bool operator<=(const map_handle &a, const map_handle &b) noexcept { + return a.dbi <= b.dbi; + } + friend MDBX_CXX14_CONSTEXPR bool operator>=(const map_handle &a, const map_handle &b) noexcept { + return a.dbi >= b.dbi; + } + friend MDBX_CXX14_CONSTEXPR bool operator!=(const map_handle &a, const map_handle &b) noexcept { + return a.dbi != b.dbi; + } + using flags = ::MDBX_db_flags_t; using state = ::MDBX_dbi_state_t; struct LIBMDBX_API_TYPE info { @@ -3012,12 +3166,11 @@ enum put_mode { /// \brief Unmanaged database environment. /// -/// Like other unmanaged classes, `env` allows copying and assignment for -/// instances, but does not destroys the represented underlying object from the -/// own class destructor. +/// Like other unmanaged classes, `env` allows copying and assignment for handles as a values, +/// but does not manage nor destroys the represented underlying object from the own class destructor. /// /// An environment supports multiple key-value tables (aka key-value maps, -/// spaces or sub-databases), all residing in the same shared-memory mapped +/// tables or sub-databases), all residing in the same shared-memory mapped /// file. class LIBMDBX_API_TYPE env { friend class txn; @@ -3029,8 +3182,9 @@ protected: public: MDBX_CXX11_CONSTEXPR env() noexcept = default; env(const env &) noexcept = default; - inline env &operator=(env &&other) noexcept; - inline env(env &&other) noexcept; + env &operator=(const env &) noexcept = default; + inline env &operator=(env &&) noexcept; + inline env(env &&) noexcept; inline ~env() noexcept; MDBX_CXX14_CONSTEXPR operator bool() const noexcept; @@ -3715,9 +3869,8 @@ public: /// \brief Unmanaged database transaction. /// -/// Like other unmanaged classes, `txn` allows copying and assignment for -/// instances, but does not destroys the represented underlying object from the -/// own class destructor. +/// Like other unmanaged classes, `txn` allows copying and assignment for handles as a values, +/// but does not manage nor destroys the represented underlying object from the own class destructor. /// /// All database operations require a transaction handle. Transactions may be /// read-only or read-write. @@ -3730,8 +3883,9 @@ protected: public: MDBX_CXX11_CONSTEXPR txn() noexcept = default; txn(const txn &) noexcept = default; - inline txn &operator=(txn &&other) noexcept; - inline txn(txn &&other) noexcept; + txn &operator=(const txn &) noexcept = default; + inline txn &operator=(txn &&) noexcept; + inline txn(txn &&) noexcept; inline ~txn() noexcept; MDBX_CXX14_CONSTEXPR operator bool() const noexcept; @@ -3756,6 +3910,9 @@ public: /// \brief Checks whether the given data is on a dirty page. inline bool is_dirty(const void *ptr) const; + /// \brief Checks whether the given slice is on a dirty page. + inline bool is_dirty(const slice &item) const { return item && is_dirty(item.data()); } + /// \brief Checks whether the transaction is read-only. bool is_readonly() const { return (flags() & MDBX_TXN_RDONLY) != 0; } @@ -4123,9 +4280,8 @@ public: /// \brief Unmanaged cursor. /// -/// Like other unmanaged classes, `cursor` allows copying and assignment for -/// instances, but does not destroys the represented underlying object from the -/// own class destructor. +/// Like other unmanaged classes, `cursor` allows copying and assignment for handles as a values, +/// but does not manage nor destroys the represented underlying object from the own class destructor. /// /// \copydetails MDBX_cursor class LIBMDBX_API_TYPE cursor { @@ -4136,8 +4292,9 @@ public: MDBX_CXX11_CONSTEXPR cursor(MDBX_cursor *ptr) noexcept; MDBX_CXX11_CONSTEXPR cursor() noexcept = default; cursor(const cursor &) noexcept = default; - inline cursor &operator=(cursor &&other) noexcept; - inline cursor(cursor &&other) noexcept; + cursor &operator=(const cursor &) noexcept = default; + inline cursor &operator=(cursor &&) noexcept; + inline cursor(cursor &&) noexcept; inline ~cursor() noexcept; inline cursor_managed clone(void *your_context = nullptr) const; MDBX_CXX14_CONSTEXPR operator bool() const noexcept; @@ -4826,12 +4983,6 @@ inline slice &slice::operator=(slice &&src) noexcept { return assign(::std::move inline slice &slice::operator=(::MDBX_val &&src) { return assign(::std::move(src)); } -inline void slice::swap(slice &other) noexcept { - const auto temp = *this; - *this = other; - other = temp; -} - MDBX_CXX11_CONSTEXPR const ::mdbx::byte *slice::byte_ptr() const noexcept { return static_cast(iov_base); } @@ -6413,7 +6564,32 @@ inline string to_string(const ::mdbx::error &value) { inline string to_string(const ::MDBX_error_t &errcode) { return to_string(::mdbx::error(errcode)); } template <> struct hash<::mdbx::slice> { - MDBX_CXX14_CONSTEXPR size_t operator()(::mdbx::slice const &slice) const noexcept { return slice.hash_value(); } + MDBX_CXX14_CONSTEXPR size_t operator()(const ::mdbx::slice &slice) const noexcept { return slice.hash_value(); } +}; + +template <> struct hash<::mdbx::map_handle> { + MDBX_CXX11_CONSTEXPR size_t operator()(const ::mdbx::map_handle &handle) const noexcept { return handle.dbi; } +}; + +template <> struct hash<::mdbx::env> : public std::hash { + using inherited = std::hash; + size_t operator()(const ::mdbx::env &env) const noexcept { return inherited::operator()(env); } +}; + +template <> struct hash<::mdbx::txn> : public std::hash { + using inherited = std::hash; + size_t operator()(const ::mdbx::txn &txn) const noexcept { return inherited::operator()(txn); } +}; + +template <> struct hash<::mdbx::cursor> : public std::hash { + using inherited = std::hash; + size_t operator()(const ::mdbx::cursor &cursor) const noexcept { return inherited::operator()(cursor); } +}; + +template struct hash<::mdbx::buffer> { + size_t operator()(::mdbx::buffer const &buffer) const noexcept { + return buffer.hash_value(); + } }; /// end cxx_api @} diff --git a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx_chk.c b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx_chk.c index fdf5f8b406c..c87e543daa8 100644 --- a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx_chk.c +++ b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx_chk.c @@ -1,5 +1,5 @@ /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 /// /// @@ -16,9 +16,9 @@ #define xMDBX_TOOLS /* Avoid using internal eASSERT() */ /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 -#define MDBX_BUILD_SOURCERY 6b5df6869d2bf5419e3a8189d9cc849cc9911b9c8a951b9750ed0a261ce43724_v0_13_7_0_g566b0f93 +#define MDBX_BUILD_SOURCERY a575a490fc080ca11e89ff6db9f0bd38aa830959905998cac0e45274b9e6bb0e_v0_13_12_0_gf619d43d #define LIBMDBX_INTERNALS #define MDBX_DEPRECATED @@ -1187,6 +1187,14 @@ typedef char pathchar_t; #define MDBX_PRIsPATH "s" #endif +MDBX_MAYBE_UNUSED static inline bool osal_yield(void) { +#if defined(_WIN32) || defined(_WIN64) + return SleepEx(0, true) == WAIT_IO_COMPLETION; +#else + return sched_yield() != 0; +#endif +} + typedef struct osal_mmap { union { void *base; @@ -1206,6 +1214,8 @@ typedef struct osal_mmap { #define MDBX_HAVE_PWRITEV 0 +MDBX_INTERNAL int osal_waitstatus2errcode(DWORD result); + #elif defined(__ANDROID_API__) #if __ANDROID_API__ < 24 @@ -1449,7 +1459,7 @@ enum osal_syncmode_bits { }; MDBX_INTERNAL int osal_fsync(mdbx_filehandle_t fd, const enum osal_syncmode_bits mode_bits); -MDBX_INTERNAL int osal_ftruncate(mdbx_filehandle_t fd, uint64_t length); +MDBX_INTERNAL int osal_fsetsize(mdbx_filehandle_t fd, const uint64_t length); MDBX_INTERNAL int osal_fseek(mdbx_filehandle_t fd, uint64_t pos); MDBX_INTERNAL int osal_filesize(mdbx_filehandle_t fd, uint64_t *length); @@ -1485,11 +1495,11 @@ MDBX_INTERNAL int osal_removedirectory(const pathchar_t *pathname); MDBX_INTERNAL int osal_is_pipe(mdbx_filehandle_t fd); MDBX_INTERNAL int osal_lockfile(mdbx_filehandle_t fd, bool wait); -#define MMAP_OPTION_TRUNCATE 1 +#define MMAP_OPTION_SETLENGTH 1 #define MMAP_OPTION_SEMAPHORE 2 MDBX_INTERNAL int osal_mmap(const int flags, osal_mmap_t *map, size_t size, const size_t limit, const unsigned options, const pathchar_t *pathname4logging); -MDBX_INTERNAL int osal_munmap(osal_mmap_t *map); +MDBX_INTERNAL void osal_munmap(osal_mmap_t *map); #define MDBX_MRESIZE_MAY_MOVE 0x00000100 #define MDBX_MRESIZE_MAY_UNMAP 0x00000200 MDBX_INTERNAL int osal_mresize(const int flags, osal_mmap_t *map, size_t size, size_t limit); @@ -1892,7 +1902,8 @@ MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline uint32_t osal_bswap32 ((defined(_POSIX_THREAD_ROBUST_PRIO_INHERIT) && _POSIX_THREAD_ROBUST_PRIO_INHERIT > 0) || \ (defined(_POSIX_THREAD_ROBUST_PRIO_PROTECT) && _POSIX_THREAD_ROBUST_PRIO_PROTECT > 0) || \ defined(PTHREAD_MUTEX_ROBUST) || defined(PTHREAD_MUTEX_ROBUST_NP)) && \ - (!defined(__GLIBC__) || __GLIBC_PREREQ(2, 10) /* troubles with Robust mutexes before 2.10 */) + (!defined(__GLIBC__) || __GLIBC_PREREQ(2, 10) /* troubles with Robust mutexes before 2.10 */) && \ + !defined(__OHOS__) /* Harmony OS doesn't support robust mutexes at the end of 2025 */ #define MDBX_LOCKING MDBX_LOCKING_POSIX2008 #else #define MDBX_LOCKING MDBX_LOCKING_POSIX2001 @@ -1947,6 +1958,22 @@ MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline uint32_t osal_bswap32 #error MDBX_USE_COPYFILERANGE must be defined as 0 or 1 #endif /* MDBX_USE_COPYFILERANGE */ +/** Advanced: Using posix_fallocate() or fcntl(F_PREALLOCATE) on OSX (autodetection by default). */ +#ifndef MDBX_USE_FALLOCATE +#if defined(__APPLE__) +#define MDBX_USE_FALLOCATE 0 /* Too slow and unclean, but not required to prevent SIGBUS */ +#elif (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L) || (__GLIBC_PREREQ(2, 10) && defined(_GNU_SOURCE)) +#define MDBX_USE_FALLOCATE 1 +#else +#define MDBX_USE_FALLOCATE 0 +#endif +#define MDBX_USE_FALLOCATE_CONFIG "AUTO=" MDBX_STRINGIFY(MDBX_USE_FALLOCATE) +#elif !(MDBX_USE_FALLOCATE == 0 || MDBX_USE_FALLOCATE == 1) +#error MDBX_USE_FALLOCATE must be defined as 0 or 1 +#else +#define MDBX_USE_FALLOCATE_CONFIG MDBX_STRINGIFY(MDBX_USE_FALLOCATE) +#endif /* MDBX_USE_FALLOCATE */ + //------------------------------------------------------------------------------ #ifndef MDBX_CPU_WRITEBACK_INCOHERENT diff --git a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx_copy.c b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx_copy.c index 96e8c485c71..f31cb1b973a 100644 --- a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx_copy.c +++ b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx_copy.c @@ -1,7 +1,7 @@ /// \copyright SPDX-License-Identifier: Apache-2.0 /// \note Please refer to the COPYRIGHT file for explanations license change, /// credits and acknowledgments. -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 /// /// mdbx_copy.c - memory-mapped database backup tool /// @@ -16,9 +16,9 @@ #define xMDBX_TOOLS /* Avoid using internal eASSERT() */ /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 -#define MDBX_BUILD_SOURCERY 6b5df6869d2bf5419e3a8189d9cc849cc9911b9c8a951b9750ed0a261ce43724_v0_13_7_0_g566b0f93 +#define MDBX_BUILD_SOURCERY a575a490fc080ca11e89ff6db9f0bd38aa830959905998cac0e45274b9e6bb0e_v0_13_12_0_gf619d43d #define LIBMDBX_INTERNALS #define MDBX_DEPRECATED @@ -1187,6 +1187,14 @@ typedef char pathchar_t; #define MDBX_PRIsPATH "s" #endif +MDBX_MAYBE_UNUSED static inline bool osal_yield(void) { +#if defined(_WIN32) || defined(_WIN64) + return SleepEx(0, true) == WAIT_IO_COMPLETION; +#else + return sched_yield() != 0; +#endif +} + typedef struct osal_mmap { union { void *base; @@ -1206,6 +1214,8 @@ typedef struct osal_mmap { #define MDBX_HAVE_PWRITEV 0 +MDBX_INTERNAL int osal_waitstatus2errcode(DWORD result); + #elif defined(__ANDROID_API__) #if __ANDROID_API__ < 24 @@ -1449,7 +1459,7 @@ enum osal_syncmode_bits { }; MDBX_INTERNAL int osal_fsync(mdbx_filehandle_t fd, const enum osal_syncmode_bits mode_bits); -MDBX_INTERNAL int osal_ftruncate(mdbx_filehandle_t fd, uint64_t length); +MDBX_INTERNAL int osal_fsetsize(mdbx_filehandle_t fd, const uint64_t length); MDBX_INTERNAL int osal_fseek(mdbx_filehandle_t fd, uint64_t pos); MDBX_INTERNAL int osal_filesize(mdbx_filehandle_t fd, uint64_t *length); @@ -1485,11 +1495,11 @@ MDBX_INTERNAL int osal_removedirectory(const pathchar_t *pathname); MDBX_INTERNAL int osal_is_pipe(mdbx_filehandle_t fd); MDBX_INTERNAL int osal_lockfile(mdbx_filehandle_t fd, bool wait); -#define MMAP_OPTION_TRUNCATE 1 +#define MMAP_OPTION_SETLENGTH 1 #define MMAP_OPTION_SEMAPHORE 2 MDBX_INTERNAL int osal_mmap(const int flags, osal_mmap_t *map, size_t size, const size_t limit, const unsigned options, const pathchar_t *pathname4logging); -MDBX_INTERNAL int osal_munmap(osal_mmap_t *map); +MDBX_INTERNAL void osal_munmap(osal_mmap_t *map); #define MDBX_MRESIZE_MAY_MOVE 0x00000100 #define MDBX_MRESIZE_MAY_UNMAP 0x00000200 MDBX_INTERNAL int osal_mresize(const int flags, osal_mmap_t *map, size_t size, size_t limit); @@ -1892,7 +1902,8 @@ MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline uint32_t osal_bswap32 ((defined(_POSIX_THREAD_ROBUST_PRIO_INHERIT) && _POSIX_THREAD_ROBUST_PRIO_INHERIT > 0) || \ (defined(_POSIX_THREAD_ROBUST_PRIO_PROTECT) && _POSIX_THREAD_ROBUST_PRIO_PROTECT > 0) || \ defined(PTHREAD_MUTEX_ROBUST) || defined(PTHREAD_MUTEX_ROBUST_NP)) && \ - (!defined(__GLIBC__) || __GLIBC_PREREQ(2, 10) /* troubles with Robust mutexes before 2.10 */) + (!defined(__GLIBC__) || __GLIBC_PREREQ(2, 10) /* troubles with Robust mutexes before 2.10 */) && \ + !defined(__OHOS__) /* Harmony OS doesn't support robust mutexes at the end of 2025 */ #define MDBX_LOCKING MDBX_LOCKING_POSIX2008 #else #define MDBX_LOCKING MDBX_LOCKING_POSIX2001 @@ -1947,6 +1958,22 @@ MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline uint32_t osal_bswap32 #error MDBX_USE_COPYFILERANGE must be defined as 0 or 1 #endif /* MDBX_USE_COPYFILERANGE */ +/** Advanced: Using posix_fallocate() or fcntl(F_PREALLOCATE) on OSX (autodetection by default). */ +#ifndef MDBX_USE_FALLOCATE +#if defined(__APPLE__) +#define MDBX_USE_FALLOCATE 0 /* Too slow and unclean, but not required to prevent SIGBUS */ +#elif (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L) || (__GLIBC_PREREQ(2, 10) && defined(_GNU_SOURCE)) +#define MDBX_USE_FALLOCATE 1 +#else +#define MDBX_USE_FALLOCATE 0 +#endif +#define MDBX_USE_FALLOCATE_CONFIG "AUTO=" MDBX_STRINGIFY(MDBX_USE_FALLOCATE) +#elif !(MDBX_USE_FALLOCATE == 0 || MDBX_USE_FALLOCATE == 1) +#error MDBX_USE_FALLOCATE must be defined as 0 or 1 +#else +#define MDBX_USE_FALLOCATE_CONFIG MDBX_STRINGIFY(MDBX_USE_FALLOCATE) +#endif /* MDBX_USE_FALLOCATE */ + //------------------------------------------------------------------------------ #ifndef MDBX_CPU_WRITEBACK_INCOHERENT diff --git a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx_drop.c b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx_drop.c index 319bcc13744..a0b0b7104a8 100644 --- a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx_drop.c +++ b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx_drop.c @@ -1,7 +1,7 @@ /// \copyright SPDX-License-Identifier: Apache-2.0 /// \note Please refer to the COPYRIGHT file for explanations license change, /// credits and acknowledgments. -/// \author Леонид Юрьев aka Leonid Yuriev \date 2021-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2021-2026 /// /// mdbx_drop.c - memory-mapped database delete tool /// @@ -16,9 +16,9 @@ #define xMDBX_TOOLS /* Avoid using internal eASSERT() */ /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 -#define MDBX_BUILD_SOURCERY 6b5df6869d2bf5419e3a8189d9cc849cc9911b9c8a951b9750ed0a261ce43724_v0_13_7_0_g566b0f93 +#define MDBX_BUILD_SOURCERY a575a490fc080ca11e89ff6db9f0bd38aa830959905998cac0e45274b9e6bb0e_v0_13_12_0_gf619d43d #define LIBMDBX_INTERNALS #define MDBX_DEPRECATED @@ -1187,6 +1187,14 @@ typedef char pathchar_t; #define MDBX_PRIsPATH "s" #endif +MDBX_MAYBE_UNUSED static inline bool osal_yield(void) { +#if defined(_WIN32) || defined(_WIN64) + return SleepEx(0, true) == WAIT_IO_COMPLETION; +#else + return sched_yield() != 0; +#endif +} + typedef struct osal_mmap { union { void *base; @@ -1206,6 +1214,8 @@ typedef struct osal_mmap { #define MDBX_HAVE_PWRITEV 0 +MDBX_INTERNAL int osal_waitstatus2errcode(DWORD result); + #elif defined(__ANDROID_API__) #if __ANDROID_API__ < 24 @@ -1449,7 +1459,7 @@ enum osal_syncmode_bits { }; MDBX_INTERNAL int osal_fsync(mdbx_filehandle_t fd, const enum osal_syncmode_bits mode_bits); -MDBX_INTERNAL int osal_ftruncate(mdbx_filehandle_t fd, uint64_t length); +MDBX_INTERNAL int osal_fsetsize(mdbx_filehandle_t fd, const uint64_t length); MDBX_INTERNAL int osal_fseek(mdbx_filehandle_t fd, uint64_t pos); MDBX_INTERNAL int osal_filesize(mdbx_filehandle_t fd, uint64_t *length); @@ -1485,11 +1495,11 @@ MDBX_INTERNAL int osal_removedirectory(const pathchar_t *pathname); MDBX_INTERNAL int osal_is_pipe(mdbx_filehandle_t fd); MDBX_INTERNAL int osal_lockfile(mdbx_filehandle_t fd, bool wait); -#define MMAP_OPTION_TRUNCATE 1 +#define MMAP_OPTION_SETLENGTH 1 #define MMAP_OPTION_SEMAPHORE 2 MDBX_INTERNAL int osal_mmap(const int flags, osal_mmap_t *map, size_t size, const size_t limit, const unsigned options, const pathchar_t *pathname4logging); -MDBX_INTERNAL int osal_munmap(osal_mmap_t *map); +MDBX_INTERNAL void osal_munmap(osal_mmap_t *map); #define MDBX_MRESIZE_MAY_MOVE 0x00000100 #define MDBX_MRESIZE_MAY_UNMAP 0x00000200 MDBX_INTERNAL int osal_mresize(const int flags, osal_mmap_t *map, size_t size, size_t limit); @@ -1892,7 +1902,8 @@ MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline uint32_t osal_bswap32 ((defined(_POSIX_THREAD_ROBUST_PRIO_INHERIT) && _POSIX_THREAD_ROBUST_PRIO_INHERIT > 0) || \ (defined(_POSIX_THREAD_ROBUST_PRIO_PROTECT) && _POSIX_THREAD_ROBUST_PRIO_PROTECT > 0) || \ defined(PTHREAD_MUTEX_ROBUST) || defined(PTHREAD_MUTEX_ROBUST_NP)) && \ - (!defined(__GLIBC__) || __GLIBC_PREREQ(2, 10) /* troubles with Robust mutexes before 2.10 */) + (!defined(__GLIBC__) || __GLIBC_PREREQ(2, 10) /* troubles with Robust mutexes before 2.10 */) && \ + !defined(__OHOS__) /* Harmony OS doesn't support robust mutexes at the end of 2025 */ #define MDBX_LOCKING MDBX_LOCKING_POSIX2008 #else #define MDBX_LOCKING MDBX_LOCKING_POSIX2001 @@ -1947,6 +1958,22 @@ MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline uint32_t osal_bswap32 #error MDBX_USE_COPYFILERANGE must be defined as 0 or 1 #endif /* MDBX_USE_COPYFILERANGE */ +/** Advanced: Using posix_fallocate() or fcntl(F_PREALLOCATE) on OSX (autodetection by default). */ +#ifndef MDBX_USE_FALLOCATE +#if defined(__APPLE__) +#define MDBX_USE_FALLOCATE 0 /* Too slow and unclean, but not required to prevent SIGBUS */ +#elif (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L) || (__GLIBC_PREREQ(2, 10) && defined(_GNU_SOURCE)) +#define MDBX_USE_FALLOCATE 1 +#else +#define MDBX_USE_FALLOCATE 0 +#endif +#define MDBX_USE_FALLOCATE_CONFIG "AUTO=" MDBX_STRINGIFY(MDBX_USE_FALLOCATE) +#elif !(MDBX_USE_FALLOCATE == 0 || MDBX_USE_FALLOCATE == 1) +#error MDBX_USE_FALLOCATE must be defined as 0 or 1 +#else +#define MDBX_USE_FALLOCATE_CONFIG MDBX_STRINGIFY(MDBX_USE_FALLOCATE) +#endif /* MDBX_USE_FALLOCATE */ + //------------------------------------------------------------------------------ #ifndef MDBX_CPU_WRITEBACK_INCOHERENT diff --git a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx_dump.c b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx_dump.c index 3193b1d34ce..784b9afd6e8 100644 --- a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx_dump.c +++ b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx_dump.c @@ -1,7 +1,7 @@ /// \copyright SPDX-License-Identifier: Apache-2.0 /// \note Please refer to the COPYRIGHT file for explanations license change, /// credits and acknowledgments. -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 /// /// mdbx_dump.c - memory-mapped database dump tool /// @@ -16,9 +16,9 @@ #define xMDBX_TOOLS /* Avoid using internal eASSERT() */ /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 -#define MDBX_BUILD_SOURCERY 6b5df6869d2bf5419e3a8189d9cc849cc9911b9c8a951b9750ed0a261ce43724_v0_13_7_0_g566b0f93 +#define MDBX_BUILD_SOURCERY a575a490fc080ca11e89ff6db9f0bd38aa830959905998cac0e45274b9e6bb0e_v0_13_12_0_gf619d43d #define LIBMDBX_INTERNALS #define MDBX_DEPRECATED @@ -1187,6 +1187,14 @@ typedef char pathchar_t; #define MDBX_PRIsPATH "s" #endif +MDBX_MAYBE_UNUSED static inline bool osal_yield(void) { +#if defined(_WIN32) || defined(_WIN64) + return SleepEx(0, true) == WAIT_IO_COMPLETION; +#else + return sched_yield() != 0; +#endif +} + typedef struct osal_mmap { union { void *base; @@ -1206,6 +1214,8 @@ typedef struct osal_mmap { #define MDBX_HAVE_PWRITEV 0 +MDBX_INTERNAL int osal_waitstatus2errcode(DWORD result); + #elif defined(__ANDROID_API__) #if __ANDROID_API__ < 24 @@ -1449,7 +1459,7 @@ enum osal_syncmode_bits { }; MDBX_INTERNAL int osal_fsync(mdbx_filehandle_t fd, const enum osal_syncmode_bits mode_bits); -MDBX_INTERNAL int osal_ftruncate(mdbx_filehandle_t fd, uint64_t length); +MDBX_INTERNAL int osal_fsetsize(mdbx_filehandle_t fd, const uint64_t length); MDBX_INTERNAL int osal_fseek(mdbx_filehandle_t fd, uint64_t pos); MDBX_INTERNAL int osal_filesize(mdbx_filehandle_t fd, uint64_t *length); @@ -1485,11 +1495,11 @@ MDBX_INTERNAL int osal_removedirectory(const pathchar_t *pathname); MDBX_INTERNAL int osal_is_pipe(mdbx_filehandle_t fd); MDBX_INTERNAL int osal_lockfile(mdbx_filehandle_t fd, bool wait); -#define MMAP_OPTION_TRUNCATE 1 +#define MMAP_OPTION_SETLENGTH 1 #define MMAP_OPTION_SEMAPHORE 2 MDBX_INTERNAL int osal_mmap(const int flags, osal_mmap_t *map, size_t size, const size_t limit, const unsigned options, const pathchar_t *pathname4logging); -MDBX_INTERNAL int osal_munmap(osal_mmap_t *map); +MDBX_INTERNAL void osal_munmap(osal_mmap_t *map); #define MDBX_MRESIZE_MAY_MOVE 0x00000100 #define MDBX_MRESIZE_MAY_UNMAP 0x00000200 MDBX_INTERNAL int osal_mresize(const int flags, osal_mmap_t *map, size_t size, size_t limit); @@ -1892,7 +1902,8 @@ MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline uint32_t osal_bswap32 ((defined(_POSIX_THREAD_ROBUST_PRIO_INHERIT) && _POSIX_THREAD_ROBUST_PRIO_INHERIT > 0) || \ (defined(_POSIX_THREAD_ROBUST_PRIO_PROTECT) && _POSIX_THREAD_ROBUST_PRIO_PROTECT > 0) || \ defined(PTHREAD_MUTEX_ROBUST) || defined(PTHREAD_MUTEX_ROBUST_NP)) && \ - (!defined(__GLIBC__) || __GLIBC_PREREQ(2, 10) /* troubles with Robust mutexes before 2.10 */) + (!defined(__GLIBC__) || __GLIBC_PREREQ(2, 10) /* troubles with Robust mutexes before 2.10 */) && \ + !defined(__OHOS__) /* Harmony OS doesn't support robust mutexes at the end of 2025 */ #define MDBX_LOCKING MDBX_LOCKING_POSIX2008 #else #define MDBX_LOCKING MDBX_LOCKING_POSIX2001 @@ -1947,6 +1958,22 @@ MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline uint32_t osal_bswap32 #error MDBX_USE_COPYFILERANGE must be defined as 0 or 1 #endif /* MDBX_USE_COPYFILERANGE */ +/** Advanced: Using posix_fallocate() or fcntl(F_PREALLOCATE) on OSX (autodetection by default). */ +#ifndef MDBX_USE_FALLOCATE +#if defined(__APPLE__) +#define MDBX_USE_FALLOCATE 0 /* Too slow and unclean, but not required to prevent SIGBUS */ +#elif (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L) || (__GLIBC_PREREQ(2, 10) && defined(_GNU_SOURCE)) +#define MDBX_USE_FALLOCATE 1 +#else +#define MDBX_USE_FALLOCATE 0 +#endif +#define MDBX_USE_FALLOCATE_CONFIG "AUTO=" MDBX_STRINGIFY(MDBX_USE_FALLOCATE) +#elif !(MDBX_USE_FALLOCATE == 0 || MDBX_USE_FALLOCATE == 1) +#error MDBX_USE_FALLOCATE must be defined as 0 or 1 +#else +#define MDBX_USE_FALLOCATE_CONFIG MDBX_STRINGIFY(MDBX_USE_FALLOCATE) +#endif /* MDBX_USE_FALLOCATE */ + //------------------------------------------------------------------------------ #ifndef MDBX_CPU_WRITEBACK_INCOHERENT @@ -3422,10 +3449,10 @@ static int dump_tbl(MDBX_txn *txn, MDBX_dbi dbi, char *name) { if (mode & GLOBAL) { mode -= GLOBAL; if (info.mi_geo.upper != info.mi_geo.lower) - printf("geometry=l%" PRIu64 ",c%" PRIu64 ",u%" PRIu64 ",s%" PRIu64 ",g%" PRIu64 "\n", info.mi_geo.lower, - info.mi_geo.current, info.mi_geo.upper, info.mi_geo.shrink, info.mi_geo.grow); + printf("geometry=l%" PRIu64 ",u%" PRIu64 ",s%" PRIu64 ",g%" PRIu64 "\n", info.mi_geo.lower, info.mi_geo.upper, + info.mi_geo.shrink, info.mi_geo.grow); printf("mapsize=%" PRIu64 "\n", info.mi_geo.upper); - printf("maxreaders=%u\n", info.mi_maxreaders); + /* printf("maxreaders=%u\n", info.mi_maxreaders); */ MDBX_canary canary; rc = mdbx_canary_get(txn, &canary); diff --git a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx_load.c b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx_load.c index 0c1ceba53c2..4f4e3bb5629 100644 --- a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx_load.c +++ b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx_load.c @@ -1,7 +1,7 @@ /// \copyright SPDX-License-Identifier: Apache-2.0 /// \note Please refer to the COPYRIGHT file for explanations license change, /// credits and acknowledgments. -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 /// /// mdbx_load.c - memory-mapped database load tool /// @@ -16,9 +16,9 @@ #define xMDBX_TOOLS /* Avoid using internal eASSERT() */ /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 -#define MDBX_BUILD_SOURCERY 6b5df6869d2bf5419e3a8189d9cc849cc9911b9c8a951b9750ed0a261ce43724_v0_13_7_0_g566b0f93 +#define MDBX_BUILD_SOURCERY a575a490fc080ca11e89ff6db9f0bd38aa830959905998cac0e45274b9e6bb0e_v0_13_12_0_gf619d43d #define LIBMDBX_INTERNALS #define MDBX_DEPRECATED @@ -1187,6 +1187,14 @@ typedef char pathchar_t; #define MDBX_PRIsPATH "s" #endif +MDBX_MAYBE_UNUSED static inline bool osal_yield(void) { +#if defined(_WIN32) || defined(_WIN64) + return SleepEx(0, true) == WAIT_IO_COMPLETION; +#else + return sched_yield() != 0; +#endif +} + typedef struct osal_mmap { union { void *base; @@ -1206,6 +1214,8 @@ typedef struct osal_mmap { #define MDBX_HAVE_PWRITEV 0 +MDBX_INTERNAL int osal_waitstatus2errcode(DWORD result); + #elif defined(__ANDROID_API__) #if __ANDROID_API__ < 24 @@ -1449,7 +1459,7 @@ enum osal_syncmode_bits { }; MDBX_INTERNAL int osal_fsync(mdbx_filehandle_t fd, const enum osal_syncmode_bits mode_bits); -MDBX_INTERNAL int osal_ftruncate(mdbx_filehandle_t fd, uint64_t length); +MDBX_INTERNAL int osal_fsetsize(mdbx_filehandle_t fd, const uint64_t length); MDBX_INTERNAL int osal_fseek(mdbx_filehandle_t fd, uint64_t pos); MDBX_INTERNAL int osal_filesize(mdbx_filehandle_t fd, uint64_t *length); @@ -1485,11 +1495,11 @@ MDBX_INTERNAL int osal_removedirectory(const pathchar_t *pathname); MDBX_INTERNAL int osal_is_pipe(mdbx_filehandle_t fd); MDBX_INTERNAL int osal_lockfile(mdbx_filehandle_t fd, bool wait); -#define MMAP_OPTION_TRUNCATE 1 +#define MMAP_OPTION_SETLENGTH 1 #define MMAP_OPTION_SEMAPHORE 2 MDBX_INTERNAL int osal_mmap(const int flags, osal_mmap_t *map, size_t size, const size_t limit, const unsigned options, const pathchar_t *pathname4logging); -MDBX_INTERNAL int osal_munmap(osal_mmap_t *map); +MDBX_INTERNAL void osal_munmap(osal_mmap_t *map); #define MDBX_MRESIZE_MAY_MOVE 0x00000100 #define MDBX_MRESIZE_MAY_UNMAP 0x00000200 MDBX_INTERNAL int osal_mresize(const int flags, osal_mmap_t *map, size_t size, size_t limit); @@ -1892,7 +1902,8 @@ MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline uint32_t osal_bswap32 ((defined(_POSIX_THREAD_ROBUST_PRIO_INHERIT) && _POSIX_THREAD_ROBUST_PRIO_INHERIT > 0) || \ (defined(_POSIX_THREAD_ROBUST_PRIO_PROTECT) && _POSIX_THREAD_ROBUST_PRIO_PROTECT > 0) || \ defined(PTHREAD_MUTEX_ROBUST) || defined(PTHREAD_MUTEX_ROBUST_NP)) && \ - (!defined(__GLIBC__) || __GLIBC_PREREQ(2, 10) /* troubles with Robust mutexes before 2.10 */) + (!defined(__GLIBC__) || __GLIBC_PREREQ(2, 10) /* troubles with Robust mutexes before 2.10 */) && \ + !defined(__OHOS__) /* Harmony OS doesn't support robust mutexes at the end of 2025 */ #define MDBX_LOCKING MDBX_LOCKING_POSIX2008 #else #define MDBX_LOCKING MDBX_LOCKING_POSIX2001 @@ -1947,6 +1958,22 @@ MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline uint32_t osal_bswap32 #error MDBX_USE_COPYFILERANGE must be defined as 0 or 1 #endif /* MDBX_USE_COPYFILERANGE */ +/** Advanced: Using posix_fallocate() or fcntl(F_PREALLOCATE) on OSX (autodetection by default). */ +#ifndef MDBX_USE_FALLOCATE +#if defined(__APPLE__) +#define MDBX_USE_FALLOCATE 0 /* Too slow and unclean, but not required to prevent SIGBUS */ +#elif (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L) || (__GLIBC_PREREQ(2, 10) && defined(_GNU_SOURCE)) +#define MDBX_USE_FALLOCATE 1 +#else +#define MDBX_USE_FALLOCATE 0 +#endif +#define MDBX_USE_FALLOCATE_CONFIG "AUTO=" MDBX_STRINGIFY(MDBX_USE_FALLOCATE) +#elif !(MDBX_USE_FALLOCATE == 0 || MDBX_USE_FALLOCATE == 1) +#error MDBX_USE_FALLOCATE must be defined as 0 or 1 +#else +#define MDBX_USE_FALLOCATE_CONFIG MDBX_STRINGIFY(MDBX_USE_FALLOCATE) +#endif /* MDBX_USE_FALLOCATE */ + //------------------------------------------------------------------------------ #ifndef MDBX_CPU_WRITEBACK_INCOHERENT @@ -3584,6 +3611,9 @@ static int readhdr(void) { "%s: line %" PRIiSIZE ": ignore values %s" " for '%s' in non-global context\n", prog, lineno, str, "geometry"); + } else if (sscanf(str, "l%" PRIu64 ",u%" PRIu64 ",s%" PRIu64 ",g%" PRIu64, &envinfo.mi_geo.lower, + &envinfo.mi_geo.upper, &envinfo.mi_geo.shrink, &envinfo.mi_geo.grow) == 4) { + envinfo.mi_geo.current = (uint64_t)INT64_C(-1); } else if (sscanf(str, "l%" PRIu64 ",c%" PRIu64 ",u%" PRIu64 ",s%" PRIu64 ",g%" PRIu64, &envinfo.mi_geo.lower, &envinfo.mi_geo.current, &envinfo.mi_geo.upper, &envinfo.mi_geo.shrink, &envinfo.mi_geo.grow) != 5) { @@ -3902,29 +3932,27 @@ int main(int argc, char *argv[]) { } } - if (envinfo.mi_geo.current | envinfo.mi_mapsize) { - if (envinfo.mi_geo.current) { - err = mdbx_env_set_geometry(env, (intptr_t)envinfo.mi_geo.lower, (intptr_t)envinfo.mi_geo.current, - (intptr_t)envinfo.mi_geo.upper, (intptr_t)envinfo.mi_geo.shrink, - (intptr_t)envinfo.mi_geo.grow, - envinfo.mi_dxb_pagesize ? (intptr_t)envinfo.mi_dxb_pagesize : -1); - } else { - if (envinfo.mi_mapsize > MAX_MAPSIZE) { - if (!quiet) - fprintf(stderr, - "Database size is too large for current system (mapsize=%" PRIu64 - " is great than system-limit %zu)\n", - envinfo.mi_mapsize, (size_t)MAX_MAPSIZE); - goto bailout; - } - err = mdbx_env_set_geometry(env, (intptr_t)envinfo.mi_mapsize, (intptr_t)envinfo.mi_mapsize, - (intptr_t)envinfo.mi_mapsize, 0, 0, - envinfo.mi_dxb_pagesize ? (intptr_t)envinfo.mi_dxb_pagesize : -1); - } - if (unlikely(err != MDBX_SUCCESS)) { - error("mdbx_env_set_geometry", err); + err = MDBX_SUCCESS; + if (envinfo.mi_geo.lower | envinfo.mi_geo.upper | envinfo.mi_geo.shrink | envinfo.mi_geo.grow) { + err = mdbx_env_set_geometry(env, (intptr_t)envinfo.mi_geo.lower, (intptr_t)envinfo.mi_geo.current, + (intptr_t)envinfo.mi_geo.upper, (intptr_t)envinfo.mi_geo.grow, + (intptr_t)envinfo.mi_geo.shrink, + envinfo.mi_dxb_pagesize ? (intptr_t)envinfo.mi_dxb_pagesize : -1); + } else if (envinfo.mi_mapsize) { + if (envinfo.mi_mapsize > MAX_MAPSIZE) { + if (!quiet) + fprintf(stderr, + "Database size is too large for current system (mapsize=%" PRIu64 " is great than system-limit %zu)\n", + envinfo.mi_mapsize, (size_t)MAX_MAPSIZE); goto bailout; } + err = mdbx_env_set_geometry(env, (intptr_t)envinfo.mi_mapsize, (intptr_t)envinfo.mi_mapsize, + (intptr_t)envinfo.mi_mapsize, 0, 0, + envinfo.mi_dxb_pagesize ? (intptr_t)envinfo.mi_dxb_pagesize : -1); + } + if (unlikely(err != MDBX_SUCCESS)) { + error("mdbx_env_set_geometry", err); + goto bailout; } err = mdbx_env_open(env, envname, envflags, 0664); diff --git a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx_stat.c b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx_stat.c index bd052d70a3c..4a0e9b3721d 100644 --- a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx_stat.c +++ b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx_stat.c @@ -1,7 +1,7 @@ /// \copyright SPDX-License-Identifier: Apache-2.0 /// \note Please refer to the COPYRIGHT file for explanations license change, /// credits and acknowledgments. -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 /// /// mdbx_stat.c - memory-mapped database status tool /// @@ -16,9 +16,9 @@ #define xMDBX_TOOLS /* Avoid using internal eASSERT() */ /// \copyright SPDX-License-Identifier: Apache-2.0 -/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2025 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2015-2026 -#define MDBX_BUILD_SOURCERY 6b5df6869d2bf5419e3a8189d9cc849cc9911b9c8a951b9750ed0a261ce43724_v0_13_7_0_g566b0f93 +#define MDBX_BUILD_SOURCERY a575a490fc080ca11e89ff6db9f0bd38aa830959905998cac0e45274b9e6bb0e_v0_13_12_0_gf619d43d #define LIBMDBX_INTERNALS #define MDBX_DEPRECATED @@ -1187,6 +1187,14 @@ typedef char pathchar_t; #define MDBX_PRIsPATH "s" #endif +MDBX_MAYBE_UNUSED static inline bool osal_yield(void) { +#if defined(_WIN32) || defined(_WIN64) + return SleepEx(0, true) == WAIT_IO_COMPLETION; +#else + return sched_yield() != 0; +#endif +} + typedef struct osal_mmap { union { void *base; @@ -1206,6 +1214,8 @@ typedef struct osal_mmap { #define MDBX_HAVE_PWRITEV 0 +MDBX_INTERNAL int osal_waitstatus2errcode(DWORD result); + #elif defined(__ANDROID_API__) #if __ANDROID_API__ < 24 @@ -1449,7 +1459,7 @@ enum osal_syncmode_bits { }; MDBX_INTERNAL int osal_fsync(mdbx_filehandle_t fd, const enum osal_syncmode_bits mode_bits); -MDBX_INTERNAL int osal_ftruncate(mdbx_filehandle_t fd, uint64_t length); +MDBX_INTERNAL int osal_fsetsize(mdbx_filehandle_t fd, const uint64_t length); MDBX_INTERNAL int osal_fseek(mdbx_filehandle_t fd, uint64_t pos); MDBX_INTERNAL int osal_filesize(mdbx_filehandle_t fd, uint64_t *length); @@ -1485,11 +1495,11 @@ MDBX_INTERNAL int osal_removedirectory(const pathchar_t *pathname); MDBX_INTERNAL int osal_is_pipe(mdbx_filehandle_t fd); MDBX_INTERNAL int osal_lockfile(mdbx_filehandle_t fd, bool wait); -#define MMAP_OPTION_TRUNCATE 1 +#define MMAP_OPTION_SETLENGTH 1 #define MMAP_OPTION_SEMAPHORE 2 MDBX_INTERNAL int osal_mmap(const int flags, osal_mmap_t *map, size_t size, const size_t limit, const unsigned options, const pathchar_t *pathname4logging); -MDBX_INTERNAL int osal_munmap(osal_mmap_t *map); +MDBX_INTERNAL void osal_munmap(osal_mmap_t *map); #define MDBX_MRESIZE_MAY_MOVE 0x00000100 #define MDBX_MRESIZE_MAY_UNMAP 0x00000200 MDBX_INTERNAL int osal_mresize(const int flags, osal_mmap_t *map, size_t size, size_t limit); @@ -1892,7 +1902,8 @@ MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline uint32_t osal_bswap32 ((defined(_POSIX_THREAD_ROBUST_PRIO_INHERIT) && _POSIX_THREAD_ROBUST_PRIO_INHERIT > 0) || \ (defined(_POSIX_THREAD_ROBUST_PRIO_PROTECT) && _POSIX_THREAD_ROBUST_PRIO_PROTECT > 0) || \ defined(PTHREAD_MUTEX_ROBUST) || defined(PTHREAD_MUTEX_ROBUST_NP)) && \ - (!defined(__GLIBC__) || __GLIBC_PREREQ(2, 10) /* troubles with Robust mutexes before 2.10 */) + (!defined(__GLIBC__) || __GLIBC_PREREQ(2, 10) /* troubles with Robust mutexes before 2.10 */) && \ + !defined(__OHOS__) /* Harmony OS doesn't support robust mutexes at the end of 2025 */ #define MDBX_LOCKING MDBX_LOCKING_POSIX2008 #else #define MDBX_LOCKING MDBX_LOCKING_POSIX2001 @@ -1947,6 +1958,22 @@ MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline uint32_t osal_bswap32 #error MDBX_USE_COPYFILERANGE must be defined as 0 or 1 #endif /* MDBX_USE_COPYFILERANGE */ +/** Advanced: Using posix_fallocate() or fcntl(F_PREALLOCATE) on OSX (autodetection by default). */ +#ifndef MDBX_USE_FALLOCATE +#if defined(__APPLE__) +#define MDBX_USE_FALLOCATE 0 /* Too slow and unclean, but not required to prevent SIGBUS */ +#elif (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L) || (__GLIBC_PREREQ(2, 10) && defined(_GNU_SOURCE)) +#define MDBX_USE_FALLOCATE 1 +#else +#define MDBX_USE_FALLOCATE 0 +#endif +#define MDBX_USE_FALLOCATE_CONFIG "AUTO=" MDBX_STRINGIFY(MDBX_USE_FALLOCATE) +#elif !(MDBX_USE_FALLOCATE == 0 || MDBX_USE_FALLOCATE == 1) +#error MDBX_USE_FALLOCATE must be defined as 0 or 1 +#else +#define MDBX_USE_FALLOCATE_CONFIG MDBX_STRINGIFY(MDBX_USE_FALLOCATE) +#endif /* MDBX_USE_FALLOCATE */ + //------------------------------------------------------------------------------ #ifndef MDBX_CPU_WRITEBACK_INCOHERENT @@ -3489,7 +3516,6 @@ int main(int argc, char *argv[]) { signal(SIGTERM, signal_handler); #endif /* !WINDOWS */ - envname = argv[optind]; envname = argv[optind]; if (!quiet) { printf("mdbx_stat %s (%s, T-%s)\nRunning for %s...\n", mdbx_version.git.describe, mdbx_version.git.datetime, diff --git a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/ut_and_examples/CMakeLists.txt b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/ut_and_examples/CMakeLists.txt new file mode 100644 index 00000000000..6a9ee80de1b --- /dev/null +++ b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/ut_and_examples/CMakeLists.txt @@ -0,0 +1,39 @@ +# Copyright (c) 2020-2026 Леонид Юрьев aka Leonid Yuriev ############################################### +# SPDX-License-Identifier: Apache-2.0 +# +project(mdbx_ut_and_examples) + +if(NOT DEFINED MDBX_LIBRARY) + set(MDBX_LIBRARY mdbx) +endif() + +if(MDBX_BUILD_CXX AND CMAKE_CXX_COMPILER_LOADED AND NOT MDBX_CXX_STANDARD LESS 17) + add_executable(mdbx_modern_example example-mdbx.c++) + set_target_properties(mdbx_modern_example PROPERTIES CXX_STANDARD ${MDBX_CXX_STANDARD} CXX_STANDARD_REQUIRED ON) + target_link_libraries(mdbx_modern_example ${MDBX_LIBRARY}) + if(DEFINED INTERPROCEDURAL_OPTIMIZATION) + set_target_properties(mdbx_modern_example PROPERTIES INTERPROCEDURAL_OPTIMIZATION $) + endif() +else() + message(NOTICE "The C++ example will be skipped, since C++17 standard is unavailable or C++ API of libmdbx was disabled.") + set(MDBX_BUILD_CXX FALSE) +endif() + +add_executable(mdbx_legacy_example example-mdbx.c) +target_link_libraries(mdbx_legacy_example ${MDBX_LIBRARY}) + +if(CMAKE_CROSSCOMPILING AND NOT CMAKE_CROSSCOMPILING_EMULATOR) + message(NOTICE "No emulator to run cross-compiled tests") + add_test(NAME fake_since_no_crosscompiling_emulator COMMAND ${CMAKE_COMMAND} -E echo + "No emulator to run cross-compiled tests") +else() + add_test(NAME c_api COMMAND mdbx_legacy_example) + if(MDBX_BUILD_CXX) + add_test(NAME c++_api COMMAND mdbx_modern_example) + endif() +endif() + +if(UNIX) + add_executable(pcrf_simulator pcrf/pcrf_simulator.c) + target_link_libraries(pcrf_simulator ${MDBX_LIBRARY}) +endif() diff --git a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/ut_and_examples/README.md b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/ut_and_examples/README.md new file mode 100644 index 00000000000..cdfff30d932 --- /dev/null +++ b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/ut_and_examples/README.md @@ -0,0 +1,11 @@ +## Examples & Unit tests + +This set of examples will be updated, and most of the examples are also publicly available tests. + +### C API +The [`example-mdbx.c`](example-mdbx.c) is the example of using the C API. +However, it is strongly recommended to use the modern [C++ API](https://libmdbx.dqdkfa.ru/group__cxx__api.html), which requires less effort +and insures against many errors related to resource leaks. + +### Berkeley DB +If you have already used Berkeley DB, then it will be useful to make a line-by-line comparison of [`example-mdbx.c`](example-mdbx.c) and the [`sample-bdb.txt`](sample-bdb.txt) file. diff --git a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/ut_and_examples/example-mdbx.c b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/ut_and_examples/example-mdbx.c new file mode 100644 index 00000000000..c9b19b77f04 --- /dev/null +++ b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/ut_and_examples/example-mdbx.c @@ -0,0 +1,167 @@ +/** \file The example of using the libmdbx C API. + * However, it is strongly recommended to use the modern C++ API, which requires less effort + * and insures against many errors related to resource leaks. + * + * \note If you have already used Berkeley DB, + * it will be useful to make a line-by-line comparison of this example and the sample-bdb.txt + */ + +/* + * Copyright 2015-2026 Leonid Yuriev . + * Copyright 2017 Ilya Shipitsin . + * Copyright 2012-2015 Howard Chu, Symas Corp. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#if (defined(__MINGW__) || defined(__MINGW32__) || defined(__MINGW64__)) && !defined(__USE_MINGW_ANSI_STDIO) +#define __USE_MINGW_ANSI_STDIO 1 +#endif /* MinGW */ + +#include "mdbx.h" + +#include +#include +#include +#include + +int main(int argc, char *argv[]) { + (void)argc; + (void)argv; + + int rc; + MDBX_env *env = NULL; + MDBX_dbi dbi = 0; + MDBX_val key, data; + MDBX_txn *txn = NULL; + MDBX_cursor *cursor = NULL; + char sval[32]; + + printf("MDBX limits:\n"); +#if UINTPTR_MAX > 0xffffFFFFul || ULONG_MAX > 0xffffFFFFul + const double scale_factor = 1099511627776.0; + const char *const scale_unit = "TiB"; +#else + const double scale_factor = 1073741824.0; + const char *const scale_unit = "GiB"; +#endif + const size_t pagesize_min = mdbx_limits_pgsize_min(); + const size_t pagesize_max = mdbx_limits_pgsize_max(); + const size_t pagesize_default = mdbx_default_pagesize(); + + printf("\tPage size: a power of 2, minimum %zu, maximum %zu bytes," + " default %zu bytes.\n", + pagesize_min, pagesize_max, pagesize_default); + printf("\tKey size: minimum %zu, maximum ≈¼ pagesize (%zu bytes for default" + " %zuK pagesize, %zu bytes for %zuK pagesize).\n", + (size_t)0, mdbx_limits_keysize_max(-1, MDBX_DB_DEFAULTS), pagesize_default / 1024, + mdbx_limits_keysize_max(pagesize_max, MDBX_DB_DEFAULTS), pagesize_max / 1024); + printf("\tValue size: minimum %zu, maximum %zu (0x%08zX) bytes for maps," + " ≈¼ pagesize for multimaps (%zu bytes for default %zuK pagesize," + " %zu bytes for %zuK pagesize).\n", + (size_t)0, mdbx_limits_valsize_max(pagesize_min, MDBX_DB_DEFAULTS), + mdbx_limits_valsize_max(pagesize_min, MDBX_DB_DEFAULTS), mdbx_limits_valsize_max(-1, MDBX_DUPSORT), + pagesize_default / 1024, mdbx_limits_valsize_max(pagesize_max, MDBX_DUPSORT), pagesize_max / 1024); + printf("\tWrite transaction size: up to %zu (0x%zX) pages (%f %s for default " + "%zuK pagesize, %f %s for %zuK pagesize).\n", + mdbx_limits_txnsize_max(pagesize_min) / pagesize_min, mdbx_limits_txnsize_max(pagesize_min) / pagesize_min, + mdbx_limits_txnsize_max(-1) / scale_factor, scale_unit, pagesize_default / 1024, + mdbx_limits_txnsize_max(pagesize_max) / scale_factor, scale_unit, pagesize_max / 1024); + printf("\tDatabase size: up to %zu pages (%f %s for default %zuK " + "pagesize, %f %s for %zuK pagesize).\n", + mdbx_limits_dbsize_max(pagesize_min) / pagesize_min, mdbx_limits_dbsize_max(-1) / scale_factor, scale_unit, + pagesize_default / 1024, mdbx_limits_dbsize_max(pagesize_max) / scale_factor, scale_unit, pagesize_max / 1024); + printf("\tMaximum sub-databases: %u.\n", MDBX_MAX_DBI); + printf("-----\n"); + + rc = mdbx_env_create(&env); + if (rc != MDBX_SUCCESS) { + fprintf(stderr, "mdbx_env_create: (%d) %s\n", rc, mdbx_strerror(rc)); + goto bailout; + } + + /* This deletion is not necessary, but it has been added here for full reproducibility of the conditions when running + * this example again as a simplest test. */ + rc = mdbx_env_delete("./example-db", MDBX_ENV_JUST_DELETE); + if (rc != /* file was successfully deleted */ MDBX_SUCCESS && + rc != /* nothing has been done because the file is missing */ MDBX_RESULT_TRUE) { + fprintf(stderr, "mdbx_env_delete: (%d) %s\n", rc, mdbx_strerror(rc)); + goto bailout; + } + + rc = mdbx_env_open(env, "./example-db", MDBX_NOSUBDIR | MDBX_LIFORECLAIM, 0664); + if (rc != MDBX_SUCCESS) { + fprintf(stderr, "mdbx_env_open: (%d) %s\n", rc, mdbx_strerror(rc)); + goto bailout; + } + + rc = mdbx_txn_begin(env, NULL, 0, &txn); + if (rc != MDBX_SUCCESS) { + fprintf(stderr, "mdbx_txn_begin: (%d) %s\n", rc, mdbx_strerror(rc)); + goto bailout; + } + rc = mdbx_dbi_open(txn, NULL, 0, &dbi); + if (rc != MDBX_SUCCESS) { + fprintf(stderr, "mdbx_dbi_open: (%d) %s\n", rc, mdbx_strerror(rc)); + goto bailout; + } + + key.iov_len = sizeof(int); + key.iov_base = sval; + data.iov_len = sizeof(sval); + data.iov_base = sval; + + snprintf(sval, sizeof(sval), "%03x %d foo bar", 32, 3141592); + rc = mdbx_put(txn, dbi, &key, &data, 0); + if (rc != MDBX_SUCCESS) { + fprintf(stderr, "mdbx_put: (%d) %s\n", rc, mdbx_strerror(rc)); + goto bailout; + } + rc = mdbx_txn_commit(txn); + if (rc) { + fprintf(stderr, "mdbx_txn_commit: (%d) %s\n", rc, mdbx_strerror(rc)); + goto bailout; + } + txn = NULL; + + rc = mdbx_txn_begin(env, NULL, MDBX_TXN_RDONLY, &txn); + if (rc != MDBX_SUCCESS) { + fprintf(stderr, "mdbx_txn_begin: (%d) %s\n", rc, mdbx_strerror(rc)); + goto bailout; + } + rc = mdbx_cursor_open(txn, dbi, &cursor); + if (rc != MDBX_SUCCESS) { + fprintf(stderr, "mdbx_cursor_open: (%d) %s\n", rc, mdbx_strerror(rc)); + goto bailout; + } + + int found = 0; + while ((rc = mdbx_cursor_get(cursor, &key, &data, MDBX_NEXT)) == 0) { + printf("key: %p %.*s, data: %p %.*s\n", key.iov_base, (int)key.iov_len, (char *)key.iov_base, data.iov_base, + (int)data.iov_len, (char *)data.iov_base); + found += 1; + } + if (rc != MDBX_NOTFOUND || found == 0) { + fprintf(stderr, "mdbx_cursor_get: (%d) %s\n", rc, mdbx_strerror(rc)); + goto bailout; + } else { + rc = MDBX_SUCCESS; + } +bailout: + if (cursor) + mdbx_cursor_close(cursor); + if (txn) + mdbx_txn_abort(txn); + if (dbi) + mdbx_dbi_close(env, dbi); + if (env) + mdbx_env_close(env); + return (rc != MDBX_SUCCESS) ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/ut_and_examples/example-mdbx.c++ b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/ut_and_examples/example-mdbx.c++ new file mode 100644 index 00000000000..7e1a9ed2dc3 --- /dev/null +++ b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/ut_and_examples/example-mdbx.c++ @@ -0,0 +1,99 @@ +/// \copyright SPDX-License-Identifier: Apache-2.0 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2026 +/// \file The example of using the libmdbx modern C++ API. + +#include +#include + +/* This is a minimal example now, which will be expanded soon. */ + +static void тысяча(const mdbx::path &database_pathname, const mdbx::env::mode mode, mdbx::env::durability durability) { + mdbx::env::remove(database_pathname); + std::cout << "INSERTIONx1000(" << mode << ", " << durability << ")" << std::endl; + + mdbx::env_managed env(database_pathname, mdbx::env_managed::create_parameters(), + mdbx::env::operate_parameters(3, 0, mode, durability)); + for (int i = 0; i < 1000; ++i) { + auto txn = env.start_write(); + auto map = txn.create_map("table-data", mdbx::key_mode::usual, mdbx::value_mode::single); + + auto k = std::to_string(i); + auto v = mdbx::to_base58(mdbx::slice::wrap(i)).as_string(); + txn.insert(map, mdbx::slice(k), mdbx::slice(v)); + txn.commit(); + } + + auto info = env.get_info(); + + // std::cout << " pgop.newly: " << info.mi_pgop_stat.newly << "\n"; + // std::cout << " pgop.cow: " << info.mi_pgop_stat.cow << "\n"; + // std::cout << " pgop.clone: " << info.mi_pgop_stat.clone << "\n"; + // std::cout << " pgop.split: " << info.mi_pgop_stat.split << "\n"; + // std::cout << " pgop.merge: " << info.mi_pgop_stat.merge << "\n --\n"; + + // std::cout << " pgop.spill: " << info.mi_pgop_stat.spill << "\n"; + // std::cout << " pgop.unspill: " << info.mi_pgop_stat.unspill << "\n"; + // std::cout << " pgop.mincore: " << info.mi_pgop_stat.mincore << "\n --\n"; + + std::cout << " pgop.prefault: " << info.mi_pgop_stat.prefault << "\n"; + std::cout << " pgop.msync: " << info.mi_pgop_stat.msync << "\n"; + std::cout << " pgop.fsync: " << info.mi_pgop_stat.fsync << "\n"; + std::cout << " pgop.wops: " << info.mi_pgop_stat.wops << "\n=====\n" << std::endl; +} + +static bool doit(const mdbx::path &database_pathname) { + using buffer = mdbx::buffer; + mdbx::env::remove(database_pathname); + auto operate_parameters = mdbx::env::operate_parameters(); + operate_parameters.max_maps = 11; + operate_parameters.options.nested_write_transactions = true; + mdbx::env_managed env(database_pathname, mdbx::env_managed::create_parameters(), operate_parameters); + + auto txn = env.start_write(); + auto map = txn.create_map("table-ordinals", mdbx::key_mode::ordinal, mdbx::value_mode::single); + txn.insert(map, buffer::key_from_u64(42), "a"); + txn.insert(map, buffer::key_from_double(0.1), mdbx::slice("b")); + txn.insert(map, buffer::key_from_jsonInteger(1), buffer("c")); + txn.insert(map, mdbx::slice::wrap(uint64_t(0xaBad1dea)), buffer::base58("aBad1dea")); + + auto cursor = txn.open_cursor(map); + cursor.to_first(); + while (!cursor.eof()) { + std::cout << cursor.current() << std::endl; + cursor.to_next(false); + } + + auto nested = txn.start_nested(); + cursor = nested.open_cursor(map); + size_t count = 0; + cursor.fullscan([&](const mdbx::pair &) -> bool { + count += 1; + return /* don't breaking/existing the scanning loop */ false; + }); + nested.commit(); + nested = txn.start_nested(); + + return count == nested.get_map_stat(map).ms_entries; +} + +int main(int, const char *[]) { + try { + const mdbx::path bench_database = +#if !(defined(_WIN32) || defined(_WIN64)) + "/tmp/" +#endif /* !Windows */ + "bench_example_database.mdbx"; + тысяча(bench_database, mdbx::env::mode::write_file_io, mdbx::env::durability::robust_synchronous); + тысяча(bench_database, mdbx::env::mode::write_file_io, mdbx::env::durability::half_synchronous_weak_last); + тысяча(bench_database, mdbx::env::mode::write_file_io, mdbx::env::durability::lazy_weak_tail); + тысяча(bench_database, mdbx::env::mode::write_file_io, mdbx::env::durability::whole_fragile); + тысяча(bench_database, mdbx::env::mode::write_mapped_io, mdbx::env::durability::robust_synchronous); + тысяча(bench_database, mdbx::env::mode::write_mapped_io, mdbx::env::durability::half_synchronous_weak_last); + тысяча(bench_database, mdbx::env::mode::write_mapped_io, mdbx::env::durability::lazy_weak_tail); + тысяча(bench_database, mdbx::env::mode::write_mapped_io, mdbx::env::durability::whole_fragile); + return doit("example_database") ? EXIT_SUCCESS : EXIT_FAILURE; + } catch (const std::exception &ex) { + std::cerr << "Exception: " << ex.what() << "\n"; + return EXIT_FAILURE; + } +} diff --git a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/ut_and_examples/pcrf/pcrf_simulator.c b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/ut_and_examples/pcrf/pcrf_simulator.c new file mode 100644 index 00000000000..89f912441f4 --- /dev/null +++ b/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/ut_and_examples/pcrf/pcrf_simulator.c @@ -0,0 +1,367 @@ +/* + * Copyright 2015 Vladimir Romanov + * , Yota Lab. + * SPDX-License-Identifier: AGPL-3.0 + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ + +#include +#include + +#include "mdbx.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#define IP_PRINTF_ARG_HOST(addr) \ + (int)((addr) >> 24), (int)((addr) >> 16 & 0xff), (int)((addr) >> 8 & 0xff), (int)((addr) & 0xff) + +char opt_db_path[PATH_MAX] = "./mdbx_bench2"; +static MDBX_env *env; +#define REC_COUNT 10240000 +int64_t ids[REC_COUNT * 10]; +int32_t ids_count = 0; + +int64_t mdbx_add_count = 0; +int64_t mdbx_del_count = 0; +uint64_t mdbx_add_time = 0; +uint64_t mdbx_del_time = 0; +int64_t obj_id = 0; +int64_t mdbx_data_size = 0; +int64_t mdbx_key_size = 0; + +typedef struct { + char session_id1[100]; + char session_id2[100]; + char ip[20]; + uint8_t fill[100]; +} session_data_t; + +typedef struct { + int64_t obj_id; + int8_t event_type; +} __attribute__((__packed__)) event_data_t; + +static void add_id_to_pool(int64_t id) { + ids[ids_count] = id; + ids_count++; +} + +static inline int64_t getClockUs(void) { + struct timespec val; +#ifdef CYGWIN + clock_gettime(CLOCK_REALTIME, &val); +#else + clock_gettime(CLOCK_MONOTONIC, &val); +#endif + return val.tv_sec * ((int64_t)1000000) + val.tv_nsec / 1000; +} + +static int64_t get_id_from_pool() { + if (ids_count == 0) { + return -1; + } + int32_t index = rand() % ids_count; + int64_t id = ids[index]; + ids[index] = ids[ids_count - 1]; + ids_count--; + return id; +} + +#define MDBX_CHECK(x) \ + do { \ + const int rc = (x); \ + if (rc != MDBX_SUCCESS) { \ + printf("Error [%d] %s in %s at %s:%d\n", rc, mdbx_strerror(rc), #x, __FILE__, __LINE__); \ + exit(EXIT_FAILURE); \ + } \ + } while (0) + +static void db_connect() { + MDBX_dbi dbi_session; + MDBX_dbi dbi_session_id; + MDBX_dbi dbi_event; + MDBX_dbi dbi_ip; + + MDBX_CHECK(mdbx_env_create(&env)); + MDBX_CHECK(mdbx_env_set_geometry(env, 0, 0, REC_COUNT * sizeof(session_data_t) * 10, -1, -1, -1)); + MDBX_CHECK(mdbx_env_set_maxdbs(env, 30)); + MDBX_CHECK( + mdbx_env_open(env, opt_db_path, MDBX_CREATE | MDBX_WRITEMAP | MDBX_UTTERLY_NOSYNC | MDBX_LIFORECLAIM, 0664)); + MDBX_txn *txn; + + // transaction init + MDBX_CHECK(mdbx_txn_begin(env, NULL, 0, &txn)); + // open database in read-write mode + MDBX_CHECK(mdbx_dbi_open(txn, "session", MDBX_CREATE, &dbi_session)); + MDBX_CHECK(mdbx_dbi_open(txn, "session_id", MDBX_CREATE, &dbi_session_id)); + MDBX_CHECK(mdbx_dbi_open(txn, "event", MDBX_CREATE, &dbi_event)); + MDBX_CHECK(mdbx_dbi_open(txn, "ip", MDBX_CREATE, &dbi_ip)); + // transaction commit + MDBX_CHECK(mdbx_txn_commit(txn)); + printf("Connection open\n"); +} + +static void create_record(uint64_t record_id) { + MDBX_dbi dbi_session; + MDBX_dbi dbi_session_id; + MDBX_dbi dbi_event; + MDBX_dbi dbi_ip; + event_data_t event; + MDBX_txn *txn; + session_data_t data; + // transaction init + snprintf(data.session_id1, sizeof(data.session_id1), "prefix%02u_%02u.fill.fill.fill.fill.fill.fill;%" PRIu64, + (unsigned)(record_id % 3) + 1, (unsigned)(record_id % 9) + 1, record_id); + snprintf(data.session_id2, sizeof(data.session_id2), "dprefix%" PRIu64 ";%" PRIu64 ".fill.fill.;suffix", record_id, + (record_id + UINT64_C(1442695040888963407)) % UINT64_C(6364136223846793005)); + snprintf(data.ip, sizeof(data.ip), "%d.%d.%d.%d", IP_PRINTF_ARG_HOST(record_id & 0xFFFFFFFF)); + event.obj_id = record_id; + event.event_type = 1; + + MDBX_val _session_id1_rec = {data.session_id1, strlen(data.session_id1)}; + MDBX_val _session_id2_rec = {data.session_id2, strlen(data.session_id2)}; + MDBX_val _ip_rec = {data.ip, strlen(data.ip)}; + MDBX_val _obj_id_rec = {&record_id, sizeof(record_id)}; + MDBX_val _data_rec = {&data, offsetof(session_data_t, fill) + (rand() % sizeof(data.fill))}; + MDBX_val _event_rec = {&event, sizeof(event)}; + + uint64_t start = getClockUs(); + MDBX_CHECK(mdbx_txn_begin(env, NULL, 0, &txn)); + MDBX_CHECK(mdbx_dbi_open(txn, "session", MDBX_CREATE, &dbi_session)); + MDBX_CHECK(mdbx_dbi_open(txn, "session_id", MDBX_CREATE, &dbi_session_id)); + MDBX_CHECK(mdbx_dbi_open(txn, "event", MDBX_CREATE, &dbi_event)); + MDBX_CHECK(mdbx_dbi_open(txn, "ip", MDBX_CREATE, &dbi_ip)); + MDBX_CHECK(mdbx_put(txn, dbi_session, &_obj_id_rec, &_data_rec, MDBX_NOOVERWRITE | MDBX_NODUPDATA)); + MDBX_CHECK(mdbx_put(txn, dbi_session_id, &_session_id1_rec, &_obj_id_rec, MDBX_NOOVERWRITE | MDBX_NODUPDATA)); + MDBX_CHECK(mdbx_put(txn, dbi_session_id, &_session_id2_rec, &_obj_id_rec, MDBX_NOOVERWRITE | MDBX_NODUPDATA)); + MDBX_CHECK(mdbx_put(txn, dbi_ip, &_ip_rec, &_obj_id_rec, 0)); + MDBX_CHECK(mdbx_put(txn, dbi_event, &_event_rec, &_obj_id_rec, 0)); + MDBX_CHECK(mdbx_txn_commit(txn)); + + mdbx_data_size += (_data_rec.iov_len + _obj_id_rec.iov_len * 4); + mdbx_key_size += (_obj_id_rec.iov_len + _session_id1_rec.iov_len + _session_id2_rec.iov_len + _ip_rec.iov_len + + _event_rec.iov_len); + + // transaction commit + mdbx_add_count++; + mdbx_add_time += (getClockUs() - start); +} + +static void delete_record(int64_t record_id) { + MDBX_dbi dbi_session; + MDBX_dbi dbi_session_id; + MDBX_dbi dbi_event; + MDBX_dbi dbi_ip; + event_data_t event; + MDBX_txn *txn; + + // transaction init + uint64_t start = getClockUs(); + MDBX_CHECK(mdbx_txn_begin(env, NULL, 0, &txn)); + // open database in read-write mode + MDBX_CHECK(mdbx_dbi_open(txn, "session", MDBX_CREATE, &dbi_session)); + MDBX_CHECK(mdbx_dbi_open(txn, "session_id", MDBX_CREATE, &dbi_session_id)); + MDBX_CHECK(mdbx_dbi_open(txn, "event", MDBX_CREATE, &dbi_event)); + MDBX_CHECK(mdbx_dbi_open(txn, "ip", MDBX_CREATE, &dbi_ip)); + // put data + MDBX_val _obj_id_rec = {&record_id, sizeof(record_id)}; + MDBX_val _data_rec; + // get data + MDBX_CHECK(mdbx_get(txn, dbi_session, &_obj_id_rec, &_data_rec)); + session_data_t *data = (session_data_t *)_data_rec.iov_base; + + MDBX_val _session_id1_rec = {data->session_id1, strlen(data->session_id1)}; + MDBX_val _session_id2_rec = {data->session_id2, strlen(data->session_id2)}; + MDBX_val _ip_rec = {data->ip, strlen(data->ip)}; + MDBX_CHECK(mdbx_del(txn, dbi_session_id, &_session_id1_rec, NULL)); + MDBX_CHECK(mdbx_del(txn, dbi_session_id, &_session_id2_rec, NULL)); + MDBX_CHECK(mdbx_del(txn, dbi_ip, &_ip_rec, NULL)); + event.obj_id = record_id; + event.event_type = 1; + MDBX_val _event_rec = {&event, sizeof(event)}; + MDBX_CHECK(mdbx_del(txn, dbi_event, &_event_rec, NULL)); + MDBX_CHECK(mdbx_del(txn, dbi_session, &_obj_id_rec, NULL)); + + mdbx_data_size -= (_data_rec.iov_len + _obj_id_rec.iov_len * 4); + mdbx_key_size -= (_obj_id_rec.iov_len + _session_id1_rec.iov_len + _session_id2_rec.iov_len + _ip_rec.iov_len + + _event_rec.iov_len); + + // transaction commit + MDBX_CHECK(mdbx_txn_commit(txn)); + mdbx_del_count++; + mdbx_del_time += (getClockUs() - start); +} + +static void db_disconnect() { + mdbx_env_close(env); + printf("Connection closed\n"); +} + +static void get_db_stat(const char *db, int64_t *ms_branch_pages, int64_t *ms_leaf_pages) { + MDBX_txn *txn; + MDBX_stat stat; + MDBX_dbi dbi; + + MDBX_CHECK(mdbx_txn_begin(env, NULL, MDBX_TXN_RDONLY, &txn)); + MDBX_CHECK(mdbx_dbi_open(txn, db, MDBX_DB_ACCEDE, &dbi)); + MDBX_CHECK(mdbx_dbi_stat(txn, dbi, &stat, sizeof(stat))); + mdbx_txn_abort(txn); + printf("%15s | %15" PRIu64 " | %5u | %10" PRIu64 " | %10" PRIu64 " | %11" PRIu64 " |\n", db, stat.ms_branch_pages, + stat.ms_depth, stat.ms_entries, stat.ms_leaf_pages, stat.ms_overflow_pages); + (*ms_branch_pages) += stat.ms_branch_pages; + (*ms_leaf_pages) += stat.ms_leaf_pages; +} + +static void periodic_stat(void) { + int64_t ms_branch_pages = 0; + int64_t ms_leaf_pages = 0; + MDBX_stat mst; + MDBX_envinfo mei; + MDBX_CHECK(mdbx_env_stat_ex(env, NULL, &mst, sizeof(mst))); + MDBX_CHECK(mdbx_env_info_ex(env, NULL, &mei, sizeof(mei))); + printf("Environment Info\n"); + printf(" Pagesize: %u\n", mst.ms_psize); + if (mei.mi_geo.lower != mei.mi_geo.upper) { + printf(" Dynamic datafile: %" PRIu64 "..%" PRIu64 " bytes (+%" PRIu64 "/-%" PRIu64 "), %" PRIu64 "..%" PRIu64 + " pages (+%" PRIu64 "/-%" PRIu64 ")\n", + mei.mi_geo.lower, mei.mi_geo.upper, mei.mi_geo.grow, mei.mi_geo.shrink, mei.mi_geo.lower / mst.ms_psize, + mei.mi_geo.upper / mst.ms_psize, mei.mi_geo.grow / mst.ms_psize, mei.mi_geo.shrink / mst.ms_psize); + printf(" Current datafile: %" PRIu64 " bytes, %" PRIu64 " pages\n", mei.mi_geo.current, + mei.mi_geo.current / mst.ms_psize); + } else { + printf(" Fixed datafile: %" PRIu64 " bytes, %" PRIu64 " pages\n", mei.mi_geo.current, + mei.mi_geo.current / mst.ms_psize); + } + printf(" Current mapsize: %" PRIu64 " bytes, %" PRIu64 " pages \n", mei.mi_mapsize, mei.mi_mapsize / mst.ms_psize); + printf(" Number of pages used: %" PRIu64 "\n", mei.mi_last_pgno + 1); + printf(" Last transaction ID: %" PRIu64 "\n", mei.mi_recent_txnid); + printf(" Tail transaction ID: %" PRIu64 " (%" PRIi64 ")\n", mei.mi_latter_reader_txnid, + mei.mi_latter_reader_txnid - mei.mi_recent_txnid); + printf(" Max readers: %u\n", mei.mi_maxreaders); + printf(" Number of readers used: %u\n", mei.mi_numreaders); + + printf(" Name | ms_branch_pages | depth | entries | leaf_pages " + "| overf_pages |\n"); + get_db_stat("session", &ms_branch_pages, &ms_leaf_pages); + get_db_stat("session_id", &ms_branch_pages, &ms_leaf_pages); + get_db_stat("event", &ms_branch_pages, &ms_leaf_pages); + get_db_stat("ip", &ms_branch_pages, &ms_leaf_pages); + printf("%15s | %15" PRIu64 " | %5s | %10s | %10" PRIu64 " | %11s |\n", "", ms_branch_pages, "", "", ms_leaf_pages, + ""); + + static int64_t prev_add_count; + static int64_t prev_del_count; + static uint64_t prev_add_time; + static uint64_t prev_del_time; + static int64_t t = -1; + if (t > 0) { + int64_t delta = (getClockUs() - t); + printf("CPS: add %" PRIu64 ", delete %" PRIu64 ", items processed - %" PRIu64 "K data=%" PRIu64 "K key=%" PRIu64 + "K\n", + (mdbx_add_count - prev_add_count) * 1000000 / delta, (mdbx_del_count - prev_del_count) * 1000000 / delta, + obj_id / 1024, mdbx_data_size / 1024, mdbx_key_size / 1024); + printf("usage data=%" PRIu64 "%%", + ((mdbx_data_size + mdbx_key_size) * 100) / ((ms_leaf_pages + ms_branch_pages) * 4096)); + if (prev_add_time != mdbx_add_time) { + printf(" Add : %" PRIu64 " c/s", (mdbx_add_count - prev_add_count) * 1000000 / (mdbx_add_time - prev_add_time)); + } + if (prev_del_time != mdbx_del_time) { + printf(" Del : %" PRIu64 " c/s", (mdbx_del_count - prev_del_count) * 1000000 / (mdbx_del_time - prev_del_time)); + } + if (mdbx_add_time) { + printf(" tAdd : %" PRIu64 " c/s", mdbx_add_count * 1000000 / mdbx_add_time); + } + if (mdbx_del_time) { + printf(" tDel : %" PRIu64 " c/s", mdbx_del_count * 1000000 / mdbx_del_time); + } + puts(""); + } + t = getClockUs(); + prev_add_count = mdbx_add_count; + prev_del_count = mdbx_del_count; + prev_add_time = mdbx_add_time; + prev_del_time = mdbx_del_time; +} + +#if 0 /* unused */ +static void periodic_add_rec() { + for (int i = 0; i < 10240; i++) { + if (ids_count <= REC_COUNT) { + int64_t id = obj_id++; + create_record(id); + add_id_to_pool(id); + } + if (ids_count > REC_COUNT) { + int64_t id = get_id_from_pool(); + delete_record(id); + } + } + periodic_stat(); +} +#endif + +int main(int argc, char **argv) { + (void)argc; + (void)argv; + + char filename[PATH_MAX]; + int i; + + strcpy(filename, opt_db_path); + strcat(filename, MDBX_DATANAME); + remove(filename); + + strcpy(filename, opt_db_path); + strcat(filename, MDBX_LOCKNAME); + remove(filename); + + puts("Open DB..."); + db_connect(); + puts("Create data..."); + int64_t t = getClockUs(); + for (i = 0; i < REC_COUNT; i++) { + int64_t id = obj_id++; + create_record(id); + add_id_to_pool(id); + if (i % 1000 == 0) { + int64_t now = getClockUs(); + if ((now - t) > 1000000L) { + periodic_stat(); + t = now; + } + } + } + periodic_stat(); + while (1) { + int i; + for (i = 0; i < 1000; i++) { + int64_t id = obj_id++; + create_record(id); + add_id_to_pool(id); + id = get_id_from_pool(); + delete_record(id); + } + for (i = 0; i < 50; i++) { + int64_t id = obj_id++; + create_record(id); + add_id_to_pool(id); + } + int64_t id = obj_id++; + create_record(id); + add_id_to_pool(id); + int64_t now = getClockUs(); + if ((now - t) > 10000000L) { + periodic_stat(); + t = now; + } + } + db_disconnect(); + return 0; +} From 098ede2b3c85e7899c7bcc3cd471d0a3504fa77f Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Wed, 6 May 2026 11:19:13 +0100 Subject: [PATCH 040/335] ci(bench): move metrics to port 9001 (#24009) --- .github/scripts/bench-metrics-proxy.py | 4 ++-- .github/scripts/bench-reth-local.sh | 2 +- .github/workflows/bench-scheduled.yml | 2 +- .github/workflows/bench.yml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/scripts/bench-metrics-proxy.py b/.github/scripts/bench-metrics-proxy.py index 2a6044a0eb8..5adfba751cc 100644 --- a/.github/scripts/bench-metrics-proxy.py +++ b/.github/scripts/bench-metrics-proxy.py @@ -241,8 +241,8 @@ def main(): parser = argparse.ArgumentParser(description="Prometheus metrics proxy with label injection") parser.add_argument("--labels", default="/tmp/bench-metrics-labels.json", help="Path to JSON file with labels to inject (default: /tmp/bench-metrics-labels.json)") - parser.add_argument("--upstream", default="http://127.0.0.1:9100/", - help="Upstream reth metrics URL (default: http://127.0.0.1:9100/)") + parser.add_argument("--upstream", default="http://127.0.0.1:9001/", + help="Upstream reth metrics URL (default: http://127.0.0.1:9001/)") bind_group = parser.add_mutually_exclusive_group() bind_group.add_argument("--bind", default=None, diff --git a/.github/scripts/bench-reth-local.sh b/.github/scripts/bench-reth-local.sh index 1895770c3d1..dc6812a3a32 100755 --- a/.github/scripts/bench-reth-local.sh +++ b/.github/scripts/bench-reth-local.sh @@ -199,7 +199,7 @@ export BENCH_TRACY_FILTER="$TRACY_FILTER" export BENCH_WORK_DIR export SCHELK_MOUNT="${SCHELK_MOUNT:-/reth-bench}" export BENCH_RPC_URL="${BENCH_RPC_URL:-https://ethereum.reth.rs/rpc}" -export BENCH_METRICS_ADDR="127.0.0.1:9100" +export BENCH_METRICS_ADDR="127.0.0.1:9001" # ── Step 1: Resolve refs to full SHAs ──────────────────────────────── echo "▸ Resolving git refs..." diff --git a/.github/workflows/bench-scheduled.yml b/.github/workflows/bench-scheduled.yml index 2055fe952c1..f6c613e8b0c 100644 --- a/.github/workflows/bench-scheduled.yml +++ b/.github/workflows/bench-scheduled.yml @@ -273,7 +273,7 @@ jobs: BENCH_COMMENT_ID: "" BENCH_SLACK: ${{ github.event_name == 'workflow_dispatch' && inputs.slack || 'always' }} BENCH_SNAPSHOT_MANIFEST_URL: ${{ secrets.BENCH_SNAPSHOT_MANIFEST_URL }} - BENCH_METRICS_ADDR: "127.0.0.1:9100" + BENCH_METRICS_ADDR: "127.0.0.1:9001" BENCH_OTLP_DISABLED: "true" BASELINE_REF: ${{ needs.resolve-refs.outputs.baseline-ref }} FEATURE_REF: ${{ needs.resolve-refs.outputs.feature-ref }} diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index d33adae12be..951fdfe0426 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -556,7 +556,7 @@ jobs: BENCH_SLACK: ${{ needs.reth-bench-ack.outputs.slack }} BENCH_NODE_BIN: ${{ needs.reth-bench-ack.outputs.big-blocks == 'true' && 'reth-bb' || 'reth' }} BENCH_SNAPSHOT_MANIFEST_URL: ${{ secrets.BENCH_SNAPSHOT_MANIFEST_URL }} - BENCH_METRICS_ADDR: "127.0.0.1:9100" + BENCH_METRICS_ADDR: "127.0.0.1:9001" BENCH_OTLP_TRACES_ENDPOINT: ${{ needs.reth-bench-ack.outputs.otlp != 'false' && secrets.BENCH_OTLP_TRACES_ENDPOINT || '' }} BENCH_OTLP_LOGS_ENDPOINT: ${{ needs.reth-bench-ack.outputs.otlp != 'false' && secrets.BENCH_OTLP_LOGS_ENDPOINT || '' }} steps: From 0dd0de65d84e37115be22def507fd8e98f5d6833 Mon Sep 17 00:00:00 2001 From: Karl Yu <43113774+0xKarl98@users.noreply.github.com> Date: Wed, 6 May 2026 18:30:50 +0800 Subject: [PATCH 041/335] refactor(storage): add decoded BAL lookup (#23998) --- crates/storage/storage-api/src/bal.rs | 76 ++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/crates/storage/storage-api/src/bal.rs b/crates/storage/storage-api/src/bal.rs index c6dead0bb36..c5dac1854f1 100644 --- a/crates/storage/storage-api/src/bal.rs +++ b/crates/storage/storage-api/src/bal.rs @@ -1,5 +1,5 @@ use alloc::{sync::Arc, vec::Vec}; -use alloy_eips::NumHash; +use alloy_eips::{eip7928::bal::DecodedBal, NumHash}; use alloy_primitives::{BlockHash, BlockNumber, Bytes, Sealed}; use reth_storage_errors::provider::ProviderResult; @@ -48,6 +48,19 @@ pub trait BalStore: Send + Sync + 'static { /// The returned vector must align with `block_hashes`. fn get_by_hashes(&self, block_hashes: &[BlockHash]) -> ProviderResult>>; + /// Fetches the BAL for the given block hash. + fn get_by_hash(&self, block_hash: BlockHash) -> ProviderResult> { + Ok(self.get_by_hashes(&[block_hash])?.into_iter().next().flatten()) + } + + /// Fetches and decodes the BAL for the given block hash. + fn get_decoded_by_hash(&self, block_hash: BlockHash) -> ProviderResult> { + self.get_by_hash(block_hash)? + .map(DecodedBal::from_rlp_bytes) + .transpose() + .map_err(Into::into) + } + /// Fetch BAL response entries for the given block hashes, stopping after the soft limit is /// exceeded. /// @@ -148,6 +161,18 @@ impl BalStoreHandle { self.inner.get_by_hashes(block_hashes) } + /// Fetches the BAL for the given block hash. + #[inline] + pub fn get_by_hash(&self, block_hash: BlockHash) -> ProviderResult> { + self.inner.get_by_hash(block_hash) + } + + /// Fetches and decodes the BAL for the given block hash. + #[inline] + pub fn get_decoded_by_hash(&self, block_hash: BlockHash) -> ProviderResult> { + self.inner.get_decoded_by_hash(block_hash) + } + /// Fetch BAL response entries for the given block hashes, stopping after the soft limit is /// exceeded. #[inline] @@ -261,9 +286,30 @@ mod tests { let by_range = store.get_by_range(1, 10).unwrap(); assert_eq!(by_hash, vec![None, None]); + assert!(store.get_by_hash(B256::random()).unwrap().is_none()); assert!(by_range.is_empty()); } + #[test] + fn noop_store_decoded_lookup_returns_none() { + let store = BalStoreHandle::default(); + + assert!(store.get_decoded_by_hash(B256::random()).unwrap().is_none()); + } + + #[test] + fn decoded_lookup_decodes_raw_bal() { + let hash = B256::random(); + let raw_bal = Bytes::from_static(&[0xc0]); + let store = BalStoreHandle::new(TestBalStore { hash, raw_bal: raw_bal.clone() }); + + assert_eq!(store.get_by_hash(hash).unwrap(), Some(raw_bal.clone())); + + let decoded = store.get_decoded_by_hash(hash).unwrap().unwrap(); + + assert_eq!(decoded.as_raw(), &raw_bal); + } + #[test] fn noop_store_limited_lookup_returns_prefix() { let store = BalStoreHandle::default(); @@ -295,4 +341,32 @@ mod tests { assert!(stream.next().await.is_none()); } + + #[derive(Debug)] + struct TestBalStore { + hash: B256, + raw_bal: Bytes, + } + + impl BalStore for TestBalStore { + fn insert(&self, _num_hash: NumHash, _bal: SealedBal) -> ProviderResult<()> { + Ok(()) + } + + fn get_by_hashes(&self, block_hashes: &[BlockHash]) -> ProviderResult>> { + Ok(block_hashes + .iter() + .map(|hash| (*hash == self.hash).then(|| self.raw_bal.clone())) + .collect()) + } + + fn get_by_range(&self, _start: BlockNumber, _count: u64) -> ProviderResult> { + Ok(Vec::new()) + } + + #[cfg(feature = "std")] + fn bal_stream(&self) -> BalNotificationStream { + reth_tokio_util::EventSender::new(1).new_listener() + } + } } From b46f961237450b632c4cbece3b844e99d3064bba Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Wed, 6 May 2026 16:32:37 +0400 Subject: [PATCH 042/335] perf(generate-big-blocks): don't fetch receipts (#24011) --- Cargo.lock | 1 - bin/reth-bench/Cargo.toml | 1 - .../src/bench/generate_big_block.rs | 61 ++++--------------- 3 files changed, 12 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 619b2419c61..3d9fece03f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7611,7 +7611,6 @@ dependencies = [ "reth-cli-util", "reth-engine-primitives", "reth-ethereum-cli", - "reth-ethereum-primitives", "reth-fs-util", "reth-node-api", "reth-node-core", diff --git a/bin/reth-bench/Cargo.toml b/bin/reth-bench/Cargo.toml index f74e34f7d8b..63f0fc0e80b 100644 --- a/bin/reth-bench/Cargo.toml +++ b/bin/reth-bench/Cargo.toml @@ -20,7 +20,6 @@ reth-cli-runner.workspace = true reth-cli-util.workspace = true reth-engine-primitives.workspace = true reth-ethereum-cli.workspace = true -reth-ethereum-primitives.workspace = true reth-fs-util.workspace = true reth-node-api.workspace = true reth-node-core.workspace = true diff --git a/bin/reth-bench/src/bench/generate_big_block.rs b/bin/reth-bench/src/bench/generate_big_block.rs index 77774363558..dabc9549745 100644 --- a/bin/reth-bench/src/bench/generate_big_block.rs +++ b/bin/reth-bench/src/bench/generate_big_block.rs @@ -27,7 +27,6 @@ use reth_cli::chainspec::ChainSpecParser; use reth_cli_runner::CliContext; use reth_engine_primitives::BigBlockData; use reth_ethereum_cli::chainspec::EthereumChainSpecParser; -use reth_ethereum_primitives::Receipt; use serde::{Deserialize, Serialize}; use std::{ collections::{HashMap, HashSet}, @@ -35,8 +34,6 @@ use std::{ }; use tracing::{info, warn}; -use crate::bench::helpers::fetch_block_access_list; - /// A single transaction with its gas used and raw encoded bytes. #[derive(Debug, Clone)] pub struct RawTransaction { @@ -380,8 +377,7 @@ impl Command { break; } }; - let FetchedBlock { execution_data, consensus_receipts, block_access_list } = - fetched; + let FetchedBlock { execution_data, block_access_list } = fetched; let block_gas = execution_data.payload.as_v1().gas_used; let block_blob_gas = @@ -393,7 +389,6 @@ impl Command { gas_used = block_gas, blob_gas_used = block_blob_gas, tx_count = execution_data.payload.transactions().len(), - receipts = consensus_receipts.len(), "Fetched block" ); @@ -629,9 +624,6 @@ impl Command { struct FetchedBlock { /// Execution payload with sidecar derived from the RPC block. execution_data: ExecutionData, - /// Consensus-format receipts (`cumulative_gas_used` is still per-block, callers offset - /// it when merging). - consensus_receipts: Vec, /// `eth_getBlockAccessListByBlockNumber` result when `--bal` is enabled. block_access_list: Option, } @@ -643,46 +635,17 @@ async fn fetch_one_block( block_number: u64, bal_enabled: bool, ) -> eyre::Result> { - let (rpc_block, receipts) = tokio::try_join!( - provider.get_block_by_number(block_number.into()).full(), - provider.get_block_receipts(block_number.into()), - )?; - let (rpc_block, receipts) = match (rpc_block, receipts) { - (Some(b), Some(r)) => (b, r), - _ => return Ok(None), - }; - - let block_access_list = if bal_enabled { - Some( - fetch_block_access_list(&provider, block_number) - .await - .wrap_err_with(|| format!("Failed to fetch BAL for block {block_number}"))?, - ) - } else { - None - }; - - let consensus_receipts: Vec = receipts - .iter() - .map(|r| { - let inner = &r.inner.inner.inner; - let tx_type = r.inner.inner.r#type.try_into().unwrap_or_default(); - Receipt { - tx_type, - success: inner.receipt.status.coerce_status(), - cumulative_gas_used: inner.receipt.cumulative_gas_used, - logs: inner - .receipt - .logs - .iter() - .map(|log| alloy_primitives::Log { - address: log.inner.address, - data: log.inner.data.clone(), - }) - .collect(), + let (rpc_block, block_access_list) = + tokio::try_join!(provider.get_block_by_number(block_number.into()).full(), async { + if bal_enabled { + provider.get_block_access_list_by_number(block_number.into()).await + } else { + Ok(None) } - }) - .collect(); + })?; + let Some(rpc_block) = rpc_block else { + return Ok(None); + }; let block = rpc_block .into_inner() @@ -695,7 +658,7 @@ async fn fetch_one_block( let (payload, sidecar) = ExecutionPayload::from_block_slow(&block); let execution_data = ExecutionData { payload, sidecar }; - Ok(Some(FetchedBlock { execution_data, consensus_receipts, block_access_list })) + Ok(Some(FetchedBlock { execution_data, block_access_list })) } fn merge_block_access_list( From 96be18816d351e4409f404d063f2013297528f56 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Wed, 6 May 2026 17:03:07 +0400 Subject: [PATCH 043/335] perf: avoid clone (#23940) --- crates/engine/tree/src/tree/payload_validator.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/engine/tree/src/tree/payload_validator.rs b/crates/engine/tree/src/tree/payload_validator.rs index 43e1d5a5703..5066e47e93f 100644 --- a/crates/engine/tree/src/tree/payload_validator.rs +++ b/crates/engine/tree/src/tree/payload_validator.rs @@ -109,7 +109,7 @@ pub type ValidationOutcome>> = Result<(ExecutedBlock, Option>), E>; /// Handle to a [`HashedPostState`] computed on a background thread. -type LazyHashedPostState = reth_tasks::LazyHandle; +type LazyHashedPostState = reth_tasks::LazyHandle>; /// Result type for block validation with optional timing stats. type InsertPayloadResult = Result< @@ -600,7 +600,7 @@ where "hashed_post_state", ) .entered(); - hashed_state_provider.hashed_post_state(&hashed_state_output.state) + Arc::new(hashed_state_provider.hashed_post_state(&hashed_state_output.state)) }); let block = convert_to_block(input)?; @@ -1125,7 +1125,7 @@ where state_provider: StateProviderBox, hashed_state: &LazyHashedPostState, ) -> ProviderResult<(B256, TrieUpdates)> { - state_provider.state_root_with_updates(hashed_state.get().clone()) + state_provider.state_root_with_updates(hashed_state.get().as_ref().clone()) } /// Awaits the state root from the background task, with an optional timeout fallback. @@ -1639,8 +1639,8 @@ where // Resolve the lazy handle into Arc. By this point the hashed state has // already been computed and used for state root verification, so .get() returns instantly. let hashed_state = match hashed_state.try_into_inner() { - Ok(state) => Arc::new(state), - Err(handle) => Arc::new(handle.get().clone()), + Ok(state) => state, + Err(handle) => handle.get().clone(), }; let deferred_trie_data = DeferredTrieData::pending(hashed_state, trie_output, anchor_hash, ancestors); @@ -2032,7 +2032,7 @@ where block.recovered_block, block.execution_output, state, - LazyHashedPostState::ready(Arc::unwrap_or_clone(block.hashed_state)), + LazyHashedPostState::ready(block.hashed_state), block.trie_updates, changeset_provider, )) From 5acc992ebfc7f6db10a1dd35f2fc317c91db7a8c Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 6 May 2026 15:58:42 +0200 Subject: [PATCH 044/335] fix(discv5): advertise configured NAT IP in ENR (#24013) --- Cargo.lock | 1 + crates/ethereum/node/Cargo.toml | 1 + crates/ethereum/node/tests/e2e/rpc.rs | 47 +++++++++++++++++++++++++++ crates/net/discv5/src/config.rs | 26 +++++++++++++++ crates/net/discv5/src/lib.rs | 12 +++++-- crates/net/network/src/config.rs | 12 +++++-- 6 files changed, 94 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3d9fece03f7..18e78831e9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9310,6 +9310,7 @@ dependencies = [ "alloy-rpc-types-trace", "alloy-signer", "alloy-sol-types", + "enr", "eyre", "futures", "jsonrpsee-core", diff --git a/crates/ethereum/node/Cargo.toml b/crates/ethereum/node/Cargo.toml index 3a52f188a90..24737c34f19 100644 --- a/crates/ethereum/node/Cargo.toml +++ b/crates/ethereum/node/Cargo.toml @@ -78,6 +78,7 @@ tokio.workspace = true serde_json.workspace = true rand.workspace = true serde.workspace = true +enr = { workspace = true, features = ["rust-secp256k1"] } alloy-rpc-types-trace.workspace = true similar-asserts.workspace = true diff --git a/crates/ethereum/node/tests/e2e/rpc.rs b/crates/ethereum/node/tests/e2e/rpc.rs index 89f4300daf1..2fc1b670d01 100644 --- a/crates/ethereum/node/tests/e2e/rpc.rs +++ b/crates/ethereum/node/tests/e2e/rpc.rs @@ -23,6 +23,7 @@ use reth_payload_primitives::BuiltPayload; use reth_rpc_api::servers::AdminApiServer; use reth_tasks::Runtime; use std::{ + net::{IpAddr, Ipv4Addr}, sync::Arc, time::{SystemTime, UNIX_EPOCH}, }; @@ -419,3 +420,49 @@ async fn test_admin_node_info_uses_discv5_port_when_discv4_is_disabled() -> eyre Ok(()) } + +#[tokio::test] +async fn test_admin_node_info_discv5_enr_uses_nat_extip_when_discv4_is_disabled() -> eyre::Result<()> +{ + reth_tracing::init_test_tracing(); + + let runtime = Runtime::test(); + + let genesis: Genesis = serde_json::from_str(include_str!("../assets/genesis.json")).unwrap(); + let chain_spec = + Arc::new(ChainSpecBuilder::default().chain(MAINNET.chain).genesis(genesis).build()); + + let mut network = NetworkArgs::default().with_unused_ports(); + network.bootnodes = Some(Vec::new()); + network.discovery.disable_dns_discovery = true; + network.discovery.disable_discv4_discovery = true; + let external_ip = Ipv4Addr::new(203, 0, 113, 7); + network = network.with_nat_resolver(NatResolver::ExternalIp(IpAddr::V4(external_ip))); + + let node_config = NodeConfig::test() + .with_chain(chain_spec) + .with_network(network) + .with_rpc(RpcServerArgs::default().with_unused_ports().with_http()); + + let NodeHandle { node, node_exit_future: _ } = NodeBuilder::new(node_config) + .testing_node(runtime) + .node(EthereumNode::default()) + .launch() + .await?; + + assert!(node.network.discv4().is_none()); + let discv5 = node.network.discv5().expect("discv5 should be enabled"); + let discv5_port = discv5.local_port(); + let info = node.add_ons_handle.admin_api().node_info().await.unwrap(); + let admin_enr: enr::Enr = + info.enr.parse().map_err(|err| eyre::eyre!("failed to parse admin ENR: {err}"))?; + + assert_eq!(discv5.local_enr().ip4(), Some(external_ip)); + assert_eq!(discv5.local_enr().udp4(), Some(discv5_port)); + assert_eq!(admin_enr.ip4(), Some(external_ip)); + assert_eq!(admin_enr.udp4(), Some(discv5_port)); + assert_eq!(info.ip, IpAddr::V4(external_ip)); + assert_eq!(info.ports.discovery, discv5_port); + + Ok(()) +} diff --git a/crates/net/discv5/src/config.rs b/crates/net/discv5/src/config.rs index 1e07986ea6b..16e94cf79a3 100644 --- a/crates/net/discv5/src/config.rs +++ b/crates/net/discv5/src/config.rs @@ -72,6 +72,12 @@ pub struct ConfigBuilder { /// NOTE: IP address of `RLPx` socket overwrites IP address of same IP version in /// [`discv5::ListenConfig`]. tcp_socket: SocketAddr, + /// IP address to advertise in the local ENR instead of the listen socket address. + /// + /// This is separate from [`discv5::ListenConfig`] because the listen address describes where + /// discv5 binds its UDP socket. Nodes commonly bind to an unspecified address like `0.0.0.0` + /// while advertising an externally reachable address from NAT configuration. + advertised_ip: Option, /// List of `(key, rlp-encoded-value)` tuples that should be advertised in local node record /// (in addition to tcp port, udp port and fork). other_enr_kv_pairs: Vec<(&'static [u8], Bytes)>, @@ -95,6 +101,7 @@ impl ConfigBuilder { bootstrap_nodes, fork, tcp_socket, + advertised_ip, other_enr_kv_pairs, lookup_interval, bootstrap_lookup_interval, @@ -107,6 +114,7 @@ impl ConfigBuilder { bootstrap_nodes, fork: fork.map(|(key, fork_id)| (key, fork_id.fork_id)), tcp_socket, + advertised_ip, other_enr_kv_pairs, lookup_interval: Some(lookup_interval), bootstrap_lookup_interval: Some(bootstrap_lookup_interval), @@ -177,6 +185,13 @@ impl ConfigBuilder { self } + /// Sets the IP address to advertise in the local [`Enr`](discv5::enr::Enr), without changing + /// the discv5 listen socket. + pub const fn advertised_ip(mut self, ip: IpAddr) -> Self { + self.advertised_ip = Some(ip); + self + } + /// Adds an additional kv-pair to include in the local [`Enr`](discv5::enr::Enr). Takes the key /// to use for the kv-pair and the rlp encoded value. pub fn add_enr_kv_pair(mut self, key: &'static [u8], value: Bytes) -> Self { @@ -221,6 +236,7 @@ impl ConfigBuilder { bootstrap_nodes, fork, tcp_socket, + advertised_ip, other_enr_kv_pairs, lookup_interval, bootstrap_lookup_interval, @@ -234,6 +250,12 @@ impl ConfigBuilder { discv5_config.listen_config = amend_listen_config_wrt_rlpx(&discv5_config.listen_config, tcp_socket.ip()); + if advertised_ip.is_some() { + // The upstream discv5 service can update local ENR IP fields from peer-observed + // socket addresses. When the operator configured a NAT address, that address is + // intentional advertisement state and must not be replaced by peer observations. + discv5_config.enr_update = false; + } let fork = fork.map(|(key, fork_id)| (key, fork_id.into())); @@ -251,6 +273,7 @@ impl ConfigBuilder { bootstrap_nodes, fork, tcp_socket, + advertised_ip, other_enr_kv_pairs, lookup_interval, bootstrap_lookup_interval, @@ -276,6 +299,8 @@ pub struct Config { /// NOTE: IP address of `RLPx` socket overwrites IP address of same IP version in /// [`discv5::ListenConfig`]. pub(super) tcp_socket: SocketAddr, + /// IP address to advertise in the local ENR instead of the listen socket address. + pub(super) advertised_ip: Option, /// Additional kv-pairs (besides tcp port, udp port and fork) that should be advertised to /// peers by including in local node record. pub(super) other_enr_kv_pairs: Vec<(&'static [u8], Bytes)>, @@ -300,6 +325,7 @@ impl Config { bootstrap_nodes: HashSet::default(), fork: None, tcp_socket: rlpx_tcp_socket, + advertised_ip: None, other_enr_kv_pairs: Vec::new(), lookup_interval: None, bootstrap_lookup_interval: None, diff --git a/crates/net/discv5/src/lib.rs b/crates/net/discv5/src/lib.rs index 5472e3c161a..7a77d8f6788 100644 --- a/crates/net/discv5/src/lib.rs +++ b/crates/net/discv5/src/lib.rs @@ -471,20 +471,26 @@ pub fn build_local_enr( ) -> (Enr, NodeRecord, Option<&'static [u8]>, IpMode) { let mut builder = discv5::enr::Enr::builder(); - let Config { discv5_config, fork, tcp_socket, other_enr_kv_pairs, .. } = config; + let Config { discv5_config, fork, tcp_socket, advertised_ip, other_enr_kv_pairs, .. } = config; let socket = { let v4 = crate::config::ipv4(&discv5_config.listen_config); let v6 = crate::config::ipv6(&discv5_config.listen_config); + // Prefer an explicit advertised IP for ENR IP fields. Listen sockets still supply UDP + // ports and determine which address-family fields are emitted. if let Some(addr) = v4 { - if *addr.ip() != Ipv4Addr::UNSPECIFIED { + if let Some(IpAddr::V4(ip)) = advertised_ip { + builder.ip4(*ip); + } else if *addr.ip() != Ipv4Addr::UNSPECIFIED { builder.ip4(*addr.ip()); } builder.udp4(addr.port()); } if let Some(addr) = v6 { - if *addr.ip() != Ipv6Addr::UNSPECIFIED { + if let Some(IpAddr::V6(ip)) = advertised_ip { + builder.ip6(*ip); + } else if *addr.ip() != Ipv6Addr::UNSPECIFIED { builder.ip6(*addr.ip()); } builder.udp6(addr.port()); diff --git a/crates/net/network/src/config.rs b/crates/net/network/src/config.rs index 8c13a21445a..8a51029f2fe 100644 --- a/crates/net/network/src/config.rs +++ b/crates/net/network/src/config.rs @@ -657,17 +657,25 @@ impl NetworkConfigBuilder { total_difficulty: chain_spec.genesis().difficulty, }); + let listener_addr = listener_addr.unwrap_or(DEFAULT_DISCOVERY_ADDRESS); + // Static NAT addresses (`extip`/`extaddr`) tell peers which IP to dial, but that IP may + // not exist on a local interface. Keep binding to `listener_addr` and use the NAT IP only + // as the ENR address. + let advertised_ip = nat.clone().and_then(|nat| nat.as_external_ip(listener_addr.port())); + discovery_v5_builder = discovery_v5_builder.map(|mut builder| { if let Some(network_stack_id) = NetworkStackId::id(&chain_spec) { let fork_id = chain_spec.fork_id(&head); builder = builder.fork(network_stack_id, fork_id) } + if let Some(ip) = advertised_ip { + builder = builder.advertised_ip(ip); + } + builder }); - let listener_addr = listener_addr.unwrap_or(DEFAULT_DISCOVERY_ADDRESS); - let mut hello_message = hello_message.unwrap_or_else(|| HelloMessage::builder(peer_id).build()); hello_message.port = listener_addr.port(); From 1021fc2c27fc212ad591328ae189011df812bc13 Mon Sep 17 00:00:00 2001 From: CPerezz <37264926+CPerezz@users.noreply.github.com> Date: Wed, 6 May 2026 16:29:25 +0200 Subject: [PATCH 045/335] feat(node, db-common): add --debug.skip-genesis-validation (#23919) Co-authored-by: Matthias Seitz --- crates/node/builder/src/launch/common.rs | 10 +++- crates/node/core/src/args/debug.rs | 10 ++++ crates/storage/db-common/src/init.rs | 69 ++++++++++++++++++++++++ docs/vocs/docs/pages/cli/reth/node.mdx | 3 ++ 4 files changed, 90 insertions(+), 2 deletions(-) diff --git a/crates/node/builder/src/launch/common.rs b/crates/node/builder/src/launch/common.rs index 94272368775..b1fc0d914c4 100644 --- a/crates/node/builder/src/launch/common.rs +++ b/crates/node/builder/src/launch/common.rs @@ -42,7 +42,9 @@ use reth_chainspec::{Chain, EthChainSpec, EthereumHardforks}; use reth_config::{config::EtlConfig, PruneConfig}; use reth_consensus::noop::NoopConsensus; use reth_db_api::{database::Database, database_metrics::DatabaseMetrics}; -use reth_db_common::init::{init_genesis_with_settings, InitStorageError}; +use reth_db_common::init::{ + init_genesis_with_settings, init_genesis_with_settings_and_validate, InitStorageError, +}; use reth_downloaders::{bodies::noop::NoopBodiesDownloader, headers::noop::NoopHeaderDownloader}; use reth_engine_local::MiningMode; use reth_evm::{noop::NoopEvmConfig, ConfigureEvm}; @@ -660,7 +662,11 @@ where /// Convenience function to [`Self::init_genesis`] pub fn with_genesis(self) -> Result { - init_genesis_with_settings(self.provider_factory(), self.node_config().storage_settings())?; + init_genesis_with_settings_and_validate( + self.provider_factory(), + self.node_config().storage_settings(), + !self.node_config().debug.skip_genesis_validation, + )?; Ok(self) } diff --git a/crates/node/core/src/args/debug.rs b/crates/node/core/src/args/debug.rs index dce3aa784e6..037f08c31ab 100644 --- a/crates/node/core/src/args/debug.rs +++ b/crates/node/core/src/args/debug.rs @@ -58,6 +58,15 @@ pub struct DebugArgs { #[arg(long = "debug.skip-new-payload", help_heading = "Debug")] pub skip_new_payload: Option, + /// If set, bypasses genesis hash validation during init. + /// Intended for tools that direct-write the database (e.g. snapshot + /// importers, state-actor) and want reth to trust the DB-resident + /// genesis state instead of recomputing it from the chainspec's alloc. + /// When the bypass fires, a structured `tracing::warn!` is emitted so + /// the divergence stays observable in operator logs. + #[arg(long = "debug.skip-genesis-validation", help_heading = "Debug")] + pub skip_genesis_validation: bool, + /// If provided, the chain will be reorged at specified frequency. #[arg(long = "debug.reorg-frequency", help_heading = "Debug")] pub reorg_frequency: Option, @@ -120,6 +129,7 @@ impl Default for DebugArgs { rpc_consensus_url: None, skip_fcu: None, skip_new_payload: None, + skip_genesis_validation: false, reorg_frequency: None, reorg_depth: None, engine_api_store: None, diff --git a/crates/storage/db-common/src/init.rs b/crates/storage/db-common/src/init.rs index f42eca05925..ada8fec152a 100644 --- a/crates/storage/db-common/src/init.rs +++ b/crates/storage/db-common/src/init.rs @@ -138,6 +138,39 @@ pub fn init_genesis_with_settings( factory: &PF, genesis_storage_settings: StorageSettings, ) -> Result +where + PF: DatabaseProviderFactory + + StaticFileProviderFactory> + + ChainSpecProvider + + StageCheckpointReader + + BlockNumReader + + MetadataProvider + + StorageSettingsCache, + PF::ProviderRW: StaticFileProviderFactory + + StageCheckpointWriter + + HistoryWriter + + HeaderProvider + + HashingWriter + + StateWriter + + TrieWriter + + MetadataWriter + + ChainSpecProvider + + StorageSettingsCache + + RocksDBProviderFactory + + NodePrimitivesProvider + + AsRef, + PF::ChainSpec: EthChainSpec

::BlockHeader>, +{ + init_genesis_with_settings_and_validate(factory, genesis_storage_settings, true) +} + +/// Write the genesis block if it has not already been written with [`StorageSettings`], +/// optionally validating the DB-resident genesis hash against the chainspec hash. +pub fn init_genesis_with_settings_and_validate( + factory: &PF, + genesis_storage_settings: StorageSettings, + validate_genesis_hash: bool, +) -> Result where PF: DatabaseProviderFactory + StaticFileProviderFactory> @@ -196,6 +229,15 @@ where return Ok(hash) } + if !validate_genesis_hash { + warn!( + target: "reth::storage", + chainspec_hash = %hash, + storage_hash = %block_hash, + "Genesis hash mismatch with chainspec; trusting DB per --debug.skip-genesis-validation" + ); + return Ok(block_hash) + } return Err(InitStorageError::GenesisHashMismatch { chainspec_hash: hash, storage_hash: block_hash, @@ -1044,6 +1086,33 @@ mod tests { )) } + #[test] + fn skip_genesis_hash_validation_accepts_mismatched_db() { + let factory = create_test_provider_factory_with_chain_spec(SEPOLIA.clone()); + let static_file_provider = factory.static_file_provider(); + let rocksdb_provider = factory.rocksdb_provider(); + init_genesis(&factory).unwrap(); + + let result = init_genesis_with_settings_and_validate( + &ProviderFactory::::new( + factory.into_db(), + MAINNET.clone(), + static_file_provider, + rocksdb_provider, + reth_tasks::Runtime::test(), + ) + .unwrap(), + StorageSettings::base(), + false, + ); + + let returned = result.expect("skip_genesis_validation should suppress mismatch error"); + assert_eq!( + returned, SEPOLIA_GENESIS_HASH, + "bypass returns the DB-resident hash, not the chainspec hash", + ); + } + #[test] fn init_genesis_history() { let address_with_balance = Address::with_last_byte(1); diff --git a/docs/vocs/docs/pages/cli/reth/node.mdx b/docs/vocs/docs/pages/cli/reth/node.mdx index 0b687232dcb..14b9c127ce5 100644 --- a/docs/vocs/docs/pages/cli/reth/node.mdx +++ b/docs/vocs/docs/pages/cli/reth/node.mdx @@ -753,6 +753,9 @@ Debug: --debug.skip-new-payload If provided, the engine will skip `n` consecutive new payloads + --debug.skip-genesis-validation + If set, bypasses genesis hash validation during init. Intended for tools that direct-write the database (e.g. snapshot importers, state-actor) and want reth to trust the DB-resident genesis state instead of recomputing it from the chainspec's alloc. When the bypass fires, a structured `tracing::warn!` is emitted so the divergence stays observable in operator logs + --debug.reorg-frequency If provided, the chain will be reorged at specified frequency From 63cd25010282800fb45bfbe977c5cc73afa73755 Mon Sep 17 00:00:00 2001 From: onbjerg Date: Wed, 6 May 2026 16:52:26 +0200 Subject: [PATCH 046/335] feat(bench): route PR benchmarks through txgen (#23869) --- .github/scripts/bench-reth-summary.py | 13 + .github/scripts/bench-txgen-build.sh | 59 ++++ .github/scripts/bench-txgen-install.sh | 37 +++ .../scripts/bench-txgen-report-to-reth-csv.py | 107 +++++++ .github/scripts/bench-txgen-run.sh | 293 ++++++++++++++++++ .github/scripts/bench-utils.js | 6 + .github/workflows/bench.yml | 86 ++++- 7 files changed, 588 insertions(+), 13 deletions(-) create mode 100755 .github/scripts/bench-txgen-build.sh create mode 100755 .github/scripts/bench-txgen-install.sh create mode 100755 .github/scripts/bench-txgen-report-to-reth-csv.py create mode 100755 .github/scripts/bench-txgen-run.sh diff --git a/.github/scripts/bench-reth-summary.py b/.github/scripts/bench-reth-summary.py index 300006cad6c..9871409178e 100755 --- a/.github/scripts/bench-reth-summary.py +++ b/.github/scripts/bench-reth-summary.py @@ -385,6 +385,8 @@ def generate_comparison_table( warmup_blocks: str | None = None, wait_time: str | None = None, bal_mode: str | None = None, + driver: str | None = None, + driver_reason: str | None = None, ) -> str: """Generate a markdown comparison table between baseline and feature.""" n = paired["blocks"] @@ -431,6 +433,11 @@ def pct(base: float, feat: float) -> float: "", ] meta_parts = [f"{n} {'big blocks' if big_blocks else 'blocks'}"] + if driver: + driver_label = driver + if driver_reason: + driver_label += f" (fallback: {driver_reason})" + meta_parts.append(f"driver: {driver_label}") if warmup_blocks: meta_parts.append(f"{warmup_blocks} warmup") if wait_time: @@ -521,6 +528,8 @@ def main(): parser.add_argument("--warmup-blocks", default=None, help="Number of warmup blocks") parser.add_argument("--wait-time", default=None, help="Wait time interval used between blocks") parser.add_argument("--bal-mode", default=None, help="BAL mode (true, feature, baseline)") + parser.add_argument("--driver", default=None, help="Benchmark driver used for this run") + parser.add_argument("--driver-reason", default=None, help="Why the benchmark fell back to this driver") parser.add_argument("--grafana-url", default=None, help="Grafana dashboard URL for this benchmark run") args = parser.parse_args() @@ -575,6 +584,8 @@ def main(): warmup_blocks=args.warmup_blocks, wait_time=args.wait_time, bal_mode=bal_mode, + driver=args.driver, + driver_reason=args.driver_reason, ) print(f"Generated comparison ({paired_stats['n']} paired blocks, " f"mean diff {paired_stats['mean_diff_ms']:+.3f}ms ± {paired_stats['ci_ms']:.3f}ms)") @@ -605,6 +616,8 @@ def main(): summary = { "blocks": paired_stats["blocks"], + "driver": args.driver, + "driver_reason": args.driver_reason, "big_blocks": args.big_blocks, "warmup_blocks": args.warmup_blocks, "wait_time": args.wait_time, diff --git a/.github/scripts/bench-txgen-build.sh b/.github/scripts/bench-txgen-build.sh new file mode 100755 index 00000000000..80b1ab3a753 --- /dev/null +++ b/.github/scripts/bench-txgen-build.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# +# Builds the node binary for the txgen-backed PR benchmark path. +# +# Usage: bench-txgen-build.sh +# +# This intentionally does not build or install reth-bench. Big-block benchmarks +# still use the legacy reth-bench path because txgen does not yet replay the +# reth-bb payload/env-switch/BAL format. +set -euxo pipefail + +MODE="$1" +SOURCE_DIR="$2" +COMMIT="$3" + +if [ "${BENCH_BIG_BLOCKS:-false}" = "true" ]; then + echo "::error::txgen path does not support big-block benchmarks yet; use the reth-bench driver" + exit 1 +fi + +EXTRA_FEATURES="" +EXTRA_RUSTFLAGS="" +if [ "${BENCH_TRACY:-off}" != "off" ]; then + EXTRA_FEATURES="tracy,tracy-client/ondemand" + EXTRA_RUSTFLAGS=" -C force-frame-pointers=yes" +fi + +build_node_binary() { + local features_arg="" + local workspace_arg="" + + cd "$SOURCE_DIR" + if [ -n "$EXTRA_FEATURES" ]; then + features_arg="--features ${EXTRA_FEATURES}" + workspace_arg="--workspace" + fi + + # shellcheck disable=SC2086 + RUSTFLAGS="-C target-cpu=native${EXTRA_RUSTFLAGS}" \ + cargo build --locked --profile profiling --bin reth $workspace_arg $features_arg +} + +case "$MODE" in + baseline|main) + echo "Building baseline reth (${COMMIT}) from source for txgen benchmark..." + build_node_binary + ;; + + feature|branch) + echo "Building feature reth (${COMMIT}) from source for txgen benchmark..." + rustup show active-toolchain || rustup default stable + build_node_binary + ;; + + *) + echo "Usage: $0 " + exit 1 + ;; +esac diff --git a/.github/scripts/bench-txgen-install.sh b/.github/scripts/bench-txgen-install.sh new file mode 100755 index 00000000000..e3334dc2a3b --- /dev/null +++ b/.github/scripts/bench-txgen-install.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# +# Installs the txgen tools used by the txgen-backed PR benchmark path. +# Keep this separate from bench-reth-build.sh so scheduled benchmarks can keep +# using the legacy reth-bench runner until they are migrated explicitly. +# +# Required env: +# TXGEN_REV – pinned txgen git revision +# Optional env: +# TXGEN_REPO – txgen repository URL (default: https://github.com/tempoxyz/txgen) +set -euxo pipefail + +: "${TXGEN_REV:?TXGEN_REV must be set to a pinned txgen revision}" + +TXGEN_REPO="${TXGEN_REPO:-https://github.com/tempoxyz/txgen}" + +# txgen is private. Prefer the deploy key secret; fall back to token auth for +# local/manual runs. Use the git CLI so cargo honors the auth configuration. +if [ -n "${TXGEN_DEPLOY_KEY:-}" ]; then + set +x + mkdir -p "$HOME/.ssh" + printf '%s\n' "$TXGEN_DEPLOY_KEY" > "$HOME/.ssh/txgen_deploy_key" + chmod 600 "$HOME/.ssh/txgen_deploy_key" + ssh-keyscan github.com >> "$HOME/.ssh/known_hosts" 2>/dev/null + export GIT_SSH_COMMAND="ssh -i $HOME/.ssh/txgen_deploy_key -o IdentitiesOnly=yes" + set -x + TXGEN_REPO="${TXGEN_SSH_REPO:-ssh://git@github.com/tempoxyz/txgen.git}" +elif [ -n "${TXGEN_TOKEN:-${GH_PROJECT_TOKEN:-${DEREK_PAT:-${DEREK_TOKEN:-}}}}" ]; then + AUTH_TOKEN="${TXGEN_TOKEN:-${GH_PROJECT_TOKEN:-${DEREK_PAT:-${DEREK_TOKEN:-}}}}" + set +x + git config --global url."https://x-access-token:${AUTH_TOKEN}@github.com/".insteadOf "https://github.com/" + set -x +fi +export CARGO_NET_GIT_FETCH_WITH_CLI=true + +cargo install --git "$TXGEN_REPO" --rev "$TXGEN_REV" txgen-ethereum --bin txgen-ethereum --locked +cargo install --git "$TXGEN_REPO" --rev "$TXGEN_REV" bench-cli --bin bench --locked diff --git a/.github/scripts/bench-txgen-report-to-reth-csv.py b/.github/scripts/bench-txgen-report-to-reth-csv.py new file mode 100755 index 00000000000..01620d94260 --- /dev/null +++ b/.github/scripts/bench-txgen-report-to-reth-csv.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +"""Convert txgen `bench send-blocks` JSON into the legacy reth-bench CSVs. + +The PR benchmark rendering pipeline still consumes `combined_latency.csv` and +`total_gas.csv`. This adapter lets the txgen-backed runner reuse the existing +summary/charts/slack code while we migrate those consumers to txgen JSON. +""" + +import argparse +import csv +import json +from pathlib import Path + + +def opt_int(value, default=None): + if value is None: + return default + return int(value) + + +def block_latency_us(block: dict) -> tuple[int, int, int]: + # txgen currently records server newPayload latency in microseconds but + # client-side forkchoiceUpdated latency in milliseconds. + new_payload_us = opt_int(block.get("new_payload_server_latency_us")) + if new_payload_us is None: + new_payload_us = opt_int(block.get("new_payload_ms"), 0) * 1000 + fcu_us = opt_int(block.get("forkchoice_updated_ms"), 0) * 1000 + return new_payload_us, fcu_us, new_payload_us + fcu_us + + +def main() -> None: + parser = argparse.ArgumentParser(description="Convert txgen JSON report to reth-bench CSVs") + parser.add_argument("report", help="txgen JSON report path") + parser.add_argument("output_dir", help="directory for combined_latency.csv and total_gas.csv") + args = parser.parse_args() + + report_path = Path(args.report) + output_dir = Path(args.output_dir) + output_dir.mkdir(parents=True, exist_ok=True) + + with report_path.open() as f: + report = json.load(f) + + blocks = report.get("blocks") or [] + if not blocks: + raise SystemExit(f"txgen report {report_path} does not contain any blocks") + + combined_path = output_dir / "combined_latency.csv" + with combined_path.open("w", newline="") as f: + writer = csv.DictWriter( + f, + fieldnames=[ + "block_number", + "gas_limit", + "transaction_count", + "gas_used", + "new_payload_latency", + "fcu_latency", + "total_latency", + "persistence_wait", + "execution_cache_wait", + "sparse_trie_wait", + ], + ) + writer.writeheader() + for block in blocks: + new_payload_us, fcu_us, total_us = block_latency_us(block) + writer.writerow( + { + "block_number": block["number"], + "gas_limit": block["gas_limit"], + "transaction_count": block["tx_count"], + "gas_used": block["gas_used"], + "new_payload_latency": new_payload_us, + "fcu_latency": fcu_us, + "total_latency": total_us, + "persistence_wait": block.get("persistence_wait_us") or 0, + "execution_cache_wait": block.get("execution_cache_wait_us") or 0, + "sparse_trie_wait": block.get("sparse_trie_wait_us") or 0, + } + ) + + total_gas_path = output_dir / "total_gas.csv" + elapsed_us = 0 + with total_gas_path.open("w", newline="") as f: + writer = csv.DictWriter( + f, + fieldnames=["block_number", "transaction_count", "gas_used", "time"], + ) + writer.writeheader() + for block in blocks: + _, _, total_us = block_latency_us(block) + elapsed_us += total_us + writer.writerow( + { + "block_number": block["number"], + "transaction_count": block["tx_count"], + "gas_used": block["gas_used"], + "time": elapsed_us, + } + ) + + print(f"Wrote legacy CSVs from {report_path} to {output_dir}") + + +if __name__ == "__main__": + main() diff --git a/.github/scripts/bench-txgen-run.sh b/.github/scripts/bench-txgen-run.sh new file mode 100755 index 00000000000..e759ab19024 --- /dev/null +++ b/.github/scripts/bench-txgen-run.sh @@ -0,0 +1,293 @@ +#!/usr/bin/env bash +# +# Runs a single txgen-backed Engine API benchmark cycle: +# mount snapshot → start node → extract source blocks → warmup → send-blocks → +# convert txgen JSON report into the legacy reth-bench CSVs. +# +# Usage: bench-txgen-run.sh